Facebook製の画像ライブラリFrescoのコードを読んでいる
2015/05/26
Facebookが新たに画像ライブラリFrescoを公開した。
DraweeView,DraweeController,DraweeHierarchyというクラスを利用したMVC的な構成を成しており、画像を効率よく読み込むようになっているらしい。(雑)
どのように画像を読み込んでいるのか気になったのでコードを読んでみた。
初期化から画像の読み込みまでの流れを追う
スタートはFrescoクラスから。
Getting startedではFresco.initialize(context)をApplicationのonCreateで呼び出している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/** Initializes Fresco with the default config. */ public static void initialize(Context context) { ImagePipelineFactory.initialize(context); initializeDrawee(context); } /** Initializes Fresco with the specified config. */ public static void initialize(Context context, ImagePipelineConfig imagePipelineConfig) { ImagePipelineFactory.initialize(imagePipelineConfig); initializeDrawee(context); } private static void initializeDrawee(Context context) { sDraweeControllerBuilderSupplier = new PipelineDraweeControllerBuilderSupplier(context); SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier); } |
initializeを呼ぶとSimpleDraweeViewが初期化されてる。
SimpleDraweeViewはDraweeViewの拡張。
Getting startedではSimpleDraweeViewをXML上で宣言して、findById(…)でSimpleDraweeViewのインスタンスを取得してsetImageURIを呼び出している。
1 2 3 |
Uri uri = Uri.parse("http://frescolib.org/static/fresco-logo.png"); SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view); draweeView.setImageURI(uri); |
setImageURIは具体的にどのような処理をしてるのか。
下記の通り。
1 2 3 4 5 6 7 8 |
public void setImageURI(Uri uri, @Nullable Object callerContext) { DraweeController controller = mSimpleDraweeControllerBuilder .setCallerContext(callerContext) .setUri(uri) .setOldController(getController()) .build(); setController(controller); } |
DraweeControllerを作ってアサインしてる。
シンプルだが、どうやって画像を読み込んで表示しているのか全く想像つかない。
でたらめにコードを読んでると、AbstractDraweeControllerのsubmitRequestというそれらしいメソッドがあった。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
protected void submitRequest() { mEventTracker.recordEvent(Event.ON_DATASOURCE_SUBMIT); getControllerListener().onSubmit(mId, mCallerContext); mSettableDraweeHierarchy.setProgress(0, true); mIsRequestSubmitted = true; mHasFetchFailed = false; mDataSource = getDataSource(); if (FLog.isLoggable(FLog.VERBOSE)) { FLog.v( TAG, "controller %x %s: submitRequest: dataSource: %x", System.identityHashCode(this), mId, System.identityHashCode(mDataSource)); } final String id = mId; final boolean wasImmediate = mDataSource.hasResult(); final DataSubscriber<T> dataSubscriber = new BaseDataSubscriber<T>() { @Override public void onNewResultImpl(DataSource<T> dataSource) { // isFinished must be obtained before image, otherwise we might set intermediate result // as final image. boolean isFinished = dataSource.isFinished(); float progress = dataSource.getProgress(); T image = dataSource.getResult(); if (image != null) { onNewResultInternal(id, dataSource, image, progress, isFinished, wasImmediate); } else if (isFinished) { onFailureInternal(id, dataSource, new NullPointerException(), /* isFinished */ true); } } @Override public void onFailureImpl(DataSource<T> dataSource) { onFailureInternal(id, dataSource, dataSource.getFailureCause(), /* isFinished */ true); } @Override public void onProgressUpdate(DataSource<T> dataSource) { boolean isFinished = dataSource.isFinished(); float progress = dataSource.getProgress(); onProgressUpdateInternal(id, dataSource, progress, isFinished); } }; mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); } |
ここで深追いはしないが、mDataSource.subscribeで画像を取りに行って、dataSubscriberで非同期なイベント処理をしてる感じだろうか。
で、このsubmitRequestはAbstractDraweeControllerのonAttach内で呼び出されていた。
onAttachはDraweeControllerインタフェースの実装である。
では、コントローラーのonAttachは誰が呼び出しているのか。
突然だが、DraweeViewを見てみる。
1 2 3 4 5 |
@Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mDraweeHolder.onAttach(); } |
DraweeViewはImageViewの拡張、上記のようにViewのライフサイクルイベントをフックにしてDraweeHolderクラスのonAttachを呼んでる。
と、いうわけでDraweeHolderを見る。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public void onAttach() { mEventTracker.recordEvent(Event.ON_HOLDER_ATTACH); mIsHolderAttached = true; attachOrDetachController(); } private void attachOrDetachController() { if (mIsHolderAttached && mIsVisible && mIsActivityStarted) { attachController(); } else { detachController(); } } private void attachController() { if (mIsControllerAttached) { return; } mEventTracker.recordEvent(Event.ON_ATTACH_CONTROLLER); mIsControllerAttached = true; if (mController != null && mController.getHierarchy() != null) { mController.onAttach(); } } |
なるほど。
ControllerのonAttachを呼んでいることが確認できた。
- DraweeHolderがViewライフサイクルとDraweeControllerとの仲介役になってる
- ViewのonAttachに応じてAbstractDraweeControllerのonAttachが呼び出され、画像の読み込み処理を行うsubmitRequestメソッドが呼び出される
次はsubmitRequestメソッドの内容を深追いしてみたい。
> ここで深追いはしないが、mDataSource.subscribeで画像を取りに行って、dataSubscriberで非同期なイベント処理をしてる感じだろうか。
dataSubscriberはDataSubscriberインタフェースのインスタンス
1 2 3 4 5 6 7 8 9 |
public interface DataSubscriber<T> { void onNewResult(DataSource<T> var1); void onFailure(DataSource<T> var1); void onCancellation(DataSource<T> var1); void onProgressUpdate(DataSource<T> var1); } |
mDataSourceはDataSourceインタフェースのインスタンス
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public interface DataSource<T> { boolean isClosed(); @Nullable T getResult(); boolean hasResult(); boolean isFinished(); boolean hasFailed(); @Nullable Throwable getFailureCause(); float getProgress(); boolean close(); void subscribe(DataSubscriber<T> var1, Executor var2); } |
DataSourceのsubscribeの実装はどこか。
AbstractDataSourceにあった。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
private final ConcurrentLinkedQueue<Pair<DataSubscriber<T>, Executor>> mSubscribers; public void subscribe(DataSubscriber<T> dataSubscriber, Executor executor) { Preconditions.checkNotNull(dataSubscriber); Preconditions.checkNotNull(executor); boolean shouldNotify; synchronized(this) { if(this.mIsClosed) { return; } if(this.mDataSourceStatus == AbstractDataSource.DataSourceStatus.IN_PROGRESS) { this.mSubscribers.add(Pair.create(dataSubscriber, executor)); } shouldNotify = this.hasResult() || this.isFinished() || this.wasCancelled(); } if(shouldNotify) { this.notifyDataSubscriber(dataSubscriber, executor, this.hasFailed(), this.wasCancelled()); } } |
既に結果がある、終了してる(通信?)、キャンセルされてる(通信?)の場合はSubscriberに通知してる。
何も無ければmSubscribersという不思議なQueueにSubscriberとExecutorをペアにして突っ込んでる。
結局、画像は誰が読み込んでいるのだ。
膨大なクラスに飲み込まれた。。
ImagePipelineとImageRequestが画像のダウンロードあたりを担当してそう。
Hierarchyはどこで登場するのだろう。。
路頭に迷った。
NetworkFetcher?
適当にコードを見る。
HttpUrlConnectionNetworkFetcher.java
なんかそれっぽい。
fetchメソッドでHttpUrlConnection使ってるし。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
@Override public void fetch(final FetchState fetchState, final Callback callback) { final Future<?> future = mExecutorService.submit( new Runnable() { @Override public void run() { HttpURLConnection connection = null; try { Uri uri = fetchState.getUri(); URL url = new URL(uri.toString()); connection = (HttpURLConnection) url.openConnection(); InputStream is = connection.getInputStream(); callback.onResponse(is, -1); } catch (Exception e) { callback.onFailure(e); } finally { if (connection != null) { connection.disconnect(); } } } }); fetchState.getContext().addCallbacks( new BaseProducerContextCallbacks() { @Override public void onCancellationRequested() { if (future.cancel(false)) { callback.onCancellation(); } } }); } } |
じゃあ、このNetworkFetcherって誰が使ってるんだろう。
NetworkFetchProducerがHttpUrlConnectionNetworkFetcherのfetchメソッドを呼んでいる。