首先,探讨一个问题,
android的UI操作只能在UI线程(即主线程)操作,为什么呢?
原因如下:
android在invalidate()时,会进行判断,如下:
void checkThread() {
2.
if (mThread != Thread.currentThread()) {
3.
throw new CalledFromWrongThreadException(
4.
"Only the original thread that created a view hierarchy can touch its views.");
5.
}
6.
}
Android UI是通过invalidate()方法去刷新界面的,而invalidate()方法没有进行线程同步,允许多个线程同时访问,invalidate()方法是线程不安全的,所以UI线程就就是不安全的。
线程不安全测策略性能较好。
android采用了UI操作只能在UI线程操作的策略,但是一些耗时的操作在UI线程执行的话,超过5秒易引起ANR,所以就有了耗时操作放到非主线程执行的说法,执行完后通知主线程更新UI.
配合上面的这些规定,android提供了以下几种方式:
1.handler+message机制
使用举例:
1.handler在UI线程处理消息:
public abstract class BaseTwoHandler<T> extends Handler {
//避免内存泄露,对activity的引用采取弱引用,因为有时会出现activity已destroy,message还存在,activity无法回收
WeakReference<T> weakReference;
public BaseTwoHandler(T t){
weakReference=new WeakReference<>(t);
}
@Override
public void handleMessage(Message msg) {
T t=weakReference.get();
if(t!=null){
handleMessage(t,msg);
}
}
public abstract void handleMessage(T t, Message message);
}
//子类继承basehandler
//在Java中,非静态(匿名)内部类会默认隐性引用外部类对象。而静态内部类不会引用外部类对象。
private static class TimeHandler extends BaseHandler<ChantingActivity> {
public TimeHandler(ChantingActivity a) {
super(a);
}
@Override
public void handleMessage(Message msg, @NonNull ChantingActivity chantingActivity) {
switch (msg.what){
case UPDATE_COUNT_TIME:
..........
break;
case SHOW_CONTINUE_DIALOG:
.........
break;
}
}
}
在主线程实例化此handler,此handler即和主线程绑定。
子线程发送消息:
Message msg = handler.obtainMessage();
msg.obj = count;
msg.what = UPDATE_COUNT_TIME;
handler.sendMessage(msg);
activity的ondestroyzhong
if(handler != null){ handler.removeCallbacksAndMessages(null);
}
2.handler在子线程处理消息,进行耗时操作
子类化handler
class MyHandler extends Handler{
public MyHandler() {}
public MyHandler(Looper looper){
super (looper);
}
public void handleMessage(Message msg){
//....这里运行耗时的过程
}
}
}
将子线程与handler绑定
有了Handler子类,则可以在UI线程中进行创建和初始化
HandlerThread handlerThread = new HandlerThread( "backgroundThread" );
handlerThread.start();
MyHandler myHandler = new MyHandler(handlerThread.getLooper());
Message msg = myHandler.obtainMessage();
//....此处对msg进行赋值,可以创建一个Bundle进行承载
msg.sendToTarget();
之后如果需要调用线程,只要对handler传入msg,就可以执行相应的过程了
最后,很重要的一点,HandlerThread 不随Activity的生命周期结束而消亡,所以必须复写Ondestory(),调用HandlerThread .stop()
2.AsyncTask异步
使用Android系统工具类 AsyncTask(Params,Progress,Result)
AsyncTask是一个轻量级线程,三个泛型参数分别是 Params传入参数,int型Progress为进度条进度,Result为返回值。
要使用AsyncTask,必须继承之并复写其中的几个重要的函数。
onPreExecute(), 该方法将在执行实际的后台操作前被UI thread调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条。
doInBackground(Params…), 将在onPreExecute 方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台计算工作。可以调用 publishProgress方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。
onProgressUpdate(Progress…),在publishProgress方法被调用后,UI thread将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。
onPostExecute(Result), 在doInBackground 执行完成后,onPostExecute 方法将被UI thread调用,后台的计算结果将通过该方法传递到UI thread.
注:Task必须在UI线程中创建,并调用并且只能调用一次execute方法,该方法的参数为传入的泛型Params。
其余函数最好不要手动调用,容易造成线程崩溃。多次调用Task,容易造成线程池溢出。
3.Activity.runOnUIThread(Runnable)
这个方法相当简单,我们要做的只是以下几步
1、编写后台线程,这回你可以直接调用UI控件
2、创建后台线程的实例
3、调用UI线程对应的Activity的runOnUIThread方法,将后台线程实例作为参数传入其中。
注意:无需调用后台线程的start方法
4.View.Post(Runnable)
该方法和方法二基本相同,只是在后台线程中能操控的UI控件被限制了,只能是指定的UI控件View。方法如下
1、编写后台线程,这回你可以直接调用UI控件,但是该UI控件只能是View
2、创建后台线程的实例
3、调用UI控件View的post方法,将后台线程实例作为参数传入其中。
5.View.PostDelayed(Runnabe,long)
该方法是方法三的补充,long参数用于制定多少时间后运行后台进程