Glide使用簡(jiǎn)介及流程分析

Glide

Glide簡(jiǎn)介

Glide是Google推薦的一套快速高效的圖片加載框架,作者是bumptech,功能強(qiáng)大且使用方便,實(shí)際的android應(yīng)用開發(fā)中,有不少的開發(fā)者在使用它,今天,老衲就帶大家來講解下Glide的使用及實(shí)現(xiàn)的邏輯流程。

Glide的使用

Glide的使用與前一篇的Picasso類似,都是鏈?zhǔn)秸{(diào)用,極其方便。但是,與其他的圖片加載框架不同的是,Glide支持GIF的加載與解碼。這是該框架的一個(gè)亮點(diǎn),以下為常用API

//設(shè)置默認(rèn)和出錯(cuò)時(shí)的圖片  
Glide.with(this).load(url).placeholder(resId).error(resId).into(mImageView)
//普通的圖片加載
Glide.with(this).load(url).into(mImageView);
//可理解為加載動(dòng)態(tài)圖的第一幀的Bitmap,比如Gif
Glide.with(this).load(url).asBitmap().into(imageView);
//GIF加載,URL指向的資源必須是gif,如果是普通圖片則不顯示。
//相反,如果指向正確但沒有執(zhí)行asGif方法,則只是作為普通圖片展示
Glide.with(this).asGif().load(url).into(mImageView);
//縮略圖的加載
Glide.with(yourFragment).load(yourUrl).thumbnail(0.1f).into(yourView)

Glide的核心思想:

第一條是多數(shù)人認(rèn)可的觀點(diǎn),其他則是老衲自己在分析源碼時(shí)對(duì)該框架的一些感悟。如有不對(duì)請(qǐng)指出

  1. 對(duì)象池:
    Glide原理的核心是為bitmap維護(hù)一個(gè)對(duì)象池。對(duì)象池的主要目的是通過減少大對(duì)象內(nèi)存的分配以重用來提高性能。對(duì)象池的概念參見對(duì)象池的使用
  2. 生命周期綁定:
    圖片的加載任務(wù)會(huì)與activity或者Fragment的生命周期綁定,當(dāng)界面執(zhí)行onStop的使用自動(dòng)暫定,而當(dāng)執(zhí)行onStart的時(shí)候又會(huì)自動(dòng)重新開啟,同樣的,動(dòng)態(tài)Gif圖的加載也是如此,以用來節(jié)省電量,同時(shí)Glide會(huì)對(duì)網(wǎng)絡(luò)狀態(tài)做監(jiān)聽,當(dāng)網(wǎng)絡(luò)狀態(tài)發(fā)生改變時(shí),會(huì)重啟失敗的任務(wù),以減少任務(wù)因網(wǎng)絡(luò)連接問題而失敗的概率。
  3. 預(yù)覽圖的使用
    為加快加載速度,提高體驗(yàn),優(yōu)先加載預(yù)覽圖
  4. AbsListView內(nèi)圖片的預(yù)加載:

Glide的代碼流程分析

按照慣例,首先介紹一下業(yè)務(wù)邏輯中需要用到的類。有印象即可

RequestManager

Glide用來管理和開始請(qǐng)求的類,實(shí)現(xiàn)了LifecycleListener接口并重寫了如下方法,可以使用Activity和Fragment的生命周期事件機(jī)制的開啟,停止及重啟請(qǐng)求任務(wù)。

/** 
  * 開始圖片加載請(qǐng)求,一般在Activity或者Fragment的onStart方法內(nèi)執(zhí)行,用來重啟失敗或暫停的任務(wù)。
  */
@Override
public void onStart() {    
  resumeRequests();
}
/** 
  * 暫停圖片加載請(qǐng)求,一般在Activity或Fragment的onStop方法內(nèi)執(zhí)行,用來暫停任務(wù)。
*/
@Override
public void onStop() {    
  pauseRequests();
}
/** 
* 取消正在執(zhí)行的請(qǐng)求,以及釋放已完成請(qǐng)求的資源。
*/
@Override
public void onDestroy() {    
  requestTracker.clearRequests();
}

RequestManagerFragment

沒有視圖的fragment,簡(jiǎn)單的來講,就是在每一個(gè)Activity或者Fragment上又添加了一個(gè)Fragment,該Fragment沒有View,僅僅用來存儲(chǔ)RequestManager并管理Glide請(qǐng)求

RequestManagerRetriever

用來創(chuàng)建并從Activity或者Fragment檢索已存在的RequestManager

Engine

負(fù)責(zé)開始加載任務(wù),以及管理活躍的,已緩存的資源

BitmapPool(bitmap對(duì)象的緩存池)

Bitmap內(nèi)存池,用來復(fù)用對(duì)象

LruBitmapPool//基于LruPoolStrategy策略的BitmapPool
BitmapPoolAdapter//該實(shí)現(xiàn)類拒絕了對(duì)象的復(fù)用,get方法總是返回null

LruPoolStrategy

對(duì)象池內(nèi)對(duì)象的匹配策略,根據(jù)不同的標(biāo)準(zhǔn),有如下三種匹配策略

//校驗(yàn)bitmap內(nèi)存大小和圖片格式,內(nèi)部的實(shí)現(xiàn)基于數(shù)組
1. SizeConfigStrategy
//僅要求bitmap尺寸完全匹配,內(nèi)部的實(shí)現(xiàn)基于HashMap
2. AttributeStrategy
//校驗(yàn)bitmap尺寸和圖片格式,內(nèi)部實(shí)現(xiàn)基于TreeMap
3. SizeStrategy

RequestTracker

用來跟蹤,取消和重啟正在進(jìn)行中,或者已完成,失敗的請(qǐng)求

ConnectivityMonitor

網(wǎng)絡(luò)狀態(tài)改變的監(jiān)聽,本質(zhì)是一個(gè)BroadcastReceiver,值得一提的是,該類也是LifecycleListener的實(shí)現(xiàn)類,所以,也會(huì)有onStart,onStop的方法,而內(nèi)部的邏輯,就是對(duì)網(wǎng)絡(luò)狀態(tài)監(jiān)聽廣播的注冊(cè)于反注冊(cè)

//注冊(cè)網(wǎng)絡(luò)狀態(tài)改變的監(jiān)聽廣播
private void register() {    
  if (isRegistered) {        
    return;    
  }    
  isConnected = isConnected(context);
  context.registerReceiver(connectivityReceiver, 
   
    new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));    
  isRegistered = true;
}
//取消注冊(cè)
private void unregister() {    
  if (!isRegistered) {        
  return;    
  }    
  context.unregisterReceiver(connectivityReceiver);    
  isRegistered = false;
}
//是否有網(wǎng)絡(luò)鏈接
private boolean isConnected(Context context) {    
  ConnectivityManager connectivityManager = (ConnectivityManager)  
  context.getSystemService(Context.CONNECTIVITY_SERVICE);    
  NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
  return networkInfo != null && networkInfo.isConnected();
}
@Override
public void onStart() {    
  register();
}
@Override
public void onStop() {    
  unregister();
}

Resource

支持復(fù)用及池存儲(chǔ)功能的特殊類型的接口,以下是其部分實(shí)現(xiàn)類

EngineResource //支持引用計(jì)數(shù)的Resource
BitmapResource //Bitmap的包裝類
DrawableResource //抽象類,根據(jù)ConstantState返回一個(gè)依賴于自身ConstantState的drawable的副本
  BitmapDrawableResource //BitmapDrawable的包裝類
  GifDrawableResource //GifDrawable的包裝類

Target:

LifecycleListener接口的子類,Glide用來加載資源并在加載時(shí)通知相關(guān)聲明周期事件的接口。ViewTarget是它的抽象實(shí)現(xiàn)類。典型的生命周期是onLoadStarted -> onResourceReady or onLoadFailed -> onLoadCleared,然而并不保證一定按照此順序執(zhí)行,比如:如果資源已經(jīng)在內(nèi)存中,則onLoadStarted就不會(huì)被調(diào)用,同樣的,如果Target如果永遠(yuǎn)不被清除,則onLoadCleared永遠(yuǎn)不會(huì)被調(diào)用。

//加載開始時(shí)調(diào)用
void onLoadStarted(@Nullable Drawable placeholder);
//加載失敗是調(diào)用
void onLoadFailed(@Nullable Drawable errorDrawable);
//加載結(jié)束時(shí)調(diào)用
void onResourceReady(R resource, Transition<? super R> transition);
//加載任務(wù)取消并且資源被釋放時(shí)調(diào)用
void onLoadCleared(@Nullable Drawable placeholder);
//取回目標(biāo)大小,Callback的實(shí)現(xiàn)類為SizeDeterminer,在ViewTarget.class中
void getSize(SizeReadyCallback cb);

ViewTarget

加載資源的基類。Target的部分實(shí)現(xiàn)類。根據(jù)參數(shù)的類型,有不同的實(shí)現(xiàn)方法,并能通過ViewTreeObserver.OnDrawListener來決定View的大小。
在需要檢測(cè)任意涉及到復(fù)用View的ViewGroup時(shí)(比如listview),該類用setTag方法來存儲(chǔ)一些標(biāo)志,當(dāng)檢測(cè)到復(fù)用時(shí),之前的加載任務(wù)和對(duì)應(yīng)的資源文件會(huì)被取消或復(fù)用。

ImageViewTarget:在ImageView中展示圖片的基類,有如下兩個(gè)子類
  DrawableImageViewTarget:當(dāng)參數(shù)是drawable的使用使用
    //核心方法
    @Override
    protected void setResource(Bitmap resource) { 
       view.setImageBitmap(resource);
    }
  BitmapImageViewTarget:當(dāng)參數(shù)是bitmap 的時(shí)候使用
    //核心方法
    @Override
    protected void setResource(Bitmap resource) { 
       view.setImageBitmap(resource);
    }

LifecycleListener

Fragment和Activity生命周期方法的監(jiān)聽類,主要用來監(jiān)聽onStart,onStop,onDestroy三個(gè)方法。實(shí)現(xiàn)類如下RequestTracker

RequestManager:負(fù)責(zé)監(jiān)聽Fragment和Activity中對(duì)應(yīng)的方法
      @Override
      public void onStart() {  
        resumeRequests();  //重啟暫?;蛘呤〉娜蝿?wù)
        targetTracker.onStart();
      }
      @Override
      public void onStop() {  
        pauseRequests();  //暫停正在執(zhí)行的任務(wù)
        targetTracker.onStop();
      }
DefaultConnectivityMonitor:負(fù)責(zé)網(wǎng)絡(luò)狀態(tài)監(jiān)聽廣播的注冊(cè)于反注冊(cè)
      @Override
      public void onStart() {  
        register();
      }
      @Override
      public void onStop() {  
        unregister();
      }
BaseTarget:空實(shí)現(xiàn),真正的實(shí)現(xiàn)者是其子類ImageViewTarget,用來開始與暫停動(dòng)畫
      @Override
      public void onStart() {  
        if (animatable != null) {    
          animatable.start();  
        }
      }
      @Override
      public void onStop() {  
        if (animatable != null) {    
          animatable.stop();  
        }
      }
TargetTracker:該類調(diào)用的,其實(shí)是Target類中對(duì)應(yīng)的方法
      @Override
      public void onStart() {  
        for (Target<?> target : Util.getSnapshot(targets)) {    
          target.onStart();  
        }
      }
      @Override
      public void onStop() {  
        for (Target<?> target : Util.getSnapshot(targets)) {   
         target.onStop();  
        }
      }

RequestFutureTarget:空實(shí)現(xiàn),忽略
NullConnectivityMonitor:空實(shí)現(xiàn),忽略

DataFetcher

數(shù)據(jù)提取的抽象接口,根據(jù)資源的來源有不同的實(shí)現(xiàn),例如

HttpUrlFetcher //加載網(wǎng)絡(luò)圖片數(shù)據(jù)
AssetPathFetcher //加載Asset圖片數(shù)據(jù)
LocalUriFetcher //加載本地圖片數(shù)據(jù)
ThumbFetcher //加載MediaStore中的縮略圖數(shù)據(jù)

DiskCacheStrategy

緩存策略的抽象類,只有Glide提供的固定的幾個(gè)對(duì)象,分別對(duì)應(yīng)不同的策略

ALL:遠(yuǎn)程數(shù)據(jù)同時(shí)緩存Data和Resource,本地?cái)?shù)據(jù)僅緩存Resource
NONE:不緩存任何數(shù)據(jù)
DATA:解碼之前直接將數(shù)據(jù)寫入硬盤
RESOURCE:解碼之后寫入硬盤
AUTOMATIC:默認(rèn)策略,根據(jù)DataFetcher以及ResourceEncoder的編碼策略(EncodeStrategy)智能選擇

Glide圖片加載的業(yè)務(wù)流程

1. Glide的初始化

public static Glide get(Context context) {  
  if (glide == null) {    
    synchronized (Glide.class) {      
    if (glide == null) {        
      Context applicationContext = context.getApplicationContext(); 
      //查找manifest文件中注冊(cè)的懶加載的配置信息,下面會(huì)介紹到   
      List<GlideModule> modules = new ManifestParser(applicationContext).parse();        
      GlideBuilder builder = new GlideBuilder(applicationContext);
      for (GlideModule module : modules) {
        module.applyOptions(applicationContext, builder);        
       }        
      //創(chuàng)建Glide實(shí)例
      glide = builder.createGlide();        
      for (GlideModule module : modules) {   
        module.registerComponents(applicationContext, glide.registry);      
       }      
      }    
    }  
  }  
  return glide;
 }
創(chuàng)建Glide實(shí)例對(duì)象
Glide createGlide() {  
//依賴于優(yōu)先級(jí)的線程池,用來執(zhí)行Glide的加載,解碼和轉(zhuǎn)換任務(wù)(當(dāng)從緩存中沒有找到對(duì)應(yīng)的對(duì)象時(shí))
//線程數(shù)量取決于當(dāng)前喚醒的CPU核數(shù),而不是CPU的總數(shù)
if (sourceExecutor == null) {    
  sourceExecutor = GlideExecutor.newSourceExecutor();  
} 
//依賴于優(yōu)先級(jí)的線程池,用來執(zhí)行Glide的加載,解碼和轉(zhuǎn)換任務(wù)(當(dāng)從緩存中有對(duì)應(yīng)的對(duì)象時(shí))
//線程數(shù)量為1
if (diskCacheExecutor == null) {    
   diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();  
}
//內(nèi)存大小的計(jì)算器,計(jì)算結(jié)果取決于一些常量和當(dāng)前設(shè)備的信息(寬,高,像素密度),最后會(huì)與介紹   
if (memorySizeCalculator == null) {    
    memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();  
}  
//網(wǎng)絡(luò)活動(dòng)監(jiān)視器,用來檢測(cè)網(wǎng)絡(luò)狀態(tài)
if (connectivityMonitorFactory == null) {    
  connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();  
}  
//bitmap對(duì)象池,用來存儲(chǔ)bitmap對(duì)象
if (bitmapPool == null) {    
  //3.0以上使用基于LRU算法的bitmap對(duì)象池
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 
     int size = memorySizeCalculator.getBitmapPoolSize();   
     bitmapPool = new LruBitmapPool(size);    
  } else {      
    bitmapPool = new BitmapPoolAdapter();    
  }  
}  
//基于LRU算法的數(shù)組緩存池
if (arrayPool == null) {    
  arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes()); 
}    
if (memoryCache == null) {    
  memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());  
} 
//硬盤緩存 
if (diskCacheFactory == null) {    
  diskCacheFactory = new InternalCacheDiskCacheFactory(context);  
}  
//負(fù)責(zé)開啟加載任務(wù)以及管理活躍的或者緩存的圖片資源
if (engine == null) {    
  engine = new Engine(memoryCache, diskCacheFactory, diskCacheExecutor, sourceExecutor);  
}  
return new Glide(context,      
                 engine,      
                 memoryCache,      
                 bitmapPool,      
                 arrayPool,      
                 connectivityMonitorFactory,      
                 logLevel,      
                 defaultRequestOptions.lock());
}

Glide真正的構(gòu)造方法

Glide(Context context,    
        Engine engine,    
        MemoryCache memoryCache,    
        BitmapPool bitmapPool,    
        ArrayPool arrayPool,    
        ConnectivityMonitorFactory connectivityMonitorFactory,    
        int logLevel,    
        RequestOptions defaultRequestOptions) {  
    this.engine = engine;  
    this.bitmapPool = bitmapPool;  
    this.arrayPool = arrayPool;  
    this.memoryCache = memoryCache;  
    this.connectivityMonitorFactory = connectivityMonitorFactory;  
    //圖片的加載格式(ARGB_8888或RGB_565),默認(rèn)ARGB_8888,判斷規(guī)則如下
    //如果支持透明或者使用了透明則使用ARGB_8888
    //如果不支持透明則使用ARGB_565
    DecodeFormat decodeFormat = defaultRequestOptions.getOptions().get(Downsampler.DECODE_FORMAT);  
    //BitmapPool的預(yù)填充器,最后面有介紹
    bitmapPreFiller = new BitmapPreFiller(memoryCache, bitmapPool, decodeFormat);  
    Resources resources = context.getResources(); 
    //從給定的inputstream中解碼圖片
    Downsampler downsampler = new Downsampler(resources.getDisplayMetrics(), bitmapPool, arrayPool); 
    //Gif圖片資源的解碼器
    ByteBufferGifDecoder byteBufferGifDecoder = new ByteBufferGifDecoder(context, bitmapPool, arrayPool);  
    ...
    //包含了加載圖片資源所需要的類,比如Registry,Engine,等等
    glideContext = new GlideContext(context, registry, imageViewTargetFactory, defaultRequestOptions, engine, this, logLevel);
  }

2. 創(chuàng)建請(qǐng)求管理器RequestManager

首先根據(jù)context的類型,拿到FragmentManager

public RequestManager get(Context context) {  
  if (context == null) {    
    ... 
  } else if (Util.isOnMainThread() && !(context instanceof Application)) {    
    if (context instanceof FragmentActivity) {    
      //FragmentActivity
      return get((FragmentActivity) context);    
    } else if (context instanceof Activity) {   
      //Activity   
      return get((Activity) context);    
    } else if (context instanceof ContextWrapper) {     
      //如果不屬于以上兩種,則遞歸查找父類Context 
      return get(((ContextWrapper) context).getBaseContext());    
    } 
  }  
  //返回ApplicationManager
  return getApplicationManager(context);
}

...//get()中會(huì)調(diào)用fragmentGet方法

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
RequestManager fragmentGet(Context context, 
        android.app.FragmentManager fm, android.app.Fragment parentHint) {  
  //獲取RequestManagerFragment
  RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);  
  //獲取RequestManagerFragment對(duì)應(yīng)的RequestManager(請(qǐng)求管理器)
  RequestManager requestManager = current.getRequestManager();  
  if (requestManager == null) {    
    requestManager = new RequestManager(context, current.getLifecycle(), 
          current.getRequestManagerTreeNode());   
    current.setRequestManager(requestManager);  
    }  
    return requestManager;
}

RequestManagerRetriever中使用兩個(gè)map用來分別存放RequestManagerFragment和SupportRequestManagerFragment,Key都是FragmentManager。說白了就是每一個(gè)Activity或者FragmentActivity都有一個(gè)唯一的FragmentManager,通過這個(gè)FragmentManager作為key就可以找到該Activity對(duì)應(yīng)的RequestManagerFragment,Glide就是通過這個(gè)Fragment用來實(shí)現(xiàn)在生命周期中圖片加載的控制,比如Paused狀態(tài)在暫停加載,在Resumed的時(shí)候又自動(dòng)重新加載

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
RequestManagerFragment getRequestManagerFragment(final android.app.FragmentManager fm
      , android.app.Fragment parentHint) {  
RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);  
if (current == null) {   
  //pendingRequestManagerFragments是一個(gè) 
   current = pendingRequestManagerFragments.get(fm);    
  if (current == null) {      
    current = new RequestManagerFragment(); 
    current.setParentFragmentHint(parentHint);  
    pendingRequestManagerFragments.put(fm, current);   
    fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
    handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();    
    }  
  }  
  return current;
}

2. 填充資源路徑(網(wǎng)絡(luò)路徑或本地路徑)

/** 
  * 利用默認(rèn)的請(qǐng)求參數(shù)創(chuàng)建RequestBuilder
  * 默認(rèn)的參數(shù)包括,是否啟動(dòng)硬盤緩存,優(yōu)先級(jí),錯(cuò)誤及加載中的默認(rèn)圖片等
  * 詳情請(qǐng)看BaseRequestOptions.java類
  * @return A new request builder for loading a { Drawable} using the given model. 
  */
public RequestBuilder<Drawable> load(@Nullable Object model) { 
 return asDrawable().load(model);
}

3. 設(shè)置要加載圖片的Target(ImageView)

public Target<TranscodeType> into(ImageView view) {  
  Util.assertMainThread();  
  Preconditions.checkNotNull(view);  
  if (!requestOptions.isTransformationSet()
        && requestOptions.isTransformationAllowed()  
        && view.getScaleType() != null) {    
    ...
  return into(context.buildImageViewTarget(view, transcodeClass));
}


public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {  
  Util.assertMainThread();  
  Preconditions.checkNotNull(target);  
  if (!isModelSet) {   //是否設(shè)置了url 
    ...
  }  
  //獲取該Target之前的請(qǐng)求任務(wù)(如果有的話)
  Request previous = target.getRequest();  
  //如果有,取消該Target之前所有的任務(wù)并釋放資源(例如bitmap)以備復(fù)用
  if (previous != null) {    
    requestManager.clear(target);  
  }  
  requestOptions.lock(); 
  //創(chuàng)建請(qǐng)求 
  Request request = buildRequest(target);  
  target.setRequest(request);  
  // TODO:下載圖片的任務(wù)
  requestManager.track(target, request);  
  return target;
}

Glide的縮略圖加載思想,為了更快的展示圖片,一般來說,一張圖片的加載任務(wù)分為全尺寸圖片和縮略圖兩部分,因?yàn)榭s略圖更小,所以一般來說相對(duì)于全尺寸圖片會(huì)加載更快,但不絕對(duì),如果縮略圖先加載完則先展示縮略圖,然后等全尺寸圖片加載完成后再加載全尺寸圖片,但是,如果全尺寸圖片先于縮略圖下載完成,則縮略圖則不會(huì)展示。

private Request buildRequestRecursive(Target<TranscodeType> target, 
    @Nullable ThumbnailRequestCoordinator parentCoordinator,  
    TransitionOptions<?, ? super TranscodeType> transitionOptions,  
    Priority priority, 
    int overrideWidth, 
    int overrideHeight) {  
      //默認(rèn)的該thumbnailBuilder對(duì)象為null,除非手動(dòng)調(diào)用RequestBuilder類的thumbnail方法,
      //否則該if代碼永遠(yuǎn)不會(huì)執(zhí)行
      if (thumbnailBuilder != null) {    
        // Recursive case: contains a potentially recursive thumbnail request builder.    
        if (isThumbnailBuilt) {      
          throw new IllegalStateException("You cannot use a request as both the main request and a thumbnail, consider using clone() on the request(s) passed to thumbnail()");    
        }    
        TransitionOptions<?, ? super TranscodeType> thumbTransitionOptions = thumbnailBuilder.transitionOptions;    
        if (DEFAULT_ANIMATION_OPTIONS.equals(thumbTransitionOptions)) { 
         thumbTransitionOptions = transitionOptions;    
        }    
        //縮略圖權(quán)限
        Priority thumbPriority = thumbnailBuilder.requestOptions.isPrioritySet() ? thumbnailBuilder.requestOptions.getPriority() : getThumbnailPriority(priority);    
        //縮略圖寬高
        int thumbOverrideWidth = thumbnailBuilder.requestOptions.getOverrideWidth();    
        int thumbOverrideHeight = thumbnailBuilder.requestOptions.getOverrideHeight();   
        //寬高校驗(yàn) 
        if (Util.isValidDimensions(overrideWidth, overrideHeight) && !thumbnailBuilder.requestOptions.isValidOverride()) {   
         thumbOverrideWidth = requestOptions.getOverrideWidth();      
         thumbOverrideHeight = requestOptions.getOverrideHeight();    
        }    
        //縮略圖請(qǐng)求協(xié)調(diào)器,用來同時(shí)協(xié)調(diào)縮略圖和原始圖片的請(qǐng)求
        ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);    
        //原始圖片請(qǐng)求
        Request fullRequest = obtainRequest(target, requestOptions, coordinator, transitionOptions, priority, overrideWidth, overrideHeight);   
        isThumbnailBuilt = true;    
        // Recursively generate thumbnail requests
        //遞歸生成縮略圖請(qǐng)求  
        Request thumbRequest = thumbnailBuilder.buildRequestRecursive(target, coordinator,        thumbTransitionOptions, thumbPriority, thumbOverrideWidth, thumbOverrideHeight);    
        isThumbnailBuilt = false;    
        coordinator.setRequests(fullRequest, thumbRequest);    
        return coordinator;  
  } else if (thumbSizeMultiplier != null) { //根據(jù)指定縮放系數(shù)加載縮略圖   
        ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);    
        Request fullRequest = obtainRequest(target, requestOptions, coordinator, transitionOptions, priority, overrideWidth, overrideHeight);
        BaseRequestOptions<?> thumbnailOptions = requestOptions.clone().sizeMultiplier(thumbSizeMultiplier);    
        Request thumbnailRequest = obtainRequest(target, thumbnailOptions, coordinator,  transitionOptions, getThumbnailPriority(priority), overrideWidth, overrideHeight);    coordinator.setRequests(fullRequest, thumbnailRequest);    
        return coordinator;  
  } else {    
      // 只加載原始圖片 
      return obtainRequest(target, requestOptions, parentCoordinator, transitionOptions, priority, overrideWidth, overrideHeight);  
  }
}

至此,Glide的流程就分析完了,但是?。?!和我們之前了解到的Picasso或者Imageloader等圖片加載框架不同,我們完全沒有看到他的網(wǎng)絡(luò)請(qǐng)求已經(jīng)緩存查找的業(yè)務(wù)邏輯,那這段邏輯究竟在哪呢。我們繼續(xù)往下看

圖片的加載

在上面的最后一步,buildRequestRecursive方法,不管是否有縮略圖,我們都會(huì)返回一個(gè)Request,這個(gè)Request會(huì)和Target一起被RequestManager接收

void track(Target<?> target, Request request) {  
    targetTracker.track(target);  
    //runRequest最終執(zhí)行的是Request的begin方法,下面以SingleRequest為例講解下流程
    requestTracker.runRequest(request);
}


@Override
public void begin() {  
  stateVerifier.throwIfRecycled();  
  startTime = LogTime.getLogTime();  
  //如果圖片的來源沒有設(shè)置,則加載失敗,來源是指網(wǎng)絡(luò),本地硬盤或者資源文件等。
  if (model == null) {    
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {      
      width = overrideWidth;      
      height = overrideHeight;    
    }    
    ...   
    onLoadFailed(new GlideException("Received null model"), logLevel);    
    return;  
}  
status = Status.WAITING_FOR_SIZE;  
//如果Target的寬高已經(jīng)獲取并且合法,則開始進(jìn)行下一步
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {    
  onSizeReady(overrideWidth, overrideHeight);  
} else {    //手動(dòng)獲取Target的寬高
  target.getSize(this);  
}  
if ((status == Status.RUNNING 
  || status == Status.WAITING_FOR_SIZE) 
  && canNotifyStatusChanged()) {    
    target.onLoadStarted(getPlaceholderDrawable());  
  }  
if (Log.isLoggable(TAG, Log.VERBOSE)) {    
  ...
  }
}

@Override
public void onSizeReady(int width, int height) {  
  stateVerifier.throwIfRecycled();  
  ...
  status = Status.RUNNING;  
  //計(jì)算縮略圖的尺寸
  float sizeMultiplier = requestOptions.getSizeMultiplier();  
  this.width = Math.round(sizeMultiplier * width);  
  this.height = Math.round(sizeMultiplier * height);  
  ...
  //加載任務(wù)
  loadStatus = engine.load(glideContext,      
        model,      
        requestOptions.getSignature(),      
        this.width,      
        this.height,      
        requestOptions.getResourceClass(),      
        transcodeClass,      
        priority,      
        requestOptions.getDiskCacheStrategy(),   
        requestOptions.getTransformations(),   
        requestOptions.isTransformationRequired(),    
        requestOptions.getOptions(),     
        requestOptions.isMemoryCacheable(),      
        this);  
      ...
}
加載資源

Glide圖片的資源加載與其他圖片加載框架的加載邏輯類似,都是按照內(nèi)存,硬盤及網(wǎng)絡(luò)的順序來加載圖片,但是Glide得加載又稍顯不同,他是的邏輯如下:

/** 
 *  1. 檢查內(nèi)存緩存
 *  2. 檢查最近的活躍資源
 *  3. 檢查最近的加載任務(wù)
 *  活躍資源指的是那些不止一次被加載并沒有進(jìn)行過資源釋放的圖片,一旦被釋放,
 *  那么該資源則會(huì)從近期活躍資源中刪除并進(jìn)入到內(nèi)存緩存中,
 *  但是如果該資源再次從內(nèi)存緩存中讀取,則會(huì)重新添加到活躍資源中
 */
public <R> LoadStatus load( 
    GlideContext glideContext, 
    Object model,    
    Key signature,
    int width,
    int height,
    Class<?> resourceClass,
    Class<R> transcodeClass,
    Priority priority,
    DiskCacheStrategy diskCacheStrategy,    
    Map<Class<?>, Transformation<?>> transformations,    
    boolean isTransformationRequired,    
    Options options,    
    boolean isMemoryCacheable,    
    ResourceCallback cb) {  

Util.assertMainThread();  
long startTime = LogTime.getLogTime();  
//內(nèi)存緩存的唯一鍵值
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options); 
//首先從緩存中查找
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);  
if (cached != null) {    
   cb.onResourceReady(cached, DataSource.MEMORY_CACHE);    
    ...  
   return null;  
}  
//如果緩存中沒有找到,則去活躍資源中加載
//memCache中該bitmap則會(huì)被remove掉bitmap并進(jìn)入activeResource中
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);  
if (active != null) {    
 cb.onResourceReady(active, DataSource.MEMORY_CACHE);    
 ...   
}    
 return null;  
}  
//如果該任務(wù)之前已經(jīng)在隊(duì)列中,則添加新的callback,然后返回
EngineJob current = jobs.get(key);  
if (current != null) {    
  current.addCallback(cb);    
  ...   
return new LoadStatus(cb, current);  
}  
//如果是新的加載任務(wù),先創(chuàng)建EngineJob和DecodeJob,然后開始任務(wù)
EngineJob<R> engineJob = engineJobFactory.build(key, isMemoryCacheable);  
DecodeJob<R> decodeJob = decodeJobFactory.build(glideContext,model,key,signature,width,height,resourceClass,transcodeClass,      priority,diskCacheStrategy,transformations,      isTransformationRequired,options,engineJob); 
jobs.put(key, engineJob);  
engineJob.addCallback(cb);  
//開始任務(wù)
engineJob.start(decodeJob);  
...
return new LoadStatus(cb, engineJob);
}


/** 
 *   DecodeJob 類中run方法的實(shí)現(xiàn),DecodeJob是一個(gè)Runnable的實(shí)現(xiàn)類
 *   該方法的作用是,
 *   1.確定數(shù)據(jù)的加載來源(Resource,Data,Source)
 *   2.創(chuàng)建對(duì)應(yīng)來源的DataFetcherGenerator
 *   3.執(zhí)行DataFetcherGenerator
 */
private void runWrapped() {   
  switch (runReason) {    
    case INITIALIZE://首次提交
      stage = getNextStage(Stage.INITIALIZE);  //確定資源的加載來源    
      currentGenerator = getNextGenerator();      
      runGenerators();      
      break;    
    case SWITCH_TO_SOURCE_SERVICE://從硬盤獲取資源失敗 ,嘗試重新獲取
      runGenerators();      
      break;    
    case DECODE_DATA://獲取數(shù)據(jù)成功,但不在同一線程  
      decodeFromRetrievedData();      
      break;    
    default:      
      throw new IllegalStateException("Unrecognized run reason: " + runReason);  
    }
  }

/** 
 *   根據(jù)當(dāng)前階段獲取下一階段
 *   Data和Resource的區(qū)別:
 *   Data:原始的圖片(或gif)數(shù)據(jù)
 *   Resource:經(jīng)過處理(旋轉(zhuǎn),縮放)后的數(shù)據(jù)
 *   該方法的大致邏輯如下
 *   1.如果是初始狀態(tài),則判斷是否解碼已緩存的Resource,true是解碼Resource。
 *     false的話則會(huì)通過遞歸進(jìn)入第二個(gè)判斷分支
 *   2.判斷是否解碼已緩存的Data,true是解碼Data
 *     false的話則會(huì)通過遞歸進(jìn)入第三個(gè)判斷分支
 *   3.該階段則需要從數(shù)據(jù)源去解碼。
 *   簡(jiǎn)單的來說,就是根據(jù)Resource--->Data--->source的順序去解碼加載數(shù)據(jù)
 *   該階段Stage的確定,影響著下一階段DataFetcherGenerator相應(yīng)子類的實(shí)例創(chuàng)建
 */
private Stage getNextStage(Stage current) {  
  switch (current) {    
    case INITIALIZE: 
    return diskCacheStrategy.decodeCachedResource() ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);   
   case RESOURCE_CACHE:
    return diskCacheStrategy.decodeCachedData() ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);    
  case DATA_CACHE:  
    return Stage.SOURCE;    
  case SOURCE:    
  case FINISHED:      
    return Stage.FINISHED;    
  default:      
    throw new IllegalArgumentException("Unrecognized stage: " + current);  
  }
}

/** 
 *   根據(jù)不同的階段創(chuàng)建不同的DataFetcherGenerator,該類使用已注冊(cè)的ModelLoaders和Model
 *   來生成一系列的DataFetcher。有如下實(shí)現(xiàn)類
 *   DataFetcherGenerator:經(jīng)過處理的資源數(shù)據(jù)緩存文件(采樣轉(zhuǎn)換等處理)
 *   ResourceCacheGenerator:未經(jīng)處理的資源數(shù)據(jù)緩存文件
 *   SourceGenerator:源數(shù)據(jù)的生成器,包含了根據(jù)來源創(chuàng)建的ModelLoader和Model(文件路徑,URL...)
 */
private DataFetcherGenerator getNextGenerator() {  
  switch (stage) {    
    case RESOURCE_CACHE:      
      return new ResourceCacheGenerator(decodeHelper, this);    
    case DATA_CACHE:      
      return new DataCacheGenerator(decodeHelper, this);    
    case SOURCE:      
      return new SourceGenerator(decodeHelper, this);    
    case FINISHED:      
      return null;    
    default:      
      throw new IllegalStateException("Unrecognized stage: " + stage); 
    }
}

/** 
 *   根據(jù)不同的狀態(tài)來選擇并執(zhí)行生成器
 *  從當(dāng)前Generator 獲取數(shù)據(jù),如果獲取成功則直接回調(diào)onDataFetcherReady,
 *  如果失敗則通過reschedule重新調(diào)度
 */
private void runGenerators() {  
  ... 
  boolean isStarted = false;  
  while (!isCancelled && currentGenerator != null 
          && !(isStarted = currentGenerator.startNext())) {    
    stage = getNextStage(stage);    
    currentGenerator = getNextGenerator();    
    if (stage == Stage.SOURCE) {      
      reschedule();      
      return;    
    }  
  }  
  // 加載失敗 
  if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
    notifyFailed();  
  }  
}

如果是首次加載一張圖片資源,最終會(huì)來到SourceGenerator的startNext來執(zhí)行。

/** 
 *  SourceGenerator
 *  DataFetcher的簡(jiǎn)介:Fetcher的意思是抓取,所以該類可以稱為數(shù)據(jù)抓取器
 *      作用就是根據(jù)不同的數(shù)據(jù)來源(本地,網(wǎng)絡(luò),Asset等) 
 *      以及讀取方式(Stream,ByteBuffer等)來提取并解碼數(shù)據(jù)資源,實(shí)現(xiàn)類如下
 *  AssetPathFetcher:加載Asset數(shù)據(jù)
 *  HttpUrlFetcher:加載網(wǎng)絡(luò)數(shù)據(jù)
 *  LocalUriFetcher:加載本地?cái)?shù)據(jù)
 *  其他實(shí)現(xiàn)類...
 * 
 */
@Override
public boolean startNext() {  
  ...
  if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {    
    return true;  
  }  
sourceCacheGenerator = null;  
loadData = null;  
boolean started = false;  
//是否有更多的ModelLoader
while (!started && hasNextModelLoader()) {    
  loadData = helper.getLoadData().get(loadDataListIndex++);    
  if (loadData != null&& (helper.getDiskCacheStrategy()
    .isDataCacheable(loadData.fetcher.getDataSource())
    || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {      
      started = true;      
      //選擇合適的LoadData,并使用LoadData中的fetcher來抓取數(shù)據(jù)
      loadData.fetcher.loadData(helper.getPriority(), this);    
      }  
   }  
  return started;
}

當(dāng)走完上面的流程,接下來就是我們最熟悉的網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求的模塊了,因?yàn)樵撃K在整個(gè)的圖片加載流程中并不是很重要,所以就簡(jiǎn)單介紹一下,不過有幾個(gè)知識(shí)點(diǎn)還是比較有意思的。

/** 
 *  HttpUrlFetcher
 *  HttpUrlFetcher的簡(jiǎn)介:網(wǎng)絡(luò)數(shù)據(jù)抓取器,通俗的來講就是去服務(wù)器上下載圖片,支持地址重定向(最多5次)
 * 
 */
@Override
public void loadData(Priority priority, DataCallback<? super InputStream> callback) {  
  long startTime = LogTime.getLogTime();  
  final InputStream result;  
  try {    
    result = loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());  
    } catch (IOException e) {    
    ...  
    callback.onLoadFailed(e);    
    return;  
    }  
  ...  
  callback.onDataReady(result);
}


private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
   Map<String, String> headers) throws IOException { 
  //重定向次數(shù)過多
  if (redirects >= MAXIMUM_REDIRECTS) {    
    throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");  
   } else {     
    //通過URL的equals方法來比較會(huì)導(dǎo)致NetworkI/O開銷,一般會(huì)有問題,
    //有興趣的同學(xué)可以看下下面的鏈接或者直接閱讀URL里equals方法的源碼注釋,一目了然
    //http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.   
   try {      
    if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {        
        throw new HttpException("In re-direct loop");      
      }    
     } catch (URISyntaxException e) {      
      // Do nothing, this is best effort.    
     }  
  }  
  //HttpUrlConnection下載圖片
  ...
}

Glide的知識(shí)點(diǎn)

接下來,老衲帶大家看下Glide中有什么我們?cè)谌粘i_發(fā)的時(shí)候能用得上的技術(shù)

1.線程池內(nèi)線程的個(gè)數(shù)的計(jì)算方式

/** 
 * 根據(jù)/sys/devices/system/cpu/下的文件來決定線程池內(nèi)線程的數(shù)量
 * 決定線程數(shù)量的不是一共得CPU核數(shù),而是喚醒的CPU核數(shù)
 * 
 * See http://goo.gl/8H670N. 
 */
public static int calculateBestThreadCount() {  
  File[] cpus = null;  
  try {    
    File cpuInfo = new File(CPU_LOCATION);    
    final Pattern cpuNamePattern = Pattern.compile(CPU_NAME_REGEX);    
      cpus = cpuInfo.listFiles(new FilenameFilter() {      
        @Override      
        public boolean accept(File file, String s) {        
          return cpuNamePattern.matcher(s).matches();      
          }    
      });  
  } catch (Throwable t) {    
    ... 
  }  
  int cpuCount = cpus != null ? cpus.length : 0;  
  int availableProcessors = Math.max(1, Runtime.getRuntime().availableProcessors());  
  return Math.min(MAXIMUM_AUTOMATIC_THREAD_COUNT, Math.max(availableProcessors, cpuCount));
}

2. 權(quán)限判斷

以網(wǎng)絡(luò)請(qǐng)求的權(quán)限為例

final int res = context
        .checkCallingOrSelfPermission("android.permission.ACCESS_NETWORK_STATE");
final boolean hasPermission = res == PackageManager.PERMISSION_GRANTED;

Glide的懶加載配置

解析Manifest文件中注冊(cè)的懶加載配置信息
public List<GlideModule> parse() {  
  List<GlideModule> modules = new ArrayList<>();  
  try {    
    ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);    
    if (appInfo.metaData != null) {      
      for (String key : appInfo.metaData.keySet()) {        
      if (GLIDE_MODULE_VALUE.equals(appInfo.metaData.get(key))) {
        modules.add(parseModule(key));        
        }      
      }    
    }  
  } catch (PackageManager.NameNotFoundException e) {    
    ...
  }  
  return modules;
}
3. Glide給出的懶加載示例(混淆代碼的話請(qǐng)讀者自己查看GlideModule類的注釋)
public class FlickrGlideModule implements GlideModule {
    Override
   public void applyOptions(Context context, GlideBuilder builder) {
     builder.setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888);
    }
    public void registerComponents(Context context, Glide glide) {
      glide.register(Model.class, Data.class, new MyModelLoader());
    }
  }

4. Glide的預(yù)填充機(jī)制

Glide最核心的部分,就是Bitmap池的使用,
Glide類中有一個(gè)preFillBitmapPool方法,用來預(yù)填充bitmap對(duì)象池,他的注釋如下

 /**
   * 作用:根據(jù)給定的尺寸預(yù)填充Bitmap對(duì)象池
   * 缺陷:太多的資源釋放會(huì)導(dǎo)致GC頻繁執(zhí)行,這樣就失去了Glide本身存在的意義。此方法要慎重使用。
   * 若太多的Bitmap被添加到對(duì)象池使其完全被填滿,會(huì)導(dǎo)致大多數(shù)甚至全部最近被添加的bitmap被驅(qū)逐(釋放)
   * bitmap會(huì)根據(jù)給定的尺寸的權(quán)重來分配,每一種尺寸只會(huì)填充對(duì)象池所占內(nèi)存的一定比例。
   * 比例的計(jì)算公式為weight / prefillWeightSum
   */
public void preFillBitmapPool(PreFillType.Builder... bitmapAttributeBuilders) {          
  bitmapPreFiller.preFill(bitmapAttributeBuilders);
}

public void preFill(PreFillType.Builder... bitmapAttributeBuilders) {    
 if (current != null) {        
    current.cancel();    
 }    
//PreFillType中保存著圖片的寬高,Config以及weight等信息
PreFillType[] bitmapAttributes = new PreFillType[bitmapAttributeBuilders.length];    
for (int i = 0; i < bitmapAttributeBuilders.length; i++) {    
  PreFillType.Builder builder = bitmapAttributeBuilders[i];       
   if (builder.getConfig() == null) {            
    builder.setConfig(defaultFormat == DecodeFormat.ALWAYS_ARGB_8888   
    || defaultFormat == DecodeFormat.PREFER_ARGB_8888? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);        
  }        
  bitmapAttributes[i] = builder.build();    
}    
  //根據(jù)PreFillType中保存的圖片信息創(chuàng)建預(yù)填充隊(duì)列
  PreFillQueue allocationOrder = generateAllocationOrder(bitmapAttributes);    
  //創(chuàng)建用來填充對(duì)象池的線程對(duì)象,
  //該類通過Handler發(fā)布到主線程中盡量避免GC因高比例的Bitmap觸發(fā)垃圾回收所導(dǎo)致的ANR,
  //通過延時(shí)減少GC線程的垃圾回收的次數(shù)
  current = new BitmapPreFillRunner(bitmapPool, memoryCache, allocationOrder);    
  handler.post(current);
}

// Visible for testing.
PreFillQueue generateAllocationOrder(PreFillType[] preFillSizes) {    
//剩余內(nèi)存
 final int maxSize = memoryCache.getMaxSize() - memoryCache.getCurrentSize() + bitmapPool.getMaxSize();    
 int totalWeight = 0;    
//計(jì)算圖片總權(quán)重
 for (PreFillType size : preFillSizes) {        
   totalWeight += size.getWeight();    
 }    
  //計(jì)算每一份權(quán)重占用的字節(jié)
 final float bytesPerWeight = maxSize / (float) totalWeight;   
 Map<PreFillType, Integer> attributeToCount = new HashMap<PreFillType, Integer>();    
 for (PreFillType size : preFillSizes) {        
   //根據(jù)權(quán)重計(jì)算出的圖片的內(nèi)存占用量
   int bytesForSize = Math.round(bytesPerWeight * size.getWeight());  
   //根據(jù)寬高和Config計(jì)算出的圖片的內(nèi)存占用量
   int bytesPerBitmap = getSizeInBytes(size);     
   int bitmapsForSize = bytesForSize / bytesPerBitmap;
   attributeToCount.put(size, bitmapsForSize);    
  }   
 return new PreFillQueue(attributeToCount);
}

5. DefaultConnectivityMonitor 網(wǎng)絡(luò)狀態(tài)監(jiān)視器

平常的開發(fā)中使用頻率還是比較高的,可以直接拿來用,完整代碼請(qǐng)參考源碼

private final BroadcastReceiver connectivityReceiver = new BroadcastReceiver() {    
@Override    
public void onReceive(Context context, Intent intent) {        
  boolean wasConnected = isConnected;        
  isConnected = isConnected(context);        
  if (wasConnected != isConnected) {   
     listener.onConnectivityChanged(isConnected);        
    }    
  }
};
public DefaultConnectivityMonitor(Context context, ConnectivityListener listener) {    
  this.context = context.getApplicationContext();    
  this.listener = listener;
}
private void register() {    
 if (isRegistered) {        
  return;    
 }    
isConnected = isConnected(context);    
context.registerReceiver(connectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));   
isRegistered = true;}private void unregister() {    
if (!isRegistered) {        
  return;    
  }    
context.unregisterReceiver(connectivityReceiver);    
isRegistered = false;
}
private boolean isConnected(Context context) {    
  ConnectivityManager connectivityManager = (ConnectivityManager) context
    .getSystemService(Context.CONNECTIVITY_SERVICE);    
  NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();    
  return networkInfo != null && networkInfo.isConnected();
}
@Override
public void onStart() {    
  register();
}
@Override
public void onStop() {    
  unregister();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容