前言
众所周知,在安卓中,我们的主线程是不可以进行一些耗时的操作的。因为当主线程超过五秒无响应后,我们的程序就会ANR。但是,在日常开发中,我们又避免不了去做一些耗时的操作。比如说访问网络,文件操作等等。但我们的主线程因为上述原因,却不能轻松完成这些操作,那怎么办?这时候就需要开启新的子线程。有的时候,我们需要把这些操作的结果更新到我们的UI界面上。咦,可是安卓也是规定了的,非UI线程(主线程)是不能更新UI的,那么,我们应该怎么办呢?不要担心,居然有这样的规定,就肯定会有解决的办法嘛= ̄ω ̄=,所以,安卓给我们提供了一种Handler机制。
为什么要这样规定呢
我们以前初中高中做政治题的时候,老师就教我们,遇到问题,首先要知道是什么,然后去想为什么,最后我们再来解决,也就是怎么做。所以,我们可以先来思考一下,为什么安卓中要规定UI界面的更新只能在主线程中呢?
假设一下,如果我们可以在非主线程更新UI,就会造成多个子线程去对我们的UI界面进行操作,那么,我们的界面就会变得非常混乱,界面更新就会存在多并发线程安全的问题。但是我们如果去对UI界面的更新进行加锁的话,我们的性能就会下降。
Android中更新界面的四种方法
方法一:runOnUiThread()
用法:
private void method1() {
new Thread(new Runnable() {
@Override
public void run() {
//耗时操作
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
runOnUiThread(new Runnable() {
@Override
public void run() {
//更新界面
textView.setText("啦啦啦");
}
});
}
}).start();
}
原理:
点击进入源码查看,我们可以发现它的实现非常简单。首先判断当前线程是不是主线程,如果是,就直接run这个Runnable,如果不是,就调用handler中的Post方法。
@Override
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
方法二:handler.post()
用法:
private void method2() {
new Thread(new Runnable() {
@Override
public void run() {
//耗时操作
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.post(new Runnable() {
@Override
public void run() {
textView.setText("啦啦啦");
}
});
}
}).start();
}
原理:
其实就是调用了handler的 sendMessageDelayed(),将runnable包装成message交给looper,looper再给handler去处理。所以其实我们也可以通过 sendMessage去更新,我们只需要重写我们的handleMessage就可以了,这里就不再说啦。都是一样的嘛~
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
方法三:View.post()
用法:
private void method3() {
new Thread(new Runnable() {
@Override
public void run() {
//耗时操作
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
textView.post(new Runnable() {
@Override
public void run() {
textView.setText("啦啦啦");
}
});
}
}).start();
}
原理:
首先判断我们这个mAttachInfo(我猜测是view绑定的线程上),如果不为空我们就调用handler的Post,进入相应的源码,如果为空就去尝试找到。其实不管怎么样,他的目的就是找到可以更新UI的线程的handler。View.postDelayed(Runnable, long)和View.post(Runable)方法一样,只是设置了延迟时间,而View.post(Runable)是立即执行。
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
方法四:AsyncTask
用法:
private AsyncTask a = new AsyncTask<String,Void,Boolean>(){
@Override
protected Boolean doInBackground(String... params) {
//在后台执行耗时操作
return null;
}
@Override
protected void onPreExecute() {
//做一些初始化操作
super.onPreExecute();
}
@Override
protected void onPostExecute(Boolean aBoolean) {
//异步任务执行完成后,也就是doInBackground()方法完成后
//运行再主线程中哦,更新更新
super.onPostExecute(aBoolean);
}
@Override
protected void onProgressUpdate(Void... values) {
//由publishProgress内部调用,表示任务进度更新
//更新更新
super.onProgressUpdate(values);
}
};
原理:
其实就是安卓替我们封装了Handler和Thread,他的使用我们需要注意需要在主线程中创建而且只能在主线程中调用一次execute。而且可能会造成内存泄漏的问题,这时候我们需要用到若引用,或用静态内部类。
我们真的不能在其他线程更新UI嘛?
试试看
我们试试看直接在Thread中更新UI
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.tv_main);
new Thread(new Runnable() {
@Override
public void run() {
textView.setText("lalalla");
}
}).start();
}
}
咦,当我们运行上面这行代码的时候,UI界面居然被成功更新,而且没有抛出异常。当我们在上面代码中让线程休眠几秒钟,我们的线程又抛出了异常。这到底是为什么呢?
深入源码
当我们在非UI线程更新后,会打印一段
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
也就是说,我们的ViewRootImpl(一个隐藏类)中抛出了异常。根据一层层的深入,我们可以发现源码中有一个checkThread()的方法,这个方法就是用来判断我们的线程是不是在主线程。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
原来,我们的安卓就是通过这样一个方法来局限我们的。咦,那么问题来了,为什么我们上面那种情况不会报错呢?这就说明,当我们在onCreate的时候我们去调用这个更新UI界面操作的时候,源码还没有去检测我们的线程。这就和我们activity的生命周期有关系了,其实是因为我们的ViewRootImpl是在onResume中去初始化的。这里推荐一篇博客:Android开发经验。
各自优缺点:
1.handler机制,结构清晰但是代码过多。
2.asyncTask,简单,快捷,将一些复杂的情况简单化,但是可能会新开一些线程,消耗系统的资源。
3.runOnUiThread,代码简单,需要传递context对象。
一些自言自语:近期目标仍然是:提高学习的效率(但是也不要操之过急哦,不要给自己太大压力,嘻嘻)。然后鼓励一下自己,最近还是有很大的进步!!!明天回家,本来想带电脑回家写博客的,但是今晚写了这篇,那就这两天去梳理一下handler原理和AsyncTask吧,回学校之后就可以整理写一篇博客了。嗯,加油咯~黄雅倩~,嘻嘻,回寝室啦,晚安( ̄o ̄) . z Z