文档章节

【Apollo播放器】源码分析之图片加载模式

军歌
 军歌
发布于 2014/03/05 15:36
字数 2041
阅读 486
收藏 3

图片加载有很多,部份都是使用LruCache、弱引用、软引用等编写的,其编目的就是优化内存、缓存、加载效果,不过,它们都是各有千秋,下面看看【Apollo播放器】它是对图片加载如何做的

它主要结构如下:

ImageInfo          - 图片信息相关

ImageCache       - 图片缓存相关

ImageProvider    -图片提供者相关(管理器)

GetBitmapTask   -图片下载任务

 下面一一分析每个类的结构和使用

ImageInfo.java

public class ImageInfo {
	
	//图像类型
	//专辑(ablum), 艺术家(artist), 播放列表(playlist), 流派genre
	public String type;
	
	//图像来源
	//lastfm - 来自网站
	//file - 来自音频
	//gallery - 来自相册
	//first_avail - 来自 'file' 或 'lastfm'
	public String source;
	
	//请求的图像的大小
	//缩略图  or 正常图
	public String size;
	
	//执行图像获取所需的额外数据
	//lastFM -    艺术家需要艺术家的形象
	//ablum  -    专辑、歌手专辑图像
	//file 	-     需要相册id
	//gallery -   需要图像的文件路径
	//first_available - 需要两个文件lastfm数据		
	public String[] data;
	
}

ImageCache.java

public final class ImageCache {

    private static final String TAG = ImageCache.class.getSimpleName();

    //二级缓存实例对象,其底层是继承自LinkedHashMap<K, V>
    //具体实现请看源码:http://androidxref.com/4.4.2_r1/xref/packages/apps/UnifiedEmail/src/com/android/mail/utils/LruCache.java
    private LruCache<String, Bitmap> mLruCache;

    private static ImageCache sInstance;

    public ImageCache(final Context context) {
        init(context);
    }

    /**
     * 图片缓存单例模式
     * @param context
     * @return
     */
    public final static ImageCache getInstance(final Context context) {
        if (sInstance == null) {
            sInstance = new ImageCache(context.getApplicationContext());
        }
        return sInstance;
    }

    public void init(final Context context) {
        final ActivityManager activityManager = (ActivityManager)context
                .getSystemService(Context.ACTIVITY_SERVICE);
        final int lruCacheSize = Math.round(0.25f * activityManager.getMemoryClass()
                * 1024 * 1024);//四舍五入计算二级缓存大小
        mLruCache = new LruCache<String, Bitmap>(lruCacheSize) {
            @Override
            protected int sizeOf(final String paramString, final Bitmap paramBitmap) {
            	//返回每个key所对应的values在二级缓存中报占大小 
                return paramBitmap.getByteCount();
            }

        };
    }

    /**
     * 查找或创建的高速缓存
     * @param activity
     * @return
     */
    public static final ImageCache findOrCreateCache(final Activity activity) {    	
    	FragmentManager nFragmentManger = activity.getFragmentManager();
        RetainFragment retainFragment = (RetainFragment)nFragmentManger.findFragmentByTag(TAG);
        if (retainFragment == null) {
            retainFragment = new RetainFragment();
            nFragmentManger.beginTransaction().add(retainFragment, TAG).commit();
        }
        ImageCache cache = (ImageCache)retainFragment.getObject();
        if (cache == null) {
            cache = getInstance(activity);
            retainFragment.setObject(cache);
        }
        return cache;
    }

    /**
     * 存入缓存
     * @param data
     * @param bitmap
     */
    public void add(final String data, final Bitmap bitmap) {
        if (data == null || bitmap == null) {
            return;
        }
        if (get(data) == null) {
            mLruCache.put(data, bitmap);
        }
    }

    /**
     * 获取缓存图片
     * @param data
     * @return
     */
    public final Bitmap get(final String data) {
        if (data == null) {
            return null;
        }
        if (mLruCache != null) {
            final Bitmap mBitmap = mLruCache.get(data);
            if (mBitmap != null) {
                return mBitmap;
            }
        }
        return null;
    }

    /**
     * 移除指定缓存
     */
    public void remove(final String key) {
        if (mLruCache != null) {
            mLruCache.remove(key);
        }
    }
    
    /**
     * 清除缴存并回收
     */
    public void clearMemCache() {
        if (mLruCache != null) {
            mLruCache.evictAll();
        }
        System.gc();
    }

    /**
     * 个人理解为是 保持应用(FragmentManager)只存在这一个实例
     * @author xiajun
     *
     */
    public static final class RetainFragment extends Fragment {

        private Object mObject;

        public RetainFragment() {
        }
        @Override
        public void onCreate(final Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            // Make sure this Fragment is retained over a configuration change
            setRetainInstance(true);
        }
        public void setObject(final Object object) {
            mObject = object;
        }
        public Object getObject() {
            return mObject;
        }
    }
}

ImageProvider.java

public class ImageProvider implements GetBitmapTask.OnBitmapReadyListener{

    private ImageCache memCache;

    private Map<String, Set<ImageView>> pendingImagesMap = new HashMap<String, Set<ImageView>>();

    private Set<String> unavailable = new HashSet<String>();
    
    private Context mContext;
    
    private int thumbSize;
    
    private static  ImageProvider mInstance;
    
    protected ImageProvider( Activity activity ) {
    	mContext = activity;
    	memCache = ImageCache.getInstance( activity );
    	Resources resources = mContext.getResources();
    	DisplayMetrics metrics = resources.getDisplayMetrics();
    	thumbSize = (int) ( ( 153 * (metrics.densityDpi/160f) ) + 0.5f );
    }
    
    public final static ImageProvider getInstance( final Activity activity ) {    	
        if (mInstance == null) {
            mInstance = new ImageProvider( activity );
            mInstance.setImageCache(ImageCache.findOrCreateCache(activity));
        }
        return mInstance;        
    }
    
    public void setImageCache(final ImageCache cacheCallback) {
    	memCache = cacheCallback;
    }
    
    /**
     * 加载图片
     * @param imageView 显示的View
     * @param imageInfo 图片信息相关
     */
    public void loadImage( ImageView imageView, ImageInfo imageInfo ){
    	//创建一个图片标识,例如:21albartimg 图片命名和创建规则,请详细查看ImageUtils.java
    	String tag = ImageUtils.createShortTag(imageInfo) + imageInfo.size;
    	if( imageInfo.source.equals(SRC_FILE) || imageInfo.source.equals(SRC_LASTFM) || imageInfo.source.equals(SRC_GALLERY)){
    		clearFromMemoryCache( ImageUtils.createShortTag(imageInfo) );
    		asyncLoad( tag, imageView, new GetBitmapTask( thumbSize, imageInfo, this, imageView.getContext() ) );
		}
    	if(!setCachedBitmap(imageView, tag)){
            asyncLoad( tag, imageView, new GetBitmapTask( thumbSize, imageInfo, this, imageView.getContext() ) );
        }
    }

    /**
     * 缓存位图
     * @param imageView
     * @param tag
     * @return
     */
    private boolean setCachedBitmap(ImageView imageView, String tag) {
        if (unavailable.contains(tag)) {
            handleBitmapUnavailable(imageView, tag);
            return true;
        }
        Bitmap bitmap = memCache.get(tag);
        if (bitmap == null)
            return false;
        imageView.setTag(tag);
        imageView.setImageBitmap(bitmap);
        return true;
    }

    /**
     * 处理位图不可用
     * @param imageView
     * @param tag
     */
    private void handleBitmapUnavailable(ImageView imageView, String tag) {
        imageView.setTag(tag);
        imageView.setImageDrawable(null);
    }

    /**
     * 加载位图
     * @param imageView
     * @param bitmap
     * @param tag
     */
    private void setLoadedBitmap(ImageView imageView, Bitmap bitmap, String tag) {
        if (!tag.equals(imageView.getTag()))
            return;

        final TransitionDrawable transition = new TransitionDrawable(new Drawable[]{
                new ColorDrawable(android.R.color.transparent),
                new BitmapDrawable(imageView.getResources(), bitmap)
        });

        imageView.setImageDrawable(transition);
        final int duration = imageView.getResources().getInteger(R.integer.image_fade_in_duration);
        transition.startTransition(duration);
    }

    /**
     * 异步加载
     * @param tag
     * @param imageView
     * @param task
     */
    private void asyncLoad(String tag, ImageView imageView, AsyncTask<String, Integer, Bitmap> task) {
        Set<ImageView> pendingImages = pendingImagesMap.get(tag);
        if (pendingImages == null) {
            pendingImages = Collections.newSetFromMap(new WeakHashMap<ImageView, Boolean>()); // create weak set
            pendingImagesMap.put(tag, pendingImages);
            task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);//可用于并行执行任务的执行
        }
        pendingImages.add(imageView);
        imageView.setTag(tag);
        imageView.setImageDrawable(null);
    }

    @Override
    public void bitmapReady(Bitmap bitmap, String tag) {
        if (bitmap == null) {
            unavailable.add(tag);
        }
        else
        {
            memCache.add(tag, bitmap);
        }
        Set<ImageView> pendingImages = pendingImagesMap.get(tag);
        if (pendingImages != null) {
            pendingImagesMap.remove(tag);
            for (ImageView imageView : pendingImages) {
                setLoadedBitmap(imageView, bitmap, tag);
            }
        }
    }
    
    /**
     * 清除内存缓存
     * @param tag
     */
    public void clearFromMemoryCache(String tag){
        if (unavailable.contains(tag + SIZE_THUMB)) {
        	unavailable.remove(tag + SIZE_THUMB);
        }
        if (pendingImagesMap.get(tag + SIZE_THUMB)!=null){
        	pendingImagesMap.remove(tag + SIZE_THUMB);
        }
        if (memCache.get(tag + SIZE_THUMB)!=null){
        	memCache.remove(tag + SIZE_THUMB);
        }
        if (unavailable.contains(tag + SIZE_NORMAL)) {
        	unavailable.remove(tag + SIZE_NORMAL);
        }
        if (pendingImagesMap.get(tag + SIZE_NORMAL)!=null){
        	pendingImagesMap.remove(tag + SIZE_NORMAL);
        }
        if (memCache.get(tag + SIZE_NORMAL)!=null){
        	memCache.remove(tag + SIZE_NORMAL);
        }
    }
    
    /**
     * 清除所有缓存(内存和磁盘)
     */
    public void clearAllCaches(){
    	try{
    		ImageUtils.deleteDiskCache(mContext);
    		memCache.clearMemCache();
    	}
    	catch(Exception e){}
    }
}

GetBitmapTask.java

public class GetBitmapTask extends AsyncTask<String, Integer, Bitmap> {

    
    private WeakReference<OnBitmapReadyListener> mListenerReference;

    private WeakReference<Context> mContextReference;

    private ImageInfo mImageInfo;
    
    private int mThumbSize;

    public GetBitmapTask( int thumbSize, ImageInfo imageInfo, OnBitmapReadyListener listener, Context context ) {
        mListenerReference = new WeakReference<OnBitmapReadyListener>(listener);
        mContextReference = new WeakReference<Context>(context);
        mImageInfo = imageInfo;
    	mThumbSize = thumbSize;
    }

    @Override
    protected Bitmap doInBackground(String... ignored) {
        Context context = mContextReference.get();
        if (context == null) {
            return null;
        }
        //返回图像信息来源于...
        File nFile = null;
        
        if( mImageInfo.source.equals(SRC_FILE)  && !isCancelled()){
        	nFile = ImageUtils.getImageFromMediaStore( context, mImageInfo );
        }
        else if ( mImageInfo.source.equals(SRC_LASTFM)  && !isCancelled()){
        	nFile = ImageUtils.getImageFromWeb( context, mImageInfo );
        }
        else if ( mImageInfo.source.equals(SRC_GALLERY)  && !isCancelled()){
        	nFile = ImageUtils.getImageFromGallery( context, mImageInfo );
        }        	
        else if ( mImageInfo.source.equals(SRC_FIRST_AVAILABLE)  && !isCancelled()){
        	Bitmap bitmap = null;
        	if( mImageInfo.size.equals( SIZE_NORMAL ) ){
        		bitmap = ImageUtils.getNormalImageFromDisk( context, mImageInfo );
        	}
        	else if( mImageInfo.size.equals( SIZE_THUMB ) ){
        		bitmap = ImageUtils.getThumbImageFromDisk( context, mImageInfo, mThumbSize );
        	}
        	//if we have a bitmap here then its already properly sized
        	if( bitmap != null ){
        		return bitmap;
        	}
        	
        	if( mImageInfo.type.equals( TYPE_ALBUM ) ){
        		nFile = ImageUtils.getImageFromMediaStore( context, mImageInfo );
        	}
        	if( nFile == null && ( mImageInfo.type.equals( TYPE_ALBUM ) || mImageInfo.type.equals( TYPE_ARTIST ) ) )
        		nFile = ImageUtils.getImageFromWeb( context, mImageInfo );
        }
        if( nFile != null ){        	
        	//返回正常大小的图
        	if( mImageInfo.size.equals( SIZE_NORMAL ) )
        		return BitmapFactory.decodeFile(nFile.getAbsolutePath());
        	//返回缩略图
        	return ImageUtils.getThumbImageFromDisk( context, nFile, mThumbSize );
        }
        return null;
    }
    
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        super.onPostExecute(bitmap);
        OnBitmapReadyListener listener = mListenerReference.get();
        if(bitmap == null && !isCancelled()){
        	if(mImageInfo.size.equals(SIZE_THUMB))
        		bitmap = BitmapFactory.decodeResource(mContextReference.get().getResources(),
        													R.drawable.no_art_small);
        	else if(mImageInfo.size.equals(SIZE_NORMAL))
        		bitmap = BitmapFactory.decodeResource(mContextReference.get().getResources(),
        													R.drawable.no_art_normal);
        }
        if (bitmap != null && !isCancelled()) {
            if (listener != null) {
                	listener.bitmapReady(bitmap,  ImageUtils.createShortTag(mImageInfo) + mImageInfo.size );
            }
        }
    }

    public static interface OnBitmapReadyListener {
        public void bitmapReady(Bitmap bitmap, String tag);
    }
}

下面看看ImageUtils这个类的格式命名规则

ImageUtils.java

public class ImageUtils {
	
	//图片格式
    private static final String IMAGE_EXTENSION = ".img";
	
	private static File getFile(Context context, ImageInfo imageInfo){
		return new File(context.getExternalCacheDir(), createShortTag(imageInfo)+IMAGE_EXTENSION);
	}
	
	/**
	 * 从磁盘中获取正常图片
	 * @param context
	 * @param imageInfo
	 * @return
	 */
	public static Bitmap getNormalImageFromDisk( Context context, ImageInfo imageInfo ){
		Bitmap bitmap = null;
		File nFile = getFile( context, imageInfo );
		if(nFile.exists()){
			bitmap = BitmapFactory.decodeFile( nFile.getAbsolutePath() );
		}		
		return bitmap;
	}
	
	/**
	 * 从磁盘中获取指定大小的缩略图片
	 * @param context
	 * @param imageInfo
	 * @param thumbSize
	 * @return
	 */
	public static Bitmap getThumbImageFromDisk( Context context, ImageInfo imageInfo, int thumbSize ){
		File nFile = getFile( context, imageInfo );
		return getThumbImageFromDisk( context, nFile, thumbSize );
	}
	
	
	/**
	 * 从磁盘中获取指定大小的缩略图片
	 * @param context
	 * @param nFile
	 * @param thumbSize
	 * @return
	 */
	public static Bitmap getThumbImageFromDisk( Context context, File nFile, int thumbSize ){
		if(!nFile.exists())
			return null;
		final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        try{
        	BitmapFactory.decodeFile( nFile.getAbsolutePath() , options);
        }
        catch(Exception e){
        }
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
        if (height > thumbSize || width > thumbSize) {
             if (width > height) {
                 inSampleSize = Math.round((float)height / (float)thumbSize);
             } else {
                 inSampleSize = Math.round((float)width / (float)thumbSize);
             }
             final float totalPixels = width * height;
             final float totalReqPixelsCap = thumbSize * thumbSize * 2;
             while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
                 inSampleSize++;
             }
        }
        options.inSampleSize = inSampleSize;
        options.inJustDecodeBounds = false;
        Bitmap bitmap = null;
		try{
			bitmap = BitmapFactory.decodeFile( nFile.getAbsolutePath() , options );
		}
		catch (Exception e){
		}
        return  bitmap;
	}

	/**
	 * 获取相册中的图片
	 * @param context
	 * @param imageInfo
	 * @return
	 */
	public static File getImageFromGallery( Context context, ImageInfo imageInfo ){		
		String albumArt = ( imageInfo.type == TYPE_ALBUM ) ? imageInfo.data[3] : imageInfo.data[1];	  
        if(albumArt != null){
        	try{
        		File orgFile = new File(albumArt);
        		File newFile = new File(context.getExternalCacheDir(), createShortTag(imageInfo)+IMAGE_EXTENSION);
        		InputStream in = new FileInputStream(orgFile);
        		OutputStream out = new FileOutputStream(newFile);
        		byte[] buf = new byte[1024];
        		int len;
        		while ((len = in.read(buf)) > 0){
        			out.write(buf, 0, len);
        		}
        		in.close();
        		out.close();
	        	return newFile;
        	}
        	catch( Exception e){
        	}
        }
        return null;
	}
	
	/**
	 * 获取Web的图片
	 * @param context
	 * @param imageInfo
	 * @return
	 */
    public static File getImageFromWeb( Context context, ImageInfo imageInfo ) {
    	String imageUrl = null;
        try {
	        if( imageInfo.type.equals(TYPE_ALBUM) ){  
	        	Album image = Album.getInfo(imageInfo.data[1], imageInfo.data[2], LASTFM_API_KEY);
	            if (image != null) {
	            	imageUrl = image.getLargestImage();
	            }
	        }
	        else if ( imageInfo.type.equals(TYPE_ARTIST) ) {
		        PaginatedResult<Image> images = Artist.getImages(imageInfo.data[0], 2, 1, LASTFM_API_KEY);
	            Iterator<Image> iterator = images.getPageResults().iterator();
	            if (iterator.hasNext()) {
		            Image image = iterator.next();	
			        imageUrl = image.getLargestImage();
	            }
	        }
        } catch ( Exception e ) {
        	return null;
        }
        if ( imageUrl == null || imageUrl.isEmpty() ) {
            return null;
        }
        File newFile = getFile( context, imageInfo );
        ApolloUtils.downloadFile( imageUrl, newFile );
		if (newFile.exists()) {
            return newFile;
        }
        return null;
    }    

    /**
     * 获取多媒体图片
     * @param context
     * @param imageInfo
     * @return
     */
    public static File getImageFromMediaStore( Context context, ImageInfo imageInfo ){
    	String mAlbum = imageInfo.data[0];
    	String[] projection = {
                BaseColumns._ID, Audio.Albums._ID, Audio.Albums.ALBUM_ART, Audio.Albums.ALBUM
        };
        Uri uri = Audio.Albums.EXTERNAL_CONTENT_URI;
        Cursor cursor = null;
        try{
        	cursor = context.getContentResolver().query(uri, projection, BaseColumns._ID+ "=" + DatabaseUtils.sqlEscapeString(mAlbum), null, null);
        }
        catch(Exception e){
        	e.printStackTrace();        	
        }
        int column_index = cursor.getColumnIndex(Audio.Albums.ALBUM_ART);
        if(cursor.getCount()>0){
	    	cursor.moveToFirst();
	        String albumArt = cursor.getString(column_index);	  
	        if(albumArt != null){
	        	try{
	        		File orgFile = new File(albumArt);
	        		File newFile = new File(context.getExternalCacheDir(), createShortTag(imageInfo)+IMAGE_EXTENSION);
	        		InputStream in = new FileInputStream(orgFile);
	        		OutputStream out = new FileOutputStream(newFile);
	        		byte[] buf = new byte[1024];
	        		int len;
	        		while ((len = in.read(buf)) > 0){
	        			out.write(buf, 0, len);
	        		}
	        		in.close();
	        		out.close();
	        		cursor.close();
		        	return newFile;
	        	}
	        	catch( Exception e){
	        	}
	        }
        }
        return null;
    }
    
    /**
     * 删除磁盘缓存
     * @param context
     * @throws IOException
     */
    public static void deleteDiskCache(Context context) throws IOException {
    	final File dir = context.getExternalCacheDir();
        final File[] files = dir.listFiles();
        for (final File file : files) {
            if (!file.delete()) {
                throw new IOException("failed to delete file: " + file);
            }
        }
    }
    
    /**
     * 创建图片标识
     * @param imageInf
     * @return
     */
    public static String createShortTag(ImageInfo imageInf){
    	String tag = null;
    	if( imageInf.type.equals( TYPE_ALBUM ) ){
    		//专辑 id + 专辑后缀 
    		tag = imageInf.data[0] + ALBUM_SUFFIX;
    	}
    	else if (imageInf.type.equals( TYPE_ARTIST )){
    		//艺术家名称 + 艺术家后缀
    		tag = imageInf.data[0] + ARTIST_SUFFIX;
    	}
    	else if (imageInf.type.equals( TYPE_GENRE )){
    		//流派名称 + 流派后缀
    		tag = imageInf.data[0] + GENRE_SUFFIX;
    	}
    	else if (imageInf.type.equals( TYPE_PLAYLIST )){
    		//流派名称 + 播放列表后缀
    		tag = imageInf.data[0] + PLAYLIST_SUFFIX;
    	}
    	ApolloUtils.escapeForFileSystem(tag);
    	return tag;
    }
    
}


【Apollo播放器】图片加载模式,就说到这里,期待后面更多代码分析....


© 著作权归作者所有

军歌
粉丝 9
博文 86
码字总数 43501
作品 0
深圳
程序员
私信 提问
Android使用得图SDK开发VR播放器

产品概述 Android SDK包含全景图片、VR视频、漫游主题等多种展示方式,支持小行星模式、陀螺仪、VR双屏沉浸式观看。文件下载、解码都在播放器中完成,您只需一个链接地址或是一条配置即可展现...

feng_blog
2016/05/23
899
0
iOS使用得图SDK开发VR播放器

产品概述 iOS SDK包含全景图片、VR视频、漫游主题等多种展示方式,支持小行星模式、陀螺仪、VR双屏沉浸式观看。您只需一个链接地址或是一条配置即可展现炫酷的全景效果。 下载并集成SDK (注意...

feng_blog
2016/05/23
1K
1
MPMoviePlayerController 视频播放器—IOS开发

MPMoviePlayerController 与AVAudioPlayer有点类似,前者播放视频,后者播放音频,不过也有很大不同,MPMoviePlayerController 可以直接通过远程URL初始化,而AVAudioPlayer则不可以。不过大...

孙启超
2013/08/16
0
0
Vue 全家桶实现网易云音乐 WebApp

基于 Vue(2.5) + vuex + vue-router + vue-axios +better-scroll + Scss + ES6 等开发一款移动端音乐 WebApp,UI 界面参考了安卓版的网易云音乐、flex 布局适配常见移动端。 项目演示地址:移...

CaiJinyc
2018/05/16
0
0
一个设计良好的本地图片/视频文件选择库,支持不同的图片加载方式

一个设计良好的本地图片/视频文件选择库,支持不同的图片加载方式 安度博客 » 安度博客2017-04-2734 阅读 设计选择图片 描述: 一个设计良好的本地图片/视频文件选择库,支持不同的图片加...

安度博客 » 安度博客
2017/04/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

错误代码0x800700c1 VS2019加载项目就闪退

--win10更新错误代码0x800700c1 解决方法:把防火墙什么的关了。然后重启下电脑。在检查更新,最后我把杀毒。卫士什么的卸载了。 退出360安全卫士,重新运行vs2019,成功!!! Windows软件异...

南风末
24分钟前
0
0
免费的编程中文书籍索引

免费的编程中文书籍索引 免费的编程中文书籍索引,欢迎投稿。 国外程序员在 stackoverflow 推荐的程序员必读书籍,中文版。 stackoverflow 上的程序员应该阅读的非编程类书籍有哪些? 中文版...

TreeZhou0511
38分钟前
2
0
线程池之ThreadPoolExecutor使用

ThreadPoolExecutor提供了四个构造方法: ThreadPoolExecutor构造方法.png 我们以最后一个构造方法(参数最多的那个),对其参数进行解释: public ThreadPoolExecutor(int corePoolSize, /...

天王盖地虎626
56分钟前
5
0
小程序登陆流程

http://www.bubuko.com/infodetail-2592845.html

为何不可1995
今天
1
0
Consul+Spring boot的服务注册和服务注销

一图胜千言 先看一看要做事情,需要在Consul上面实现注册中心的功能,并以2个Spring boot项目分别作为生产者,消费者。 Consul 假设已经完成文章《Consul的开发者模式之Docker版》中的所有的...

亚林瓜子
今天
11
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部