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

       在前面的博客中有提到 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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值