最近写代码的时候,由于对Android线程问题不了解,总是会报错。虽然学过一点Android的异步消息处理机制,但是因为没有从根本上对Android线程问题进行了解,所以在开发过程中总是会出现一些不应该的错误。因此我花了一些时间查看书籍跟博客,还看了一些源码,对了解Android线程问题有了一些认识。
1>首先要搞清楚Android中什么是主线程,什么是子线程
主线程即UI线程是进行UI操作的线程,主线程只有一个。子线程是进行一些耗时操作,比如:网络请求、I/O操作等,因为如果将这些耗时操作放在主线程中可能会导致主线程阻塞 从而出现ANR现象。从Android4.0开始,网络请求必须在子线程中进行,佛则会抛出NetWorkOnMainThreadExcept的异常。
2>为什么会出现ANR现象?
Android希望UI线程能根据用户的要求迅速做出响应,如果UI线程花费太多时间处理后台工作,当UI事件发生时,让用户等待5秒而未进行响应,Android系统就会给用户显示ANG信息。UI线程中不仅需要进行UI操作,还要处理Broadcast的消息,所以BroadcastReceiver的onReceive()方法不宜占用太长时间,当让用户等待10s时,
Andorid系统便会显示ANG信息。
3>Android到底有没有main()方法?
我刚接触Android的时候,一直听说Android是支持java语言的,但是我写了这么长时间以来从来没有在Android中看到过main()方法,我都怀疑Android中到底有没有main()方法。答案是有的。Android的main()方法被包装在源码的ActivityThread类中。
public static final void main(String[] args) {
SamplingProfilerIntegration.start();
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
if (sMainThreadHandler == null) {
sMainThreadHandler = new Handler();
}
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
if (Process.supportsProcesses()) {
throw new RuntimeException("Main thread loop unexpectedly exited");
}
thread.detach();
String name = (thread.mInitialApplication != null)
? thread.mInitialApplication.getPackageName()
: "<unknown>";
Slog.i(TAG, "Main thread of " + name + " is now exiting");
}
}
这便是包装在源码ActivityThread类中的Android的main()函数。ActivityThread为应用程序的主线程类。一个APK文件中只有一个ActivityThread类,ActivityThread所在的线程就是主线程(UI线程)。
Activity从main()方法开始执行,先调用perpareMain()方法为主线程创建出一个消息队列(Message Queue),接着创建一个ActivityThread的对象,并在ActivityThread初始化代码中创建一个Handler对象和一个ApplicationThread(Binder)对象。其中Binder对象负责接收
远程AmS的IPC调用,接收到调用后,通过Handler将消息发送到消息队列(Message Queue)中,UI主线程会异步地从消息队列中取出消息进行相应的操作,比如start,pause,stop等,接着通过Looper.loop()方法进入消息循环体进入后就会不断的从消息队列中取出并处理消息。
4>子线程如何开启?
子线程的开启有两种方法:一个是继承Thread类重写run方法,想要开启线程时调用start()方法就行。
class MyThread extends Thread{
@Override
public void run(){
//此处为耗时操作的逻辑代码
}
}
new MyThread().start(); //开启子线程
另一个方法是实现Runnable接口,重写run()方法。
class MyRunnable implements Runnable{
@Override
public void run(){
//此处为耗时操作的逻辑代码
}
}
new MyRunnable().start(); //开启子线程
5>Android APK中都有哪些进程?
其中蓝色部位为主线程,在这个程序中还有3个Binder对象,每个Binder对象都对应一个线程,这些线程主要负责接收Linux Binder驱动发送的IPC调用。除此之外还有Java的守护线程和垃圾回收线程堆裁剪守护线程在运行。
6>程序中UI线程跟自定义的Thread线程有什么区别?
UI线程在是从ActivityThread中运行的,在该类的main()方法中早就调用了Looper.perpareMainLooper()方法创建出一个Looper对象,即为该线程创建了消息队列(因为消息队列是在Looper中)。因此可以直接在活动中定义Handler对象(因为声明Handler必须所在的线程已经创建出了MessageQueue),而自定义Thread,是一个裸线程,不能在其中
定义Handler对象。从使用场景来说,即不能直接给Thread对象发消息,可以直接给UI线程发消息。
7>子线程为什么不能更新UI?
因为UI访问是没有加锁的,多个线程UI访问是不安全的,如果多个线程进行更新UI,会导致界面混乱不堪。
8>Android中存在一个唯一的控件可以在子线程中进行UI更新。
这个控件是SurfaceView,它可以在主线程之外的线程让屏幕绘图,这样可以解除画图任务繁重导致的主线程阻塞。当需要快速主动的更新UI时,或者是当前渲染代码阻塞GUI线程时,SuifaceView就是一个最佳选择。
9>这个问题是我在知乎上看到的
这个问题定不能说明UI更新可以不再子线程中进行,这个问题的原因是线程的检查是用ViewRoot的,而ViewRoot的创建是在onResume()之后,而问题中的子线程是在onCreate()时候,这个时候系统本身分辨不出哪一个是主线程哪一个是子线程。
10>子线程如何与主线程进行通信。
1.Activity.runOnUiThread(Runnable)
2.View.post(Runnable)
3.View.postDelayed(Runnable,long)
4.Handler(子线程调用Handler的handle.sendMessage(msg);
5.AsyncTask
主线程:
aTask ak = new aTask();
ak.execute();
继承AsyncTask
private class aTask extends AsyncTask {
//后台线程执行时
@Override
protected Object doInBackground(Object... params) {
// 耗时操作
return loadNetWork();
}
//后台线程执行结束后的操作,其中参数result为doInBackground返回的结果
@Override
protected void onPostExecute(Object result) {
super.onPostExecute(result);
mTextView.setText(result);
}
}
总结:
一个应用程序只有一个主线程,主线程的开启是ActivityThread中的main()方法,主线程实际是一个死循环(源码中为for(;;)的死循环),不断处理系统以及其他子线程发送的消息。主线程的绑定是在DecorView创建以后,即onResume()以后。主线程进行UI,当在主线程进行耗时操作导致阻塞,或者BroadcastReceiver的onReceive()方法处理超过10s时,系统就会像用户显示ANR。所以耗时操作一般放入子线程中进行处理。