https://www.bilibili.com/video/BV1m4411r73w?p=3https://www.bilibili.com/video/BV1m4411r73w?p=3https://www.bilibili.com/video/BV1m4411r73w?p=3
为什么要使用多线程?
a)提高用户体验或避免ANR
在事件处理中需要使用多线程,否则会出现ANR,或者因为响应较慢导致用户体验很差。
b)异步
应用中有些情况并不一定需要同步阻塞去等待返回结果,可以通过多线程来实现异步,例如你的应用的某个Activity需要从云端
获取一些图片,加载图片比较耗时,这时需要使用异步加载,加载完成一个图片刷新一个。
c)多任务
例如多线程下载
ANR全程Application Not Responding,意思是程序未响应,如果一个应用无法响应用户的输入,系统就会弹出一个ANR对话框,用户可以自行选择继续等待亦或停止当前程序。
我们继续来了解一下Android应用程序的main线程,它负责处理UI的绘制,Android系统为了防止应用程序较慢导致系统无法正常运行做了一个处理,一种情况是当用户输入事件在5秒内无法得到响应,那么系统会弹出ANR对话框,由用户决定继续等待还是强制结束应用程序。(另一种情况是BroadcastReceiver超过10秒没有执行完也会弹出ANR对话框,见文章 ANR 弹窗的显示原理)
ANR 的四种场景:
Service TimeOut: service 未在规定时间执行完成:前台服务 20s,后台 200s
BroadCastQueue TimeOut: 未在规定时间内未处理完广播:前台广播 10s 内, 后台 60s 内
ContentProvider TimeOut: publish 在 10s 内没有完成
Input Dispatching timeout: 5s 内未响应键盘输入、触摸屏幕等事件
ANR 的根本原因是:应用未在规定的时间内处理 AMS 指定的任务才会 ANR。
另外,人眼可以分辨的时间的160毫秒,超过这个时间就可以感到卡顿,所以要控制好这个时间。
事件处理的原则:所有可能耗时的操作都放到其他线程去处理。
Android中的main线程的事件处理不能太耗时,否则后续的事件无法在5秒内得到响应,就会弹出ANR对话框。那么哪些方法会在main线程执行呢?
1)Activity的生命周期方法,例如:onCreate()、onStart()、onResume()等
2)事件处理方法,例如onClick()、onItemClick()等
通常Android基类中的以on开头的方法是在main线程被回调的。
提高应用的响应性,可以从这两方面入手。
一般来说,Activity的onCreate()、onStart()、onResume()方法的执行时间决定了你的应用首页打开的时间,这里要尽量把不必要的操作放到其他线程去处理,如果仍然很耗时,可以使用SplashScreen。使用SplashScreen最好用动态的,这样用户知道你的应用没有死掉。
当用户与你的应用交互时,事件处理方法的执行快慢决定了应用的响应性是否良好,
这里分两种情况:
1)同步,需要等待返回结果.例如用户点击了注册按钮,需要等待服务疏返回结果,那么需要有一个进度条来提示用户你的程序正在运行没有死掉。一般与服务端交互的都要有进度条,例如系统自带的浏览器,URL跳转时会有进度条.
2)异步,不需要等待返回结果。例如微博中的收藏功能,点击完收藏按钮后是否成功执行完成后告诉我就行了,我不想等它,这里最好实现为异步的. 无论同步异步,事件处理都可能比较耗时,那么需要放到其他线程中处理, 等处理完成后,再通知界面刷新。
这里有一点要注意,不是所有的界面刷新行为都需要放到Main线程处理, 例如Textview的setText()方法需要在Main线程中,否则会抛出 CalledFromWrongThreadException ,而ProgressBar的setProgress()方法 则不需要在Main线程中处理,当然你也可以把所有UI组件相关行为都放到Main线程中处理,没有问题。可以减轻你的思考负担,但你最好了解他们之间的差别,掌握事物之间细微差别的是专家。把事件处理代码放到其他线程中处理,如果处理的结果需要
刷新界面,那么需要线程间通讯的方法来实现在其他线程中发消息绐 Main线程处理.
Handler、Looper、Message、MessageQueue
AsyncTask是Android框架提供的异步处理的辅助类,它可以实现耗时操作 在其他线程执行,而处理结果在Main线程执行,对于开发者而言,它屏蔽掉了多线程和后面要即Handler的概念。你不了解怎么处理线程间通讯也 没有关系,AsyncTask体贴的帮你做好了。不过封装越好施高级的API,对初级程序员反而越不利,就是你不了解它的原理.当你需要面对更加复杂的情况,而高级API无法完成得很好时,你就杯具了.所以,我们也要掌握功 能更强大,更自由的与Main线程通讯的方法:Handler的使用.
这里我们建议使用线程池来管理临时的Thread对象,从而达到提高应用程序 性能的目的.
线程池是资源池在线程应用中的一个实例.了解线程池之前我们首先要了解 一下资源池的概念在JAVA中,创建和销毁对象是匕阳消耗资源的。我们 如果在应用中需要频繁创建销毁某个类型的对象实例,这样会产生很多临时 对象,当失去引用的情时对象较多时,虚拟机会进行垃圾回收(GC), CPU在 进行GC时会导致应用程序的运行得不到相应,从而导致应用的响应性降低。
资源池就是用来解决这个问题,当你需要使用对象时,从资源池来获取.资 源池负责维护对象的生命周期。了解了资源池,就很好理解线程池了,线程 池就是存放对象类型都是线程的资源池.
继承Thread类的方法尽管被我列为一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extends Thread ,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。
如果自己的类extends另一个类,就无法直接extends Thread ,此时 必须实现一个Runnable接口。同时为了启动MyThread ,需要首先实例化一个Thread ,用传入自己的已经实现好Runnable接口的目标对象。
1 ,一个类只能继承一个父类,存在局限;一个类可以实现多个接口
2 ,在实现Runable接口的时候调用Thread(Runnable target)创建进程时 ,使用同一个Runnable实例,则建立的多线程的实例变量也是共享的。 但是通过继承Thread类是不能用一个实例建立多个线程,故而实现 Runnable接口适合于资源共享。当然,继承Thread类也能够共享变量, 能共享Thread类的static变量;
3 , Runnable接口和Thread之间的联系:
public class Thread extends Object implements Runnable可以看出Thread类也是Runnable接口的子类;
public class Constant {
public static final String TAG = "Multi_Thread";
}
public class MyThread extends Thread {
@Override
public void run() {
Log.i(Constant.TAG, Thread.currentThread().getName() + ".run()");
}
}
public class MyRunnable implements Runnable{
@Override
public void run() {
Log.i(Constant.TAG, Thread.currentThread().getName() + ".run()");
}
}
public class SaleTicket implements Runnable {
private int ticket = 20;
@Override
public void run() {
while (true) {
synchronized (this){
if (ticket > 0) {
Log.i(Constant.TAG, Thread.currentThread().getName() + "卖出了第" + (20 - ticket + 1) + "张票");
ticket--;
} else {
break;
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_test_thread).setOnClickListener(view -> testThread());
findViewById(R.id.btn_test_runnable).setOnClickListener(view -> testRunnable());
findViewById(R.id.btn_test_sale).setOnClickListener(view -> testSale());
}
private void testSale() {
SaleTicket saleTicket = new SaleTicket();
Thread thread1 = new Thread(saleTicket, "A代理");
Thread thread2 = new Thread(saleTicket, "B代理");
Thread thread3 = new Thread(saleTicket, "C代理");
Thread thread4 = new Thread(saleTicket, "D代理");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
private void testRunnable() {
MyRunnable runnable1 = new MyRunnable();
Thread thread1 = new Thread(runnable1);
MyRunnable runnable2 = new MyRunnable();
Thread thread2 = new Thread(runnable1);
thread1.start();
thread2.start();
}
private void testThread() {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start();
thread2.start();
}
}