为了更好的用户体验,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中通过观察者模式通知出去,由于篇幅有限,这里不再深入。