Picasso源碼分析記錄

Picasso,看的版本是v.2.5.2

  • 使用方法,大概這么幾種加載資源的形式
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);

Picasso.with(context).load(R.drawable.landing_screen).into(imageView1);

Picasso.with(context).load("file:///android_asset/DvpvklR.png").into(imageView2);

Picasso.with(context).load(newFile(...)).into(imageView3);

還可以對(duì)圖片進(jìn)行一些操作:設(shè)置大小谭网、裁剪、加載中&加載錯(cuò)誤時(shí)顯示的圖片等逻族,參考Picasso官網(wǎng)蜻底。

Picasso.with(context)
    .load(url)
    .resize(50, 50)
    .centerCrop()
    .into(imageView)
Picasso.with(context)
    .load(url)
    .placeholder(R.drawable.user_placeholder)
    .error(R.drawable.user_placeholder_error)
    .into(imageView);
  • 類關(guān)系圖


    Picasso-classes-relation
    Picasso-classes-relation
  • 開(kāi)始捋源碼
    從外部調(diào)用看,first聘鳞,初始化方法:Picasso.java #with(context):?jiǎn)卫J?double-check + 內(nèi)部靜態(tài)類Builder薄辅,在Picasso.java#Builder().build(){}方法中使用默認(rèn)或自定義配置初始化Picasso,返回給外部調(diào)用抠璃。Builder 中初始化了以下配置:
public Picasso build() {
      Context context = this.context;

      if (downloader == null) {  //1
        downloader = Utils.createDefaultDownloader(context);
      }
      if (cache == null) {  //2
        cache = new LruCache(context);
      }
      if (service == null) {  //3
        service = new PicassoExecutorService();
      }
      if (transformer == null) {  //4
        transformer = RequestTransformer.IDENTITY;
      }

      Stats stats = new Stats(cache);  //5

      Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);  //6

      return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
          defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
    }
  }```
  *  downloader
createDefaultDownloader()里站楚,API 9以上使用 OkHttp (最低支持API 9)、以下使用HttpURLConnection 的 Http 的本地緩存搏嗡。

  *  cache
  Picasso自定義了一個(gè) LruCache窿春,與 Google 的實(shí)現(xiàn)類似,但是是針對(duì) value 為 Bitmap 的精簡(jiǎn)實(shí)現(xiàn)采盒。應(yīng)該單獨(dú)寫(xiě)一篇來(lái)回顧下[LRU緩存策略]()旧乞。
  *  service 
創(chuàng)建線程池,默認(rèn)是3個(gè)執(zhí)行線程磅氨,會(huì)根據(jù)網(wǎng)絡(luò)狀況再切換線程數(shù)尺栖。

  * transformer
默認(rèn)配置是返回原始的 Request,Picasso 文檔對(duì)于 RequestTransformer 這個(gè)接口的解釋是
> A transformer that is called immediately before every request is submitted. This can be used to modify any information about a request.
For example, if you use a CDN you can change the hostname for the image based on the current location of the user in order to get faster download speeds.

    如果使用CDN烦租,則可以根據(jù)用戶的當(dāng)前位置更改映像的主機(jī)名延赌,以獲得更快的下載速度。

  * stats 
用于統(tǒng)計(jì)下載和緩存的狀況叉橱,比如總/平均下載數(shù)挫以、緩存命中率/未命中率、下載圖片的總/平均大小等等

 * dispatcher
用以上的1&2&3&5&new Handler(Looper.getMainLooper())窃祝,構(gòu)造了dispatcher掐松,用于調(diào)度任務(wù),handler與主線程進(jìn)行交互。

  最后用 //6 dispatcher 和一些其他的參數(shù)大磺,構(gòu)造 Picasso返回給外部調(diào)用泻仙。構(gòu)造函數(shù)如下:
          Picasso(Context context, //1
              Dispatcher dispatcher, //2
              Cache cache, //3
              Listener listener, //4
              RequestTransformer requestTransformer, //5
              List<RequestHandler> extraRequestHandlers, //6
              Stats stats, //7
              Bitmap.Config defaultBitmapConfig, //8
              boolean indicatorsEnabled, //9
              boolean loggingEnabled //10) {
              ...
              ...
              int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
              int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
              List<RequestHandler> allRequestHandlers =
              new ArrayList<RequestHandler>(builtInHandlers + extraCount);
              allRequestHandlers.add(new ResourceRequestHandler(context));
              if (extraRequestHandlers != null) {
                  allRequestHandlers.addAll(extraRequestHandlers);
              }
              allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
              allRequestHandlers.add(new MediaStoreRequestHandler(context));
              allRequestHandlers.add(new ContentStreamRequestHandler(context));
              allRequestHandlers.add(new AssetRequestHandler(context));
              allRequestHandlers.add(new FileRequestHandler(context));
              allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
              requestHandlers = Collections.unmodifiableList(allRequestHandlers);
              ...
              ...
           }
  
    第6個(gè)參數(shù)比較重要,一個(gè)默認(rèn)添加了7個(gè)RequestHandler的List量没,也可以自定義RequestHandler加進(jìn)去,均繼承自RequestHandler突想,再返回一個(gè)只讀的List殴蹄。類名都很見(jiàn)名知意,對(duì)應(yīng)于開(kāi)篇時(shí)的使用方法猾担,不同的RequestHandler去處理不同資源類型的Request袭灯。除了以上10個(gè)從Builder里傳入的參數(shù)外,Picasso的構(gòu)造函數(shù)里還有以下幾個(gè):
       Picasso(1,2,3,4,5,6,7,8,9,10){
        ...
        this.targetToAction = new WeakHashMap<Object, Action>();  //11
        this.targetToDeferredRequestCreator = new WeakHashMap<ImageView,    DeferredRequestCreator>();  //12
        this.referenceQueue = new ReferenceQueue<Object>();  //13
        this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);  //14
        this.cleanupThread.start();
      }

  Picasso構(gòu)造好之后绑嘹,調(diào)用** load(File | int | String | Uri) **傳入要加載的資源稽荧,不管傳入的什么類型,都是去構(gòu)造一個(gè)RequestCreator工腋,然后返回姨丈。好的,那就接著去看看RequestCreator擅腰。RequestCreator的構(gòu)造函數(shù)很簡(jiǎn)單蟋恬,其中實(shí)例化了這樣一個(gè)變量:
      private final Request.Builder data;
      RequestCreator(Picasso picasso, Uri uri, int resourceId) {
         ...//省略
        this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
      }
進(jìn)而調(diào)用** load() **方法返回 Request.Builder 后可以調(diào)用一些像在前面使用方法里的那些方法,對(duì)圖像做二次處理趁冈,這些方法都是Request.java 里的方法歼争,rotate、resize什么的渗勘,其中有一個(gè)方法靈活性很高沐绒,可以滿足自定義的一些需求,就是 ** transform(Transformation transformation) **旺坠。比如讓圖片模糊顯示(讓我想起了微信某個(gè)版本的臨時(shí)性玩法)乔遮,等等。詳細(xì)用法可見(jiàn) [Picasso — Image Rotation and Transformation](https://futurestud.io/tutorials/picasso-image-rotation-and-transformation)

  接下來(lái)价淌,最常用到的方法就是**into(ImageView)**方法了申眼,一系列check以及初次調(diào)用沒(méi)有緩存時(shí),重要的是以下幾個(gè)方法:

public void into(ImageView target, Callback callback) {
...//省略
Request request = createRequest(System.nanoTime());
String requestKey = createKey(request);
Action action = new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,errorDrawable, requestKey, tag, callback, noFade);
picasso.enqueueAndSubmit(action);
}

生成一個(gè)唯一標(biāo)識(shí)的 requestKey 來(lái)構(gòu)造 ImageViewAction蝉衣,它繼承自Action括尸,封裝了一些回調(diào)方法:**complete(),error(),cancel()**等。然后作為參數(shù)病毡,調(diào)用**enqueueAndSubmit(Action)**

void enqueueAndSubmit(Action action) {
Object target = action.getTarget();
if (target != null && targetToAction.get(target) != action) {
// This will also check we are on the main thread.
cancelExistingRequest(target);
targetToAction.put(target, action);
}
submit(action);
}

判定不為空&&取消當(dāng)前target已有請(qǐng)求后濒翻,把當(dāng)前的target和action放入targetToAction中,就是上面構(gòu)造Picasso時(shí)的第11個(gè)變量,**cancelExistingRequest(target)**其實(shí)就是從這個(gè)WeakHashMap里移除有送。重點(diǎn)的方法來(lái)了**submit(action)**:

void submit(Action action) {
dispatcher.dispatchSubmit(action);
}

這個(gè)dispatcher就是前面builder里的第6個(gè)變量淌喻,再來(lái)回顧一下它的構(gòu)造:

Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);

看了dispatcher手里的牌,接下來(lái)要干什么是不是有點(diǎn)不言而喻了雀摘?

void dispatchSubmit(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}

@Override public void handleMessage(final Message msg) {
switch (msg.what) {
case REQUEST_SUBMIT: {
Action action = (Action) msg.obj;
dispatcher.performSubmit(action);
break;
}
...
}

簡(jiǎn)化一下performSubmit:

void performSubmit(Action action, boolean dismissFailed) {
BitmapHunter hunter = hunterMap.get(action.getKey());
//1
if (hunter != null) {
hunter.attach(action);
return;
}
...
//2
hunter = forRequest(action.getPicasso(), this, cache, stats, action);
hunter.future = service.submit(hunter);
hunterMap.put(action.getKey(), hunter);
...
}

截了兩段我覺(jué)得比較重要的片段裸删。
* 第1段,沒(méi)明白attach之后是要干什么阵赠,因?yàn)橹苯泳蛂eturn了涯塔,按照 **hunter.attach(action)**里的邏輯看是把相同key的action添加到了BitmapHunter內(nèi)部維護(hù)的一個(gè)List<Action>中,但是目前沒(méi)有找到對(duì)于List<Action>的主動(dòng)操作清蚀,只是cancel匕荸、detach用到了。
* 第2段枷邪,先是構(gòu)造了BitmapHunter

static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
Action action) {
Request request = action.getRequest();
List<RequestHandler> requestHandlers = picasso.getRequestHandlers();

  // Index-based loop to avoid allocating an iterator.
  //noinspection ForLoopReplaceableByForEach
  for (int i = 0, count = requestHandlers.size(); i < count; i++) {
    RequestHandler requestHandler = requestHandlers.get(i);
    if (requestHandler.canHandleRequest(request)) {
      return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
    }
  }

  return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}
通過(guò)責(zé)任鏈模式榛搔,匹配之前添加的那些RequestHandler誰(shuí)能處理當(dāng)前發(fā)送過(guò)來(lái)的Request(匹配request的資源的Uri的scheme等判斷邏輯),**requestHandler.canHandleRequest(request)**為true就用這個(gè)匹配的requestHandler作為參數(shù)構(gòu)造一個(gè)BitmapHunter返回。BitmapHunter是實(shí)現(xiàn)了Runnable接口的,再貼一下上面的代碼:

hunter.future = service.submit(hunter);

值得注意的是這里hunter內(nèi)部持有了一個(gè)future對(duì)象肥照,是Future類型欢际,它代表一個(gè)異步任務(wù)的計(jì)算結(jié)果。把**service.submit(Runnable)**的結(jié)果賦值給了future,這是為了拿到這個(gè)返回結(jié)果可以做后續(xù)的**cancel()**操作。如果不需要這個(gè)結(jié)果,一般調(diào)用的是**service.execute(Runnable)**穷娱。stackoverflow上有一段關(guān)于這兩個(gè)方法的選擇問(wèn)答,說(shuō)的還蠻清楚的运沦。[Choose between ExecutorService's submit and ExecutorService's execute](http://stackoverflow.com/questions/3929342/choose-between-executorservices-submit-and-executorservices-execute)
然后去看看實(shí)現(xiàn)了Runnable接口的BitmapHunter內(nèi)部的**run()**方法(簡(jiǎn)化后的):

@Override
public void run() {
...
result = hunt();
if (result == null) {
dispatcher.dispatchFailed(this);
} else {
dispatcher.dispatchComplete(this);
}
...
}

Bitmap hunt() throws IOException {
Bitmap bitmap = null;

if (shouldReadFromMemoryCache(memoryPolicy)) {
  bitmap = cache.get(key);
  if (bitmap != null) {
    stats.dispatchCacheHit();
    loadedFrom = MEMORY;
    }
    return bitmap;
  }
}

data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (result != null) {
  loadedFrom = result.getLoadedFrom();
  exifOrientation = result.getExifOrientation();
  bitmap = result.getBitmap();

  // If there was no Bitmap then we need to decode it from the stream.
  if (bitmap == null) {
    InputStream is = result.getStream();
      bitmap = decodeStream(is, data);
  }
}

if (bitmap != null) {
  stats.dispatchBitmapDecoded(bitmap);
  if (data.needsTransformation() || exifOrientation != 0) {
    synchronized (DECODE_LOCK) {
      if (data.needsMatrixTransform() || exifOrientation != 0) {
        bitmap = transformResult(data, bitmap, exifOrientation);
      }
      if (data.hasCustomTransformations()) {
        bitmap = applyCustomTransformations(data.transformations, bitmap);  
      }
    }
    if (bitmap != null) {
      stats.dispatchBitmapTransformed(bitmap);
    }
  }
}
return bitmap;

}

先根據(jù)緩存策略判斷是否允許從緩存中讀取泵额,允許的話就從cache中g(shù)et,get到的話在stats中記錄命中了緩存并返回命中的bitmap携添,不允許或未get到就去從之前匹配到的**RequestHandler.load()**嫁盲,得到**load()**的結(jié)果再判斷是否有對(duì)結(jié)果的二次處理什么的,就不再贅述烈掠。主要分析下load()跟進(jìn)去的方法羞秤,比如此次請(qǐng)求的是個(gè)url資源,匹配的是**NetworkRequestHandler**左敌,去看下它的**load()**方法(簡(jiǎn)化后的):

@Override @Nullable public Result load(Request request, int networkPolicy) throws IOException {
Response response = downloader.load(request.uri, request.networkPolicy);
Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;
InputStream is = response.getInputStream();
return new Result(is, loadedFrom);
}

先調(diào)用downloader的load方法瘾蛋,去獲得請(qǐng)求的Response,我開(kāi)始看的是grep code上的源碼2.5.2版矫限,http請(qǐng)求時(shí)的downloader還像前文分析的那樣分為OkHttpDownloader(API 9以上)和UrlConnectionDownloader(API 9以下)哺哼,寫(xiě)這篇時(shí)再看github上的源碼由于OkHttp更新到OkHttp3佩抹,只有OkHttp3Downloader這一個(gè)用于url請(qǐng)求時(shí)的Downloader了。無(wú)論哪種其內(nèi)部邏輯都是請(qǐng)求網(wǎng)絡(luò)返回Response取董,然后根據(jù)Picasso的緩存策略利用的http的本地緩存棍苹。
一大圈的調(diào)用,順利地在BitmapHunter的**run()**方法中拿到result且不為空的話茵汰,就調(diào)用了**dispatcher.dispatchComplete(this)**枢里,這個(gè)方法又調(diào)用了:

void dispatchComplete(BitmapHunter hunter) {
handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}

case HUNTER_COMPLETE: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performComplete(hunter);
break;
}

void performComplete(BitmapHunter hunter) {
if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
cache.set(hunter.getKey(), hunter.getResult());
}
hunterMap.remove(hunter.getKey());
batch(hunter);
}
}

在這里,根據(jù)緩存策略蹂午,將結(jié)果里的Bitmap加入內(nèi)存Cache中坡垫,也就是前面實(shí)例化的LruCache。然后發(fā)送一個(gè)HUNTER_DELAY_NEXT_BATCH画侣,
handleMessage(Message)中處理:

case HUNTER_DELAY_NEXT_BATCH: {
dispatcher.performBatchComplete();
break;
}

void performBatchComplete() {
List<BitmapHunter> copy = new ArrayList<>(batch);
batch.clear();
mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage( HUNTER_BATCH_COMPLETE, copy));
logBatch(copy);
}

這個(gè)mainThreadHandler也就是最開(kāi)始Picasso的構(gòu)造函數(shù)里Builder里放入去實(shí)例化Dispatcher傳入的那個(gè)HANDLER,再次回到Picasso.java里去看當(dāng)時(shí)HANDLER的初始化:

static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
case HUNTER_BATCH_COMPLETE: {
@SuppressWarnings("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
//noinspection ForLoopReplaceableByForEach
for (int i = 0, n = batch.size(); i < n; i++) {
BitmapHunter hunter = batch.get(i);
hunter.picasso.complete(hunter);
}
break;
}
...
}
};

一切又都回到了當(dāng)初入口類里的**complete(BitmapHunter)** 堡妒,按照源碼邏輯配乱,順利的話接下來(lái)是 **deliverAction(Bitmap result, LoadedFrom from, Action action, Exception e)**,再action.complete(result, from);從上面的分析可知皮迟,最終調(diào)用的是繼承了Action的ImageViewAction的complete方法搬泥,let's go(依然是簡(jiǎn)化版):

@Override
public void complete(Bitmap result, Picasso.LoadedFrom from) {
ImageView target = this.target.get();
Context context = picasso.context;
boolean indicatorsEnabled = picasso.indicatorsEnabled;
PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
if (callback != null) {
callback.onSuccess();
}
}

這樣就完成對(duì)控件的圖片設(shè)置。

  源碼分析下來(lái)伏尼,發(fā)現(xiàn)Picasso其實(shí)非常適合作為一個(gè)骨架去自定義重要模塊的實(shí)現(xiàn)忿檩,比如LruCache,比如本地緩存的Downloader爆阶,優(yōu)秀的框架是易于擴(kuò)展的框架燥透。
* 小結(jié):Picasso的源碼不多,也就30幾個(gè)類辨图,通讀一遍也就倆小時(shí)班套,但是記錄成文字倒是花費(fèi)了很長(zhǎng)時(shí)間。這也是我第一次寫(xiě)這么多東西故河。深知了那些寫(xiě)文章分享技術(shù)的人的不易吱韭。同時(shí)也希望自己可以養(yǎng)成記錄的習(xí)慣,這對(duì)于徹底理解一件事物還是非常有幫助的鱼的。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末理盆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子凑阶,更是在濱河造成了極大的恐慌猿规,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晌砾,死亡現(xiàn)場(chǎng)離奇詭異坎拐,居然都是意外死亡烦磁,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)哼勇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)都伪,“玉大人,你說(shuō)我怎么就攤上這事积担≡删В” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵帝璧,是天一觀的道長(zhǎng)先誉。 經(jīng)常有香客問(wèn)我,道長(zhǎng)的烁,這世上最難降的妖魔是什么褐耳? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮渴庆,結(jié)果婚禮上铃芦,老公的妹妹穿的比我還像新娘。我一直安慰自己襟雷,他們只是感情好刃滓,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著耸弄,像睡著了一般咧虎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上计呈,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天砰诵,我揣著相機(jī)與錄音,去河邊找鬼捌显。 笑死胧砰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的苇瓣。 我是一名探鬼主播尉间,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼击罪!你這毒婦竟也來(lái)了哲嘲?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤媳禁,失蹤者是張志新(化名)和其女友劉穎眠副,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體竣稽,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡囱怕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年霍弹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娃弓。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡典格,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出台丛,到底是詐尸還是另有隱情耍缴,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布挽霉,位于F島的核電站防嗡,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏侠坎。R本人自食惡果不足惜蚁趁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望实胸。 院中可真熱鬧荣德,春花似錦、人聲如沸童芹。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)假褪。三九已至,卻和暖如春近顷,著一層夾襖步出監(jiān)牢的瞬間生音,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工窒升, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缀遍,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓饱须,卻偏偏與公主長(zhǎng)得像域醇,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蓉媳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 概述 在Android開(kāi)發(fā)界譬挚,大家都知到square出品必屬精品,圖片加載庫(kù)Picasso自然也是酪呻。本文就從源碼角...
    朔野閱讀 659評(píng)論 0 7
  • Picasso源碼完全解析(一)--概述 Picasso源碼完全解析(二)--Picasso實(shí)例的創(chuàng)建 Picas...
    三木青一閱讀 582評(píng)論 0 0
  • 一. 概述 Picasso是Square出品的一個(gè)非常精簡(jiǎn)的圖片加載及緩存庫(kù)减宣,其主要特點(diǎn)包括: 易寫(xiě)易讀的流式編程...
    SparkInLee閱讀 1,079評(píng)論 2 11
  • 傍晚降臨原野旅人尋覓遮風(fēng)避雨處 首先,割下一捆捆燈芯草并將其樹(shù)立在原野上玩荠,把頂部綁緊束好一座草屋出現(xiàn)了漆腌,就這樣 翌...
    間崎閱讀 331評(píng)論 0 7
  • 「求善」這個(gè)系列闷尿,這是第一篇文章塑径。我將「求真」和「求善」列為兩個(gè)獨(dú)立的系列,因?yàn)樵谖业睦斫庵杏蒲猓呤窍嚓P(guān)但又需要厘...
    零敲碎打閱讀 1,061評(píng)論 6 2