【多线程】用HandlerThread管理线程和控制异常

    为了更好的用户体验,Android系统中的耗时操作我们一般都在子线程中执行,从而避免影响用户点击或者布局渲染等处理。甚至在Android4.4版本之后直接在主线程进行网络请求系统会抛出NetWrokOnMainThreadException异常,以此要求我们使用Thread处理。但如果我们无节制的使用new Thread的方式创建线程,这对CPU来说无疑是灾难性的。正如此,Thread的线程数量以及生命周期的管理也成了重中之重。

    在我最近的项目中,就有一个需要使用Thread管理数据库CRUD操作的需求。每当client应用请求在仪表上显示/隐藏/销毁presentation时,server进程都需要根据优先级来授权或者拒绝请求,或者将当前请求加入队列中等待显示。为了在复杂环境下保证程序正常执行,我们就需要使用数据库来保存client的请求信息。当然所有的CRUD操作都需要非阻塞式的在子线程中执行。那么我们如何构建子线程来处理请求呢?难道是使用不停new Thread的方式或者线程池的方式吗?

    我认为不论是反复创建还是使用线程池的方式,都有着管理线程和处理异常的痛点。所以另择路线使用HandlerThread来进行数据库操作。HandlerThread在子线程使用Looper轮询来管理消息处理,我们都知道Looper在没有消息时会处于休眠态,这样可以减少对CPU的占用消耗,这样的方式显然比我们在Thread中使用while(true)要明智的多。同时,在数据库的增删改查操作时难免会因为某些原因导致Exception抛出,所以还需要对异常进行集中处理,这就需要使用CrashHandler来触发HandlerThread退出与重建。

    HandlerThread其实也是Thread的一个子类,同所有Thread的一样,它的执行代码都需要放入run方法中。

public class HandlerThread extends Thread

    以下是HandlerThread的run方法,它在其中手动调用了Looper.prepare方法,并且开启了Looper.loop操作(其实这里可以简单的理解为执行了while(true)循环处理消息,当然Looper不像while(true)那样会在没有消息时也一直执行,而是进入了休眠状态)。这样我们只需要一个拥有此轮询器Looper的Handler就可以往HandlerThread中post消息或者runnable了。

@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

    以上是HandlerThread的部分源码,用法的话参见下面。

         //创建一个线程
		HandlerThread handlerThread = new HandlerThread("demo");
		//指定一个handler用于post消息到thread中进行处理
		Handler mHandler = new Handler(handlerThread.getLooper());
         //启动线程
		handlerThread.start();
         //调用方式
		mHandler.post(new Runnable() {
			@Override
			public void run() {
				//此处会运行在HandlerThread中,我们可以在这里处理耗时操作
			}
		});

    使用方式解释:

        1.创建一个HandlerThread,构造参数是字符串,是此线程的description。

        2.创建Handler,传入之前创建的HandlerThread在run方法中会用到的Looper,使Handler发出的消息都通过此Looper进行轮询处理。

        3.启动线程,启动方式和普通线程没什么两样。

        4.在调用时,使用Handler来post消息到内部的MessageQueue中,再由轮询器Looper处理。

     通过上面我们了解了HandlerThread的用法。以下我在项目中使用HandlerThread来处理数据库操作的关键代码。

package com.wm.msdcoresdk.sdk.dao.performer;

import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Log;

import com.wm.msdcoresdk.sdk.constant.Cluster;
import com.wm.msdcoresdk.sdk.dao.ClusterDao;
import com.wm.msdcoresdk.sdk.dao.contract.ILoadClusterCallback;
import com.wm.msdcoresdk.sdk.dao.contract.IReConnStateCallback;
import com.wm.msdcoresdk.sdk.dao.contract.ISQLOperation;

import java.util.List;

/**
 * Created by [email protected]
 * on 2018/5/26.
 */

public class DBStatePerformer extends BasicStatePerformer {
	private static final String TAG = "DBStatePerformer";
	private static DBStatePerformer mInstance;
	private HandlerThread mHandlerThread;
	private DBHandler mHandler;
	private HandlerThread mLastThread;
	private ISQLOperation mISQLOperation;
	//子线程处理所有逻辑
	public class CrashHandler implements Thread.UncaughtExceptionHandler {

		private Thread.UncaughtExceptionHandler mDefaultHandler;
		/**
		 * 数据库线程发生崩溃时调用
		 */
		public CrashHandler(){
			mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
		}

		@Override
		public void uncaughtException(Thread t, Throwable e) {
			if (!handleException(e)&&mDefaultHandler!=null){
				mDefaultHandler.uncaughtException(t,e);
			}else {
				Log.i(TAG,"数据库线程发生错误!!!");
				//此处是否需要重新创建线程。
				synchronized (DBStatePerformer.class){
					if (mHandlerThread!=null /*&& (!mHandlerThread.isAlive()||mHandlerThread.isInterrupted())*/&& mHandlerThread.getId()== mLastThread.getId()){
						mHandlerThread.quitSafely();
						mHandler.removeCallbacksAndMessages(null);
						mHandlerThread=null;
						mHandler = null;
						Log.i(TAG, "销毁Thread与Handler");
					}
					if (mHandlerThread==null&&mHandler==null){
						Log.i(TAG, "重新创建Thread与Handler");
						mLastThread = mHandlerThread = new HandlerThread("server-db-operation");
						mHandlerThread.start();
						mHandlerThread.setUncaughtExceptionHandler(this);
						mHandler = new DBHandler(mHandlerThread.getLooper());
					}
				}

			}

		}

		private boolean handleException(Throwable throwable) {
			//TODO 此处记录错误日志,并保存到本地or网络
			throwable.printStackTrace();
			return true;
		}
	}
	protected class DBHandler extends Handler{
		public DBHandler(Looper looper) {
			super(looper);
		}
	}
	public DBStatePerformer(Context context){
		mISQLOperation = ClusterDao.getInstance(context);
		mLastThread = mHandlerThread = new HandlerThread("server-db-operation");
		mHandlerThread.start();
		mHandlerThread.setUncaughtExceptionHandler(new CrashHandler());
		mHandler = new DBHandler(mHandlerThread.getLooper());
	}

	public static DBStatePerformer getInstance(Context context) {
		if (mInstance==null){
			synchronized (DBStatePerformer.class){
				if (mInstance==null){
					mInstance = new DBStatePerformer(context);
				}
			}
		}
		return mInstance;
	}

	@Override
	public void addCluster(final Cluster cluster, final String device, final String type) {
		mHandler.post(new Runnable() {
			@Override
			public void run() {
				mISQLOperation.addCluster(cluster.getCID(),cluster.getID(),cluster.getState(),cluster.getApplyState(),cluster.getPriority(),device,type);
				Log.i(TAG, "run: addCluster");
			}
		});
	}

	@Override
	public void updateCluster(final Cluster cluster, final String device, final String type) {
		mHandler.post(new Runnable() {
			@Override
			public void run() {
				mISQLOperation.updateCluster(cluster,device,type);
				Log.i(TAG, "run: updateCluster");
			}
		});
	}

	@Override
	public void destroyCluster(final Cluster cluster, final String device, final String type) {
		mHandler.post(new Runnable() {
			@Override
			public void run() {
				mISQLOperation.deleteCluster(cluster,device,type);
				Log.i(TAG, "run: destroyCluster");
			}
		});
	}

	@Override
	public void destroyCluster(final String cid, final String clusterID, final String device, final String type) {
		mHandler.post(new Runnable() {
			@Override
			public void run() {
				mISQLOperation.deleteCluster(cid,clusterID,device,type);
				Log.i(TAG, "run: destroyCluster->type:"+type);
			}
		});
	}

	@Override
	public void destroyCluster(final String cid, final String device, final String type) {
		mHandler.post(new Runnable() {
			@Override
			public void run() {

				mISQLOperation.deleteCluster(cid,device,type);
			}
		});
	}

	@Override
	public void findClusterByDeviceAndType(final ILoadClusterCallback callback, final String device, final String type) {

		mHandler.post(new Runnable() {
			@Override
			public void run() {
				List clusters = mISQLOperation.findAllByDeviceAndType(device, type);
				//此处注意线程切换
				if (callback!=null)
					callback.onClusterLoad(clusters);
				Log.i(TAG, "run: findClusterByDeviceAndType");
			}
		});
	}

	@Override
	public void findClusterByDeviceAndTypeAndCID(final IReConnStateCallback callback, final String cid, final String device, final String type) {
		mHandler.post(new Runnable() {
			@Override
			public void run() {
				List clusters = mISQLOperation.findClusterByDeviceAndTypeAndCID(callback.getStack(), cid, device, type);
				callback.onClusterLoad(clusters);
			}
		});

	}

	@Override
	public void insertCluster(final int index, final Cluster cluster, final String device, final String type) {
		mHandler.post(new Runnable() {
			@Override
			public void run() {
				mISQLOperation.insertCluster(index,cluster,device,type);
				Log.i(TAG, "run: insertCluster");
			}
		});

	}

	private int findIndexOfLastShowCluster(Cluster cluster, String device, String type) {

		return mISQLOperation.findIndexOfLastShowCluster(cluster,device,type);
	}

	@Override
	public void moveToLast(final int index, final Cluster cluster, final String device, final String type) {
		mHandler.post(new Runnable() {
			@Override
			public void run() {
				insertCluster(index,cluster,device,type);
				Log.i(TAG, "run: moveToLast");
			}
		});
	}

	@Override
	public void compareStack(final List stack, final String device, final String type) {
		//比较当前stack与数据库中的异同
		mHandler.post(new Runnable() {
			@Override
			public void run() {

				mISQLOperation.compareStack(stack,device,type);
				Log.i(TAG, "run: compareStack");
			}
		});

	}

	@Override
	public void destroyInstance() {
		if (mHandlerThread!=null /*&& (!mHandlerThread.isAlive()||mHandlerThread.isInterrupted())*/&& mHandlerThread.getId()== mLastThread.getId()){
			mHandlerThread.quitSafely();
			mHandlerThread=null;
		}
		if (mHandler!=null){
			mHandler.removeCallbacksAndMessages(null);
			mHandler = null;
		}
		if (mISQLOperation!=null){
			mISQLOperation.destroyInstance();
			mISQLOperation = null;
		}
		mInstance = null;
	}

	@Override
	public void bootCompleted() {

		mHandler.post(new Runnable() {
			@Override
			public void run() {
				mISQLOperation.deleteAll();
			}
		});
	}

	@Override
	public void shutDown() {
		mHandler.post(new Runnable() {
			@Override
			public void run() {
				mISQLOperation.deleteAll();
			}
		});
	}
}

    以上代码说明:

        1.ClusterDao是进行数据库操作的类。

        2.HandlerThread除非在执行数据库操作过程中发生异常,否则只会存在一个线程处理数据库操作。

        3.当没有增删改查操作请求时,当前处理线程由于Looper原理处于休眠态。

        4.当处理CRUD操作时抛出Exception,我们使用了CrashHandler把异常截停在当前线程中,不会影响主线程调用,更不会导致主线程崩溃。

        5.在HandlerThread因异常崩溃后,能够自动重建线程,不会影响后续数据库CRUD操作。

    当然,我们还可以在异常catch中通过观察者模式通知出去,由于篇幅有限,这里不再深入。


你可能感兴趣的:(安卓)