利用 HandlerThread 创建一个后台工作线程( AsyncTask 的缺点),处理图片缩放时内存溢出问题

来源:互联网 发布:ip地址添加网络打印机 编辑:程序博客网 时间:2024/06/07 03:31
       在前面的博客中有提到 HandlerThread 的简单使用:http://blog.csdn.net/antimage08/article/details/50473788 

       HandlerThread 可以帮助创建一个拥有有效 Looper 的后台线程,该 Looper 会关联一个 Handler,而 Handler 中的 MessageQueue 会处理所有的任务。Android 中最常用的后台技术之一就是 AsyncTask,这个了类很好用。但是,它也有一些缺点。一个缺点就是 AsyncTask 只能执行一次且受限,如果想要在 Activity 或服务这样的组件生命周期中执行重复或无限期的任务,AsyncTask 显示有些工作繁重。通常,需要创建多个AsyncTask 实例才能完成这些任务。AsyncTask执行(调用 execute() 方法)之后就不容易停止了,但是可以将其放入 WeakReference (弱引用)中,这样就能很好的停止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>


0 0