1. 程式人生 > >利用 HandlerThread 建立一個後臺工作執行緒( AsyncTask 的缺點),處理圖片縮放時記憶體溢位問題

利用 HandlerThread 建立一個後臺工作執行緒( AsyncTask 的缺點),處理圖片縮放時記憶體溢位問題

       HandlerThread 可以幫助建立一個擁有有效 Looper 的後臺執行緒,該 Looper 會關聯一個 Handler,而 Handler 中的 MessageQueue 會處理所有的任務。Android 中最常用的後臺技術之一就是 AsyncTask,這個了類很好用。但是,它也有一些缺點。一個缺點就是 AsyncTask 只能執行一次且受限,如果想要在 Activity 或服務這樣的元件生命週期中執行重複或無限期的任務,AsyncTask 顯示有些工作繁重。通常,需要建立多個 AsyncTask 例項才能完成這些任務。AsyncTask 執行(呼叫 execute() 方法)之後
就不容易停止了,但是可以將其放入 (弱引用)中,這樣就能很好的停止 AsyncTask 的執行。
       本例是通過後臺執行緒任務用兩個按鈕來實現圖片的縮放,裁剪。本例中在載入了一個 233kb 大小的 bg_04 之後就出現的記憶體溢位的情況。通過優化後,解決了該問題。關於 Android 圖片記憶體優化可以參考我以前的部落格《Android 圖片的記憶體優化 http://blog.csdn.net/antimage08/article/details/50445820        顯示 ic_launcher 時的效果 (沒有優化圖片)       載入 bg_04 圖片時的記憶體溢位:
優化後的效果: 利用 HandlerThread 建立後臺執行緒的 ImageProcessor.java 該類實現了 Handler.Callback 介面,該介面在前面的 《SurfaceView 實現高效能的繪製 》和 《TextureView 的使用 》中都有用到 : ImageProcessor.java :
package com.crazy.handlerthreadtest;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;

import java.io.InputStream;

/**
 * Created by antimage on 2016/1/7.
 */
public class ImageProcessor extends HandlerThread implements Handler.Callback {
    public static final int MSG_SCALE = 0;
    public static final int MSG_CROP = 1;

    private Context mContext;
    private Handler mReceiver, mCallback;

    public ImageProcessor(Context context) {
        this(context, null);
    }

    public ImageProcessor(Context context, Handler callback) {
        super("AndroidRecipesWorker");
        mCallback = callback;
        mContext = context.getApplicationContext();
    }

    @Override
    protected void onLooperPrepared() {
        mReceiver = new Handler(getLooper(), this);
    }

    @Override
    public boolean handleMessage(Message msg) {

        Bitmap source, result;
        // 從傳入的訊息中解析引數
        int scale = msg.arg1;
        switch (msg.what) {
            case MSG_SCALE:

                source = readBitMap(mContext, R.drawable.bg_04);
                // 載入圖片較小時可以這麼做,較大時則會 記憶體溢位
         //       source = BitmapFactory.decodeResource(mContext.getResources(),
         //               R.drawable.bg_04);
                // 建立一張新的、縮放的圖片
                result = Bitmap.createScaledBitmap(source,
                        source.getWidth() * scale, source.getHeight() * scale, true);
                break;
            case MSG_CROP:
                source = readBitMap(mContext, R.drawable.bg_04);
       //         source = BitmapFactory.decodeResource(mContext.getResources(),
       //                 R.drawable.ic_launcher);
                int newWidth = source.getWidth() / scale;
                // 建立一張新的、橫向裁剪的圖片
                result = Bitmap.createBitmap(source,
                        (source.getWidth() - newWidth) / 2, 0,
                        newWidth, source.getHeight());
                break;
            default:
                throw new IllegalArgumentException("Unknown Worker Request");
        }

        // 將圖片返回給注執行緒
        if (mCallback != null) {
            mCallback.sendMessage(Message.obtain(null, 0, result));
        }
        return true;
    }

    // 新增、刪除 回撥 Handler
    public void setCallback(Handler callback){
        mCallback = callback;
    }

    /* 佇列操作相關方法 */
    // 縮放圖示為特定的值
    public void scaleIcon(int scale) {
        Message msg = Message.obtain(null, MSG_SCALE, scale, 0, null);
        mReceiver.sendMessage(msg);
    }

    // 居中裁剪圖示,然後縮放為特定的值
    public void cropIcon(int scale) {
        Message msg = Message.obtain(null, MSG_CROP, scale, 0, null);
        mReceiver.sendMessage(msg);
    }

    /**
     * 以最省記憶體的方式讀取本地資源的圖片
     */
    public static Bitmap readBitMap(Context mContext, int resId){
        BitmapFactory.Options opt = new BitmapFactory.Options();
        opt.inPreferredConfig = Bitmap.Config.RGB_565;
        opt.inPurgeable = true;
        opt.inInputShareable = true;
        // 獲取資源圖片
        InputStream is = mContext.getResources().openRawResource(resId);
        return BitmapFactory.decodeStream(is,null,opt);
    }
}
HandlerThread 是一個執行緒,和外部 Handler 一起建立一個後臺執行緒。所以我們必須自己實現一個 Handler 來真正處理我們想要執行的工作。本例自定的處理器實現了 Handler.Callback 介面並傳入了執行緒擁有的新 Handler。這樣做避免了使用 Handler 的子類。在 onLooperPrepared() 回撥之後接收器 Handler 才會被建立,這是因為我們需要用 HandlerThread 所建立的 Looper 物件將要執行的任務傳送到後臺執行緒。 MainActivity.java :
package com.crazy.handlerthreadtest;

import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity implements Handler.Callback{

    private ImageProcessor mWorker;
    private Handler mResponseHandler;

    private ImageView imageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        imageView = (ImageView)findViewById(R.id.image_result);
        // 該 MainActivity 關聯的後臺回撥 Handler
        mResponseHandler = new Handler(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 啟動一個新的工作執行緒
        mWorker = new ImageProcessor(this, mResponseHandler);
        mWorker.start();
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 終止工作執行緒
        mWorker.setCallback(null);
        mWorker.quit();
        mWorker = null;
    }

    /**
     *  後臺執行結果的回撥方法,執行在 UI 執行緒上
     */
    @Override
    public boolean handleMessage(Message msg) {
        Bitmap result = (Bitmap)msg.obj;
        imageView.setImageBitmap(result);
        return true;
    }

    /* 傳送後臺工作執行緒的動作方法 */
    public void onScaleClick(View v) {
        for (int i = 1; i < 10; i++) {
            mWorker.scaleIcon(i);
        }
    }

    public void onCropClick(View v) {
        // i 的初始值部位 0 ,否則引數傳遞過去後會報異常(除數不能為0)
        for (int i = 1; i < 10; i++) {
            mWorker.cropIcon(i);
        }
    }
}
啟動後臺執行緒只需要呼叫 HandlerThread 的 start() 方法,,這時會設定 Looper 和 Handler ,然後等待任務的輸入。終止後臺執行緒也只需要呼叫 quit() 方法就可以停止 Looper 並立即移除佇列中未處理的訊息。將回調設定為 null ,這樣此時可能正在執行的任務就不會再通知 Activity le 。 所需的佈局檔案,content_main.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.crazy.handlerthreadtest.MainActivity"
    tools:showIn="@layout/activity_main">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Scale Icon"
        android:onClick="onScaleClick" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Crop Icon"
        android:onClick="onCropClick" />

    <ImageView
        android:id="@+id/image_result"
        android:scaleType="center"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>