安卓多任务实现的基本原理

## 安卓多任务实现的基本原理

### 一.基本概念

>  操作一些耗时操作时候,如I/O读写大文件,数据库操作以及网络下载需要很长时间,为了不阻塞用户界面,出现ANR(应用程序无响应)的响应提示窗口,这个时候我们考虑使用Thread线程来进行解决.                                                                                       

### 二.Android中的进程和线程

> 在Android系统中,如果有一个应用程序组件时第一次被启用,而且这时候,应用程序也没有其他的组件来运行,则Android系统会为应用程序创建一个linux的**进程**,这个Linux进程包含一个**线程**,称为**主线程**或者**UI线程**.

> 当一个组件在被启动时,如果该process已经存在了,那么该组件就直接通过这个process被启动起来,并且运行在这个process的UI线程中.

#### 1.进程

* 默认情况下,同一个应用程序内的所有组件都是运行在同一个进程中的,大部分应用程序都是按照这种方式运行的;

* 在具体应用中,很多时候需要通过在manifest文件中进行设置,通过修改四大组件在Manifest.xml的代码块中的android:proess属性指定组件运行的进程,使其运行在不同的process中。

* <application>中的元素也支持android:proess属性,用于指定所有组件的默认进程。

#### 2.进程的重要性层次结构

**进程有5中层次,按其重要程度递减分为**

> 1.前台进程(用户当前操作所必须的进程)

* 拥有一个正在与用户交互的Activity

* 拥有一个Service,这个Service绑定了某一个正在与用户交互的Activity

* 拥有一个前台Service

* 拥有一个Service且它正在执行声明周期回调方法

* 拥有一个BroadcastReceiver且这个BroadcastReceiver正在执行onRece方法

> 2.可见进程

* 没有任何前台组件,但是仍然会影响用户在屏幕上所见内容的进程

* 拥有一个可见但是不可与用户交互的Activity

* 拥有一个Service,这个Service绑定了一个可见但是不可与用户进行交互的Activity

> 3.服务进程

* 由startService()方法启动的Service进程,虽然不直接和所见内容关联,但是会执行一些用户关心的操作,例如后台播放音乐或者下载数据等等

* 若系统不足以维持前台进程和可见进程,才会牺牲服务进程的空间

> 4.后台进程

* 包含用户不可见的Activity的进程,这些进程对用户体验没有直接影响,可以随时在任意时间终止它们,以回收内存资源.

* 系统通过LRU(最近最少使用)列表进行多个后台进程的管理,确保最近使用的Activity最后被终止

> 5.空进程

* 进程不含有任何应用组件,该进程主要作用是缓存,以改善在此进程中运行组件的启动时间。

* 系统会经常终正此类进程

#### 3.线程

> Android是单线程模型,我们创建的Service、Activity以及Broadcast均是在一个主线程处理,这里我们可以理解为uI线程。(应用程序是一个默认的**单线程单任务程序**)

> Ul Thread中运行着许多重要的逻辑,如系统事件处理,用户输入事件处理,ul绘制,Service,Alarm等

> 我们编写的代码穿插在主线程的逻辑中,比如对用户触摸事件的检测和响应,对用户输入的处理,自定义View的绘制等。如果我们插入的代码比价耗时,如网络请求或数据库读取,就会阻塞uI线程其他逻辑的执行,从而导致界面卡顿。

> 如果卡顿时间超过5秒,系统就会报ANR错误。所以,执行耗时的操作,我们需要另起线程执行。

> 在新线程执行完耗时的逻辑后,往往需要将结果反馈给界面,进行uI更新。Android的ul toolkit不是线程安全的,不能在非uI线程进行uI的更新

> **所有对界面的更新必须在uI线程进行**

> 安卓的单线程模式遵从两个原则

>

> * 1.不要阻塞UI进程

> * 2.不要在UI线程之外访问UI组件

> 创建线程:  基础操作都在UI线程中运行,耗时操作可以创建新的线程去完成

* 继承Thread类

* 实现Runnable接口

> **安卓提供的四种常用的操作多线程的方式,分别是:**

* Handle + Thread

* AsyncTask

* ThreadPoolExecutor

* IntentService

————————————————————————————————————————————————————————————————————

### 三、实现多任务

#### 1.多任务的实现原理

* **在Android中,我们把除uI线程外的,其他所有的线程都叫做工作线程,也就是说Android只会存在两种线程:UI主线程(ul thread)和工作线程(work thread)**

* 我们把耗时的操作放在工作线程中去做。操作完成后,再通知UI主线程做出相应的响应。

* 这就需要掌握线程间通信的方式。在Android中提供了两种线程间的通信方式:

  * AsyncTask机制

  * Handler机制

##### (1)使用AsyncTask

* AsyncTask是Android框架提供的**异步处理**的辅助类,它可以实现耗时操作仕具他线程执行,而处理结果在Main线程执行

* 它屏蔽掉了多线程和Handler的概念,进行了较高程度的封装。

* 使用AsyncTask的代码很容易被理解,因为他们都有一些具有特定职责的方法,如:预处理的方法onPreExecute,后台执行任务的方法dolnBackground,更新进度的方法publishProgress,返回结果的方法onPostExecute等等

##### (2)Handle机制

* Handler机制是通过消息队列进行通信的机制,通过使用Handler,LooperMessageQueue,和Message这几个类协调来完成

  * Handler:在android里负责发送和处理消息,通过它可以实现其他线程与Main线程之间的消息通讯

  * Looper:负责管理线程的消息队列和消息循环

  * MessageQueue:消息队列,先进先出,它的作用是保存有待线程处理的消息

#####    (3)Handler类

* UI主线程在创建时会被自动创建一个消息队列和消息循环,主线程的Looper通过创建一个Handler对象,对外界提供了访问消息队列的渠道

  * 主线程通过Handler.handleMessage()读取消息队列中的消息

  * 工作线程通过方法发送消息到主线程的消息队列**Handler.sendMessage()    Handler.post()**

————————————————————————————————————————————————————————————————

### 四、.Android实现多线程的两种操作模式

#### 1Android有两种方式实现多线程操作UI:

* 第一种是创建新线程Thread,用handler负责线程间的通信和消息。

* 第二种方式AsyncTask异步执行任务

#### 2.使用Handler实现多任务

在新的线程中调用主线程中的Handler的postXX和sendmessage方法来实现与主线程进行通信

**使用post方法实现多任务的主要步骤**:

* 创建一个Handler对象;

* 将要执行的操作卸载线程对象的run方法中;

* 使用post方法运行线程对象

* 如果需要循环执行,需要在线程对象的run方法中再次调用post方法

#### 3.Handler机制实现异步任务demo

* xml文件中添加一个TextView

  ```xml

  <?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=".AsynchronousTask.HandlerAsyTaskActivity">


      <TextView

          android:id="@+id/textview"

          android:layout_width="match_parent"

          android:layout_height="wrap_content"

          android:text="hello"

          android:textAllCaps="false"

          tools:ignore="MissingConstraints" />



  </androidx.constraintlayout.widget.ConstraintLayout>

  ```

* java文件 编辑代码

  ```java

  package com.example.handleractivity.AsynchronousTask;


  import androidx.annotation.NonNull;

  import androidx.appcompat.app.AppCompatActivity;


  import android.os.Bundle;

  import android.os.Handler;

  import android.os.Message;

  import android.widget.TextView;


  import com.example.handleractivity.R;


  public class HandlerAsyTaskActivity extends AppCompatActivity {


      private final static int TEST = 1;


      private TextView textView;


      //这是自己创建的Handler,实现异步任务,这里用来更改UI

      private Handler mHandler = new Handler(){

          @Override

          public void handleMessage(@NonNull Message msg) {

              textView.setText("这是工作线程文本");

          }

      };



      @Override

      protected void onCreate(Bundle savedInstanceState) {

          super.onCreate(savedInstanceState);

          setContentView(R.layout.activity_handler_asy_task);


          textView = findViewById(R.id.textview);

  //        textView.setText("这是一段文本");

          new ActivityThread().start();

      }


      /*

      这是一个线程不安全的行为

      */

      class ActivityThread extends Thread{

          @Override

          public void run() {

              try {

                  Thread.sleep(3000);

              } catch (InterruptedException e) {

                  e.printStackTrace();

              }

  //            textView.setText("这是工作线程文本");


              //使用handler发送消息至消息队列

              Message message = new Message();

              message.what = TEST;

              mHandler.sendMessage(message);

          }

      }

  }

  ```

> 我们设置一个线程,让其休眠3秒钟,然后执行 textView.setText("这是工作线程文本")操作,三秒之后程序会崩溃。

>

> 原因是: 不能在除了UI线程之外的线程里边进行更改UI的操作。

那怎么才能实现呢?

> 使用Handler实现异步任务,自己新建一个Handler,然后在工作线程中使用handler来传递信息。handler里面进行更改UI的操作,就可了

### 五、Handler机制方法调用

#### 1.使用Runnable和Handler实现五秒钟之后出现弹窗

```xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout 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=".HandlerPost.HandlerPostActivity">

    <Button

        android:id="@+id/btnShowToast"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="开启Toast"/>

    <TextView

        android:text="你好"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"/>

</LinearLayout>

```

```java

public class HandlerPostActivity extends AppCompatActivity implements View.OnClickListener{

    //写一个Handler

    Handler countHandler = new Handler();

    /*线程1:启动一个Toast显示线程*/

    Runnable mRunToast = new Runnable() {

        @Override

        public void run() {

            Toast.makeText(HandlerPostActivity.this,"hello Toast",Toast.LENGTH_SHORT).show();

        }

    };

    private Button mbtnShowToast;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_handler_post);

        mbtnShowToast = findViewById(R.id.btnShowToast);

        mbtnShowToast.setOnClickListener(this);

    }

    //添加事件监听

    @Override

    public void onClick(View v) {

        switch (v.getId()) {

            case R.id.btnShowToast:

                /**

                * SystemClock.uptimeMillis()表示开机到当前的一个累计时间

                */

                countHandler.postAtTime(mRunToast, SystemClock.uptimeMillis()+5*1000);

                break;

        }

    }

}

```

#### 2.开辟一个工作线程配合Handler实现计数器的功能

```xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout 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=".HandlerPost.HandlerPostActivity"

    android:orientation="vertical">

    <Button

        android:id="@+id/btnStart"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="启动"/>

    <Button

        android:id="@+id/btnStop"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="关闭"/>

    <Button

        android:id="@+id/btnShowToast"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="开启Toast"/>

    <TextView

        android:id="@+id/tvCount"

        android:text="你好"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"/>

</LinearLayout>

```

```java

public class HandlerPostActivity extends AppCompatActivity implements View.OnClickListener {

    Handler countHandler = new Handler();

    int count = 0;

    /*线程1:启动一个Toast显示线程*/

    Runnable mRunToast = new Runnable() {

        @Override

        public void run() {

            Toast.makeText(HandlerPostActivity.this, "hello Toast", Toast.LENGTH_SHORT).show();

        }

    };

    /*线程2:文本区计数器线程*/

    Runnable mRunCount = new Runnable() {

        @Override

        public void run() {

            textCount.setText("count: " + String.valueOf(count++));

            //再调用一次,以此形成一个类似于循环的操作,一秒加一次

            countHandler.postDelayed(mRunCount, 1000);

        }

    };

    private Button mbtnShowToast;

    private TextView textCount;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_handler_post);

        mbtnShowToast = findViewById(R.id.btnShowToast);

        mbtnShowToast.setOnClickListener(this);

        findViewById(R.id.btnStart).setOnClickListener(this);

        findViewById(R.id.btnStop).setOnClickListener(this);

        textCount = findViewById(R.id.tvCount);

    }

    //添加事件监听

    @Override

    public void onClick(View v) {

        switch (v.getId()) {

            case R.id.btnShowToast:

                /**

                * SystemClock.uptimeMillis()表示开机到当前的一个累计时间

                */

                countHandler.postAtTime(mRunToast, SystemClock.uptimeMillis() + 5 * 1000);

                break;

            case R.id.btnStart:

                //表示推迟一秒执行

                countHandler.postDelayed(mRunCount, 1000);

                break;

            case R.id.btnStop:

                //移除回调的过程

                countHandler.removeCallbacks(mRunCount);

                break;

        }

    }

}

```

运行效果;

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210616152014880.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3R3ZW50eV9mb3VyXzc=,size_16,color_FFFFFF,t_70#pic_center)

### 六、AsyncTask实现多任务

* 使用Handler类来在子线程中更新UI线程总会启动一些匿名的子线程,太多的子线程给系统带来了巨大的负担

* Andoroid提供了一个工具类AdyncTask,来实现异步执行任务

* AsyncTask是抽象类,具有三种泛型:Params、Progress、Result

  * **Params: **表示启动任务执行的参数,比如HTTP请求的URL

  * **Progress:** 表示后台任务执行的百分比

  * **Result: **表示后台执行任务最终返回的结果,比如String,Integer等

* 通过继承一个AsyncTask类定义一个异步任务类

* Android提供一个让程序员编写后台操作更为容易和透明AsyncTask,使得后台线程能够在UI主线程外进行处理

* 使用AsyncTask,不需要自己来写后台线程,无需终结后台线程,只需要创建AsyncTask类,并实现其中的抽象方法以及重写某些方法

> 下面是一个实例,点击按钮,启动进度条。进度条加载完毕,把“执行完毕”显示在文本上

#### 1.xml文件

```xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout 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"

    android:orientation="vertical"

    tools:context=".AsynchronousTask.AsyncTaskDemo">

    <Button

        android:id="@+id/btn"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="Press it"

        android:textAllCaps="false" />

    <TextView

        android:id="@+id/txt"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_gravity="center"

        android:textSize="30sp"

        android:text="hello world" />

    <ProgressBar

        android:id="@+id/progressbar"

        style="@style/Widget.AppCompat.ProgressBar.Horizontal"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:visibility="invisible"

        android:layout_marginTop="50dp" />

</LinearLayout>

```

#### 2.java文件

```java

public class AsyncTaskDemo extends AppCompatActivity implements View.OnClickListener {

    private final static String TAG = "AsyncTaskDemo";

    private Button mBtn;

    private ProgressBar progressBar;

    private TextView mTxt;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_async_task_demo);

        mBtn = findViewById(R.id.btn);

        mBtn.setOnClickListener(this);

        progressBar = findViewById(R.id.progressbar);

        mTxt = findViewById(R.id.txt);

    }

    @Override

    public void onClick(View v) {

        TimeTickLoad timeTickLoad = new TimeTickLoad();

        timeTickLoad.execute(1000);

    }

    class TimeTickLoad extends AsyncTask<Integer,Integer,String>{

        @Override

        protected void onPreExecute() {

            Log.i(TAG, "onPreExecute");

            progressBar.setVisibility(ProgressBar.VISIBLE);

            super.onPreExecute();

        }

        @Override

        protected void onPostExecute(String s) {

            Log.i(TAG, "onPostExecute");

            super.onPostExecute(s);

            mTxt.setText(s);

        }

        @Override

        protected void onProgressUpdate(Integer... values) {

            mTxt.setText("onProgressUpdate");

            Log.i(TAG, "onProgressUpdate");

            //更新进度条

            progressBar.setProgress(values[0]);

            super.onProgressUpdate(values);

        }

        /**

        这个方法是使用AsyncTask必须进行覆写的一个方法,耗时的操作在此方法内执行

        */

        @Override

        protected String doInBackground(Integer... integers) {

            //这里不能撰写UI相关操作

            Log.i(TAG, "hello");

//            mTxt.setText("doInBackground");

            for (int i = 0; i < 10; i++) {

                //执行此操作会自动调用onProgressUpdate()

                publishProgress(i*10);

                try {

                    Thread.sleep(integers[0]);

                }catch (Exception e){

                    e.printStackTrace();

                }

            }

            return "执行完毕";

        }

    }

}

```

> AsyncTask的周期:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210616152048240.png#pic_center)

**从打印的日志可以看出,首先运行的是onPreExecute()方法,然后运行的是doInBackground(),最后运行的是onPostExecute**

> 运行效果:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210616152057393.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3R3ZW50eV9mb3VyXzc=,size_16,color_FFFFFF,t_70#pic_center)

#### 3.实现步骤

* 使用execute方法出发异步任务的执行

* 使用onPreExecute()表示执行预处理 : 在本例中实现绘制一个进度条控件

* 使用doInBackground()用于执行较为费时的操作:在本例中就是计算进度

  **这个方法是AsyncTask的关键,必须进行覆写**

* 使用onProgressUpdate()对进度条控件根据进度值做出具体的响应

* 使用onPostExecute()可以对后台任务的结果做出处理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容