4.4.2、Activity的runOnUiThread方法
相关资料:
=====
1、多线程的意义
========
1.1、为什么要使用多线程
为什么要使用多线程?
a)提高用户体验或避免ANR
在事件处理中需要使用多线程,否则会出现ANR,或者因为响应较慢导致用户体验很差。
b)异步
应用中有些情况并不一定需要同步阻塞去等待返回结果,可以通过多线程来实现异步,例如你的应用的某个Activity需要从云端
获取一些图片,加载图片比较耗时,这时需要使用异步加载,加载完成一个图片刷新一个。
c)多任务
例如多线程下载
1.2、为什么通过多线程可以提高用户体验避免ANR
1.2.1、什么是ANR
ANR全程Application Not Responding,意思是程序未响应,如果一个应用无法响应用户的输入,系统就会弹出一个ANR对话框,用户可以自行选择继续等待亦或停止当前程序。
1.2.2、深入了解
我们继续来了解一下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毫秒,超过这个时间就可以感到卡顿,所以要控制好这个时间。
1.2.3、事件处理原则
事件处理的原则:所有可能耗时的操作都放到其他线程去处理。
Android中的main线程的事件处理不能太耗时,否则后续的事件无法在5秒内得到响应,就会弹出ANR对话框。那么哪些方法会在main线程执行呢?
1)Activity的生命周期方法,例如:onCreate()、onStart()、onResume()等
2)事件处理方法,例如onClick()、onItemClick()等
通常Android基类中的以on开头的方法是在main线程被回调的。
提高应用的响应性,可以从这两方面入手。
一般来说,Activity的onCreate()、onStart()、onResume()方法的执行时间决定了你的应用首页打开的时间,这里要尽量把不必要的操作放到其他线程去处理,如果仍然很耗时,可以使用SplashScreen。使用SplashScreen最好用动态的,这样用户知道你的应用没有死掉。
1.2.4、实际操作
当用户与你的应用交互时,事件处理方法的执行快慢决定了应用的响应性是否良好,
这里分两种情况:
1)同步,需要等待返回结果.例如用户点击了注册按钮,需要等待服务疏返回结果,那么需要有一个进度条来提示用户你的程序正在运行没有死掉。一般与服务端交互的都要有进度条,例如系统自带的浏览器,URL跳转时会有进度条.
2)异步,不需要等待返回结果。例如微博中的收藏功能,点击完收藏按钮后是否成功执行完成后告诉我就行了,我不想等它,这里最好实现为异步的. 无论同步异步,事件处理都可能比较耗时,那么需要放到其他线程中处理, 等处理完成后,再通知界面刷新。
这里有一点要注意,不是所有的界面刷新行为都需要放到Main线程处理, 例如Textview的setText()方法需要在Main线程中,否则会抛出 CalledFromWrongThreadException ,而ProgressBar的setProgress()方法 则不需要在Main线程中处理,当然你也可以把所有UI组件相关行为都放到Main线程中处理,没有问题。可以减轻你的思考负担,但你最好了解他们之间的差别,掌握事物之间细微差别的是专家。把事件处理代码放到其他线程中处理,如果处理的结果需要
刷新界面,那么需要线程间通讯的方法来实现在其他线程中发消息绐 Main线程处理.
1.3、如何实现多线程之间的通讯
1.3.1、Handler
Handler、Looper、Message、MessageQueue
1.3.2、AsyncTask
AsyncTask是Android框架提供的异步处理的辅助类,它可以实现耗时操作 在其他线程执行,而处理结果在Main线程执行,对于开发者而言,它屏蔽掉了多线程和后面要即Handler的概念。你不了解怎么处理线程间通讯也 没有关系,AsyncTask体贴的帮你做好了。不过封装越好施高级的API,对初级程序员反而越不利,就是你不了解它的原理.当你需要面对更加复杂的情况,而高级API无法完成得很好时,你就杯具了.所以,我们也要掌握功 能更强大,更自由的与Main线程通讯的方法:Handler的使用.
1.4、利用线程池提高性能
这里我们建议使用线程池来管理临时的Thread对象,从而达到提高应用程序 性能的目的.
线程池是资源池在线程应用中的一个实例.了解线程池之前我们首先要了解 一下资源池的概念在JAVA中,创建和销毁对象是匕阳消耗资源的。我们 如果在应用中需要频繁创建销毁某个类型的对象实例,这样会产生很多临时 对象,当失去引用的情时对象较多时,虚拟机会进行垃圾回收(GC), CPU在 进行GC时会导致应用程序的运行得不到相应,从而导致应用的响应性降低。
资源池就是用来解决这个问题,当你需要使用对象时,从资源池来获取.资 源池负责维护对象的生命周期。了解了资源池,就很好理解线程池了,线程 池就是存放对象类型都是线程的资源池.
2、多线程的创建
========
2.1、继承Thread实现多线程
继承Thread类的方法尽管被我列为一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extends Thread ,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。
2.2、实现Runnable接口实现多线程
如果自己的类extends另一个类,就无法直接extends Thread ,此时 必须实现一个Runnable接口。同时为了启动MyThread ,需要首先实例化一个Thread ,用传入自己的已经实现好Runnable接口的目标对象。
2.3、实现Runnablej接口和继承Thread的区别
1 ,一个类只能继承一个父类,存在局限;一个类可以实现多个接口
2 ,在实现Runable接口的时候调用Thread(Runnable target)创建进程时 ,使用同一个Runnable实例,则建立的多线程的实例变量也是共享的。 但是通过继承Thread类是不能用一个实例建立多个线程,故而实现 Runnable接口适合于资源共享。当然,继承Thread类也能够共享变量, 能共享Thread类的static变量;
3 , Runnable接口和Thread之间的联系:
public class Thread extends Object implements Runnable可以看出Thread类也是Runnable接口的子类;
2.4、代码实战
2.4.1、Constant
public class Constant {
public static final String TAG = “Multi_Thread”;
}
2.4.2、MyThread
public class MyThread extends Thread {
@Override
public void run() {
Log.i(Constant.TAG, Thread.currentThread().getName() + “.run()”);
}
}
2.4.3、MyRunnable
public class MyRunnable implements Runnable{
@Override
public void run() {
Log.i(Constant.TAG, Thread.currentThread().getName() + “.run()”);
}
}
2.4.4、SaleTicket
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();
}
}
}
}
2.4.5、MainActivity
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();
}
}
2.4.6、布局文件
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
tools:context=“.MainActivity”>
<TextView
android:id=“@+id/tv_mark”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=“MainActivity”
android:textSize=“30sp”
app:layout_constraintLeft_toLeftOf=“parent”
app:layout_constraintRight_toRightOf=“parent”
app:layout_constraintTop_toTopOf=“parent” />
<Button
android:id=“@+id/btn_test_thread”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=“测试Thread”
android:textAllCaps=“false”
android:textSize=“26sp”
app:layout_constraintLeft_toLeftOf=“parent”
app:layout_constraintRight_toRightOf=“parent”
app:layout_constraintTop_toBottomOf=“@+id/tv_mark” />
<Button
android:id=“@+id/btn_test_runnable”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=“测试Runnable”
android:textAllCaps=“false”
android:textSize=“26sp”
app:layout_constraintLeft_toLeftOf=“parent”
app:layout_constraintRight_toRightOf=“parent”
app:layout_constraintTop_toBottomOf=“@+id/btn_test_thread” />
<Button
android:id=“@+id/btn_test_sale”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=“测试卖票”
android:textAllCaps=“false”
android:textSize=“26sp”
app:layout_constraintLeft_toLeftOf=“parent”
app:layout_constraintRight_toRightOf=“parent”
app:layout_constraintTop_toBottomOf=“@+id/btn_test_runnable” />
</androidx.constraintlayout.widget.ConstraintLayout>
3、线程池的应用
========
new Thread的弊端:
-
a.每次new Thread新建对象性能差.
-
b.线程缺乏统一管理,可能无限制新建线程,相互之间竞争,即可能占用过多系统资源导致死机或oom.
-
c.缺乏更多功能,如定时执行、定期执行、线程中断。
3.1、缓存线程池
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,如无可回收,则新建线程。
private void testCache() {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
Log.i(Constant.TAG, Thread.currentThread().getName() + " " + index);
}
});
}
}
3.2、定长线程池
newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
private void testFixed() {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
Log.i(Constant.TAG, Thread.currentThread().getName() + " " + index);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
3.3、单个线程池
newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行。
private void testSingle() {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
Log.i(Constant.TAG, Thread.currentThread().getName() + " " + index);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
3.4、定时线程池
newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行。
private void testScheduled() {
Log.i(Constant.TAG, “开始执行 testScheduled”);
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
// 定时3秒后执行
// scheduledExecutorService.schedule(new Runnable() {
// @Override
// public void run() {
// Log.i(Constant.TAG, “延时3秒执行”);
// }
// },3, TimeUnit.SECONDS);
// 定时3秒后执行,每隔2秒执行一次
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
Log.i(Constant.TAG, “每隔2秒执行一次”);
}
}, 3, 2, TimeUnit.SECONDS);
}
4、异步消息处理机制
==========
当我们学会使用多线程编程以后,就需要思考一个问题:线程之间如何通讯?
4.1、分析
详细了解Android SDK提供的几个线程间通讯的类.
1、Handler
Handler在android里负责发送和处理消息,通过它可以实现其他线程与Main线程之间的消息通讯。
2、Looper
Looper负责管理线程的消息队列和消息循环。
3、Message
Message是线程间通讯的消息载体。两个码头之间运输货物,Message充当集装箱的功能 ,里面可以存放任何你想要传递的消息.
4、MessageQueue
MessageQueue是消息队队列,先进先出,它的作用是保存有待线程处理的消息.
它们四者之间的关系是,在其他线程中调用Handler.sendMsg()方法(参数是Message对象),
将需要Main线程处理的事件添加到Main例的MessageQueue中,Main线程通过MainLooper从消息队列中取出Handler发过来的这个消息时,会回调Handler的handlerMessage()方法。
4.2、用法
4.2.1、send
1、sendEmptyMessage(int)
2、sendMessage(Message)
3、sendMessageAtTime(Message , long)
4、sendMessageDelayed(Message , long)
sendMessage类方法允许你安排一个带数据的Message对象到队列中,等待处理。
4.2.2、post
1、post(Runnable)
2、postAtTime(Runnable, long)
3、postDelayed(Runnable, long)
post方法允许你排列一个Runnable对象到主线程队列中,等待执行。
4.3、总结
1、传递Message。用于接受子线程发送的数据,并用此数据配合主线程更新UI。
在Android中,对于U1的操作通常需要放在主线程中进行操作。如果在子线程中有关于UI的操作,
那么就需要把数据消息作为一个Message对象发送到消息队列中,然后,用Handler中的handleMessge方法处理传过来的数据信息,并操作UI,类似sendMessage(Message msg)
方法实现发送消息的操作。在初始化Handler对象时重写的handleMessage方法来接收Message并进行相关操作。
2、传递Runnable对象。用于通过Handler绑定的消息队列,安排不同操作的执行顺序。Handler对象在进行初始化的时候,会默认地自动绑定消息队列。利用类post方法,可以将Runnable对象发送到消息队列中,按照队列的机制按顺序执行不同的Runnable对象中的run方法.
4.4、补充
4.4.1、View的post()方法
public boolean post(Runnable action) {
//1 View自己创建的Handler
Handler handler;
if (mAttachInfo != null) {
//2 获取View所依附的Thread的Handler
handler = mAttachInfo.mHandler;
} else {
getRunQueue().post(action);
return true;
}
//3 执行post方法 所以View.post方法本质上还是handler.post
return handler.post(action);
}
4.4.2、Activity的runOnUiThread方法
@Override
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
4.5、代码
4.5.1、MainActivity.java
public class MainActivity extends AppCompatActivity {
private Button btn1;
private Button btnTestPb;
private ProgressBar pb;
private int progress = 0;
@SuppressWarnings(“HandlerLeak”)
private final Handler handler = new Handler() {
// 接收消息等待处理
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 1:
btn1.setText(“12345”);
break;
case 2:
String str = (String) msg.obj;
btn1.setText(str);
break;
case 3:
if (progress < 100) {
progress += 10;
pb.setProgress(progress);
handler.sendEmptyMessageDelayed(3, 2000);
}
break;
default:
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn1 = findViewById(R.id.btn_test1);
btnTestPb = findViewById(R.id.btn_test_pb);
pb = findViewById(R.id.pb);
btn1.setOnClickListener(view -> test1());
btnTestPb.setOnClickListener(view -> testTimer());
findViewById(R.id.btn_test_post).setOnClickListener(view -> testPost());
}
private void testPost() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// handler.post(new Runnable() {
// @Override
// public void run() {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
题外话
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料免费分享出来。
【Android学习PDF+学习视频+面试文档+知识点笔记】
【Android思维脑图(技能树)】
知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。
需要的朋友,可以点赞关注+转发”前往免费领取!
e
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// handler.post(new Runnable() {
// @Override
// public void run() {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-VzOOktep-1711048107932)]
[外链图片转存中…(img-AmXkJnYG-1711048107933)]
[外链图片转存中…(img-MXOBkYHX-1711048107934)]
[外链图片转存中…(img-U2uaEbuj-1711048107934)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-c9qwjM8A-1711048107935)]
题外话
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料免费分享出来。
【Android学习PDF+学习视频+面试文档+知识点笔记】
【Android思维脑图(技能树)】
知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。
[外链图片转存中…(img-GlAkvFlx-1711048107935)]
需要的朋友,可以点赞关注+转发”前往免费领取!
希望我能够用我的力量帮助更多迷茫、困惑的朋友们,帮助大家在IT道路上学习和发展~