Androidプログラミング権威ガイド(第2版)学習ノート(24)-第24章Looper、Handler、HandlerThread

7660 ワード

この章では,Looper,Handler,およびスレッド間のインタラクションについて述べる.
GitHubアドレス:24章を完成したが未完成挑戦完成24章挑戦1 24章挑戦を完成2
この章では、新しいスレッドを使用し、Looperを使用して画像のダウンロードを完了します.なぜAsyncTaskのdoInBackgroundに直接ネットワークダウンロードコードを追加しないのですか?そうすれば、すべての100枚のダウンロードが完了するまで、1枚ずつダウンロードしなければなりません.最後にonPostExecuteが実行されます(...)方法では、ダウンロードしたすべての画像をRecyclerViewビューに表示し、時間とメモリを消費します.AsyncTaskはバックグラウンドスレッドを実行する最も簡単な方法ですが、重複して長時間実行されるタスクには適用されません.
1.各種定義
1.1 Looper
Androidシステムでは、スレッドにメッセージキュー(message queue)があります.メッセージキューを使用するスレッドをメッセージループ(message loop)と呼びます.メッセージループは、キューに新しいメッセージがあるかどうかをループします.メッセージループはスレッドとlooperで構成されます.Looperオブジェクトはスレッドのメッセージキューを管理しています.プライマリ・スレッドはメッセージ・ループであるため、looperもあります.プライマリ・スレッドのすべての作業はlooperによって行われます.looperは、メッセージキューからメッセージをキャプチャし続け、メッセージによって指定されたタスクを完了します.
1.2 Message
Messageクラスは、Handlerのサブクラスであり、実装時に定義する必要がある3つのインスタンス変数がいくつかあるHandler伝達メッセージに使用されます.
  • what:メッセージを記述するためにユーザ定義int型メッセージコード.
  • obj:メッセージ送信に伴うユーザ指定オブジェクト.
  • target:メッセージを処理するHandler.MessageのtargetはHandlerクラスの一例である.Messageを作成すると、Handlerに自動的に関連付けられます.Messageが処理される場合、Handlerオブジェクトはメッセージ処理イベントをトリガーします.

  • 1.3 Handler
    Handlerはmessage handlerの略称と見なすことができる.HandlerはMessageを処理するターゲットだけでなく、Messageを作成してパブリッシュするインタフェースでもある.Handlerを作成すると、スレッドのLooperに自動的に関連付けられます.
    1.4三者関係
  • Threadは1つのLooper、すなわちメッセージキュー
  • のみである
  • 1 1つのThreadは、1つのメッセージキュー
  • を共有する複数のHandlerを有することができる
  • 1 1つのMessageは1つのHandlerのみに対応し、1つのHandlerは複数のMessage
  • を有することができる.
    1.5典型的なLooperの処理プロセス
    まずLooperを用意し、他のスレッドが参照できるHandlerを確立し、必要に応じてHandlerにLooperにメッセージを送信して実行させる必要があります.
    2. HandlerThread
    HandlerThreadクラスはLooperを構築するプロセスを完了し、それを継承すればいくつかの作業を省き、リスクを回避することができます.
    // ThumbnailDowloadler.java        
    public class ThumbnailDowloader extends HandlerThread {
        private static final int MESSAGE_DOWNLOAD = 0;
    
        private Boolean mHasQuit = false;
        private Handler mRequestHandler;
    
        @Override
        protected void onLooperPrepared() {
            //      Handler
            mRequestHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    if (msg.what == MESSAGE_DOWNLOAD) {
                        // get a photo and refresh view
                    }
                }
            };
        }
    
        public void queueThumbnail(T target, String url) {
            //     ,     Handler
            mRequestHandler.obtainMessage(MESSAGE_DOWNLOAD, target)
                        .sendToTarget();
        }
    
        public void clearQueue() {
            mRequestHandler.removeMessages(MESSAGE_DOWNLOAD);
        }
    }
    
    

    プライマリ・スレッドには次のようなものがあります.
    mThumbnailDownloader = new ThumbnailDownloader<>();
    mThumbnailDownloader.start();
    mThumbnailDownloader.getLooper();
    //         
    mThumbnailDownloader.queueThumbnail(holder, url);
    

    3.スレッドインタラクション
    プライマリスレッドは、画像をダウンロードするために、このスレッドを適時に呼び出すことができます.しかし、ダウンロードスレッドがタスクをダウンロードした後、どのようにビューを更新するかという問題もあります.UIはプライマリ・スレッドでしか更新できないことを知っています.そのため、プライマリ・スレッドにHandlerを宣言し、ダウンロード・スレッドに渡し、ダウンロード・スレッドがダウンロードが完了した後、プライマリ・スレッドで更新操作を実行するようにします.プライマリスレッドを直接参照する方法がないため,ここではコールバックを用いた.
    3.1ダウンロードスレッド
    // ThumbnailDownloader,        
    
    //     
    private Handler mResponseHandler;
    private ThumbnailDowloadListener mThumbnailDownloadListener;
    
    //     
    public interface ThumbnailDowloadListener {
        void onThumbnailDownloaded(T target, Bitmap thumbnail);
    }
    
    public void setThumbnailDownloaderListener(ThumbnailDowloadListener listener) {
        mThumbnailDownloadListener = listener;
    }
    
    //              Handler
    public ThumbnailDowloader(Handler responseHandler) {
        super(TAG);
        mResponseHandler = responseHandler;
    }
    

    これにより、メインスレッドは、これらのメソッドを呼び出すことにより、ダウンロードスレッドにメインスレッドのHandlerおよびコールバックインタフェースインスタンスを取得させることができる.
    3.2メインスレッド
    //     
    private ThumbnailDowloader mThumbnailDownloader;
    
    //          
    //    Handler        ,        Looper     
    Handler responseHandler = new Handler(); 
    mThumbnailDownloader = new ThumbnailDowloader<>(responseHandler);
    mThumbnailDownloader.setThumbnailDownloaderListener(
        new ThumbnailDowloader.ThumbnailDowloadListener() {
            @Override
           public void onThumbnailDownloaded(PhotoHolder target, Bitmap thumbnail) {
                Drawable drawable = new BitmapDrawable(getResources(), thumbnail);
                target.bindDrawable(drawable);
            }
        }
    );
    

    3.3ダウンロードスレッドでメインスレッドLooperにメッセージを送信する
    ここでmResponseHandlerにより,ダウンロードスレッドはメインスレッドLooperにバインドされたHandlerにアクセスできるようになった.また、ThumbnailDownloadListenerは、返されたBitmapを使用してUI更新操作を実行する.具体的には、onThumbnailDownloadedで実現し、新たにダウンロードしたBitmapを使ってPhotoHolderのDrawableを設定します.ダウンロードスレッド上でダウンロード画像のリクエストをメッセージキューに入れるのと同様に、カスタムMessageをメインスレッドに返し、ダウンロードした画像を表示するように要求することもできます.ただし、これには別のHandlerサブクラスとhandleMessage(...)が必要です.上書き方法便宜上、もう一つの便利なHandler方法であるpost(Runnable)を使用します.
    mResponseHandler.post(new Runnable() {
        @Override
        public void run() {
            if (mRequestMap.get(target) != url ||
                    mHasQuit) {
                return;
            }
    
            mRequestMap.remove(target);
            mThumbnailDownloadListener.onThumbnailDownloaded(target, bitmap);
        }
    });
    

    ここで,新規のRunnableオブジェクトはMessageのコールバックメソッドとして扱われ,run()メソッドを直接実行するので,オブジェクトやメッセージタイプをHandlerに送るのではなく,どのようにするかを示すメッセージを送ることに相当する.
    4.チャレンジ練習
    4.1キャッシュ層の追加
    まず、このキャッシュは、すべてのダウンロード処理が含まれているため、ダウンロードスレッドに格納されるべきであることを認識することができます.LruCacheはキー値ペアのような存在であり、ピクチャキャッシュでは当然、ピクチャのurlをキーとし、対応するBitmapを値とし、1枚のピクチャをダウンロードする前にCacheにこのピクチャが存在するかどうかを確認し、再ダウンロードは存在しない.
    //               
    
    private LruCache mCache;
    
    @Override
    protected void onLooperPrepared() {
        ……
        //    Cache
        int maxCacheSize = 4 * 1024 * 1024; // 4MiB
        mCache = new LruCache<>(maxCacheSize);
    }
    
    private void handleRequest(final T target) {
        try {
            final String url = mRequestMap.get(target);
    
            if (url == null) {
                return;
            }
    
            final Bitmap bitmap;
            if (mCache.get(url) == null) {
                byte[] bitmapBytes = new FlickrFetchr().getUrlBytes(url);
                bitmap = BitmapFactory
                        .decodeByteArray(bitmapBytes, 0, bitmapBytes.length);
                Log.i(TAG, "Bitmap created");
    
                mCache.put(url, bitmap);
            } else {
                bitmap = mCache.get(url);
                Log.i(TAG, "Bitmap from cache");
            }
            ……
        } catch (IOException ioe) {
            Log.e(TAG, "Error downloading image", ioe);
        }
    }
    

    4.2プリロード
    onBindViewHolderのときに対応するitemの最初の10個と後の10個を1個ずつロードしてキャッシュに入れるという愚かな方法で簡単なプリロードを実現しました.
    //         
    private void handlePreload(String url) {
        try {
            if (url == null) {
                return;
            }
    
            if (mCache.get(url) == null) {
                byte[] bitmapBytes = new FlickrFetchr().getUrlBytes(url);
                Bitmap bitmap = BitmapFactory
                        .decodeByteArray(bitmapBytes, 0, bitmapBytes.length);
                mCache.put(url, bitmap);
            }
    
        } catch (IOException ioe) {
            Log.e(TAG, "Error preloading image", ioe);
        }
    }
    
    //        
    @Override
    public void onBindViewHolder(PhotoHolder holder, int position) {
        ……
        for (int i = Math.max(0, position - 10);
                i < Math.min(mGalleryItems.size() - 1, position + 10);
                i ++ ) {
            Log.i(TAG, "Preload position" + i);
            mThumbnailDownloader.queuePreloadThumbnail(mGalleryItems.get(i).getUrl());
        }
    }
    

    GitHub Page: kniost.github.io :http://www.jianshu.com/u/723da691aa42