Android异步任务与多线程

Posted songzi1228

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android异步任务与多线程相关的知识,希望对你有一定的参考价值。

相关资料:

https://www.bilibili.com/video/BV1m4411r73w?p=3https://www.bilibili.com/video/BV1m4411r73w?p=3https://www.bilibili.com/video/BV1m4411r73w?p=3

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 的四种场景:

  1. Service TimeOut:  service 未在规定时间执行完成:前台服务 20s,后台 200s

  2. BroadCastQueue TimeOut: 未在规定时间内未处理完广播:前台广播 10s 内, 后台 60s 内

  3. ContentProvider TimeOut:  publish 在 10s 内没有完成

  4. 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、线程池的应用

4、异步消息处理机制

5、异步任务

6、设计自己的图片轮播器

以上是关于Android异步任务与多线程的主要内容,如果未能解决你的问题,请参考以下文章

csharp 线程和异步任务片段

Android 我应该在异步任务中将片段作为弱引用传递吗?

Python基础入门 - 内存管理与多线程

Android中的异步任务不使用AsyncTask

异步任务片段背景数据

Android - 为列表视图填充适配器的异步任务