Android每天一个知识点+Demo—异步消息机制实现

一 前言

 Handler的由来

(1)首先为什么需要Handler呢?                 因为UI更新只能在UI线程。 
(2)那为什么只能在UI线程更新UI呢?        因为Android是单线程模型。 
(3)那为什么Android是单线程模型?         因为如果任意线程都可以更新UI的话,线程安全问题处理起来会相当麻烦复杂,所以就规定了Android是单线程模型,只允许在UI线程更新UI操作。

 

二 异步消息的方式与Demo

1 handler+Message+Looper

        Android在设计的时候,就封装了一套消息创建、传递、处理机制,如果不遵循这样的机制就没有办法更新UI信息,就会抛出异常。 Handler就是是Android提供的用来更新UI的一套机制,也是一套消息处理机制,我们可以通过它发送消息,也可以通过它处理消息。

(1) 相关介绍

Handler:

  Handler 顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用 Handler 的 sendMessage()方法,而发出的消息经过一系列地辗转处理后,最终会传递到 Handler 的 handlerMessage()方法中。

Message:

  Message 是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。通常使用 Message 的 what 字段携带命令,除此之外还可以使用 arg1 和arg2 字段来携带一些整形数据,使用 obj 字段携带一个 Object 对象。

MessageQueue:

  MessageQueue 是消息队列的意思,它主要用于存放所有通过 Handler 发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个 MessageQueue 对象。

Looper:

  Looper 是每个线程中的 MessageQueue 的管家,调用 Looper 的 loop() 方法后,就会进入到一个无限循环当中,然后每当发现 MessageQueue 中存在一条消息,就会将它取出,并传递到 Handler 的 handleMessage() 方法中。每个线程中也只会有一个 Looper 对象。

(2) 运行机制流程

                                                 

        首先需要在主线程当中创建一个Handler对象,并重写handleMessage()方法。

        然后当子线程中需要进行UI操作的时,就创建一个Message对象,并通过Handler将这条消息发送出去。

       之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MesssageQueue中取出待处理消息,最后发回Handler的HandleMessage()方法中。

       由于Handler是在主线程中创建的,所以此时handleMessage()方法中的代码页会在主线程中运行,于是我们在这里就可以进行UI操作了。

(3) Demo

package com.demo.handler; 
 
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity implements OnClickListener {
 
	protected static final int UPDATE_TEXT = 1;
	private Button button;
	private TextView textView;
	//创建handler对象,并重写父类handleMessage方法
	private Handler handler = new Handler(){
		//处理传递过来的消息
		public void handleMessage(android.os.Message msg) {
			switch (msg.what) {
			case UPDATE_TEXT:
				//在主线程里更新UI操作
				textView.setText("我要更新了");
				break;
			}
		};
	};
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		button = (Button) findViewById(R.id.btn);
		textView = (TextView) findViewById(R.id.text);
		button.setOnClickListener(this);
	}
 
	@Override
	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.btn:
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					Message message = new Message();
					message.what = UPDATE_TEXT;
					handler.sendMessage(message);//将message对象发送出去
				}
			}).start();
			break;
		}
		
	}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="点击修改内容" />
    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="我要被改动了" />
</LinearLayout>

(4) 其它

另外除了发送消息之外,我们还有以下几种方法可以在子线程中进行UI操作:

              Handler的post(Runnable)方法

              View的post(Runnable)方法

              Activity的runOnUiThread()方法

2 AsyncTask

(1) AsyncTask介绍

       AsyncTask是由线程池 + 工作线程 + Handler进行构建的一个异步任务工具类。

     AsyncTask本质上是一个封装了线程池、工作线程和Handler的异步框架,主要是执行异步任务,由于AsyncTask里面封装了Handler,所以它可以在主线程和子线程之间灵活的切换。

     AsyncTask 背后的实现原理也是基于异步消息处理机制的,只是 Android 做了很好的封装而已。我们并不需要去考虑异步消息处理机制,也不需要专门使用一个 Handler 来发送和接收消息,只需要调用一下 publishProgress()方法就可以轻松地从子线程切换到 UI 线程了。

(2) AsyncTask的机制原理

1) 线程池:

     为了在处理多个任务时,避免创建多个线程带来内存开销,内部维护了一个线程池,管理每个线程。内部的线程池通过ThreadPoolExecutor来动态分配固定大小的核心工作线程。这样所有的异步任务都会放到线程池,并维护每个工作线程。

2) 工作线程:

        该线程用于执行一个耗时的操作,内部通过FutureTask接收一个WorkerRunnable接口,并在接口的call方法中,执行异步任务,此时调用AsyncTask类的doInBackground()方法,做耗时操作。由于FutureTask类实现了FutureRunnable接口,通过这个接口,在它的run方法中,执行任务。

3) Handler消息机制更新UI:

      当FutureTask工作线程任务执行完成以后,把结果交给Handler,Handler通过发送消息,在主线程中接收消息,并调用AsyncTask类的onPostExecute(Result result)方法,来处理结果。

(3)  AsyncTask 的基本用法

        首先来看一下 ,由于 AsyncTask 是一个抽象类,所以如果我们想使用它,就必须创建一个子类去继承它。在继承时我们可以为 AsyncTask 类指定三个泛型参数。

三个参数

       Params:启动任务执行的输入参数,比如HTTP请求的URL。
       Progress:后台任务执行的百分比。
       Result:后台任务执行最终返回的结果,比如String,Integer等。

/**
 * AsyncTask泛型参数解释
 * Void 表示在执行AsyncTask的时候不需要传入参数给后台任务
 * Integer 表示使用整形数据来作为进度显示单位
 * Boolean 表示使用布尔类型数据来反馈执行结果
 */
class MyTask extends AsyncTask<Void, Integer, Boolean> {

    @Override
    protected Boolean doInBackground(Void... params) {
        return null;
    }
}

四个方法

        onPreExecute():一般会在 UI Thread 中执行。用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。

       doInBackground(Params…):这个方法中的所有代码都会在子线程 Worker Thread 中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果进行返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用下面这个 publishProgress(Progress...) 方法来完成。

       onProgressUpdate(Progress…):当在后台任务中调用了publishProgress(Progress…)方法后,这个方法就很快会被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。

       onPostExecute(Result):在 UI Thread 中执行。当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如弹出Toast提醒任务执行的结果,以及关闭掉进度条对话框等。

       可以知道,使用 AsyncTask ,则可以在onPreExecute()进行一些界面上的初始化操作,在 doInBackground() 方法中去执行具体的耗时任务,在 onProgressUpdate() 方法中进行 UI 操作,在 onPostExecute()方法中执行一些任务的收尾工作。

       而如果想要启动这个任务,则只需要  new MyTask.execute();

(4) AsyncTask注意事项

     1)内存泄漏(类似handler,只是AsyncTask销毁方法为cancel())

     2)生命周期(不会随着Activity的销毁而销毁,Activity销毁前若doInBackground()中耗时未进行完会出现问题)

     3)并行or串行(一般做串行,并行不稳定)

     4)但是AsyncTask并不适合进行特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池

     5)AsyncTask的实例必须在UI thread中创建,启动实例的execute方法必须在UI thread中调用

     6)一个AsyncTask实例只能被执行一次,多次调用时将会出现异常

(5) Demo

public class MyActivity extends Activity
{
    private Button btn;
    private TextView tv;
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        btn = (Button) findViewById(R.id.start_btn);
        tv = (TextView) findViewById(R.id.content);
        btn.setOnClickListener(new Button.OnClickListener(){
            public void onClick(View v) {
                update();
            }
        });
    }
    private void update(){
        UpdateTextTask updateTextTask = new UpdateTextTask(this);
        updateTextTask.execute();
    }
 
    class UpdateTextTask extends AsyncTask<Void,Integer,Integer>{
        private Context context;
        UpdateTextTask(Context context) {
            this.context = context;
        }
 
        /**
         * 运行在UI线程中,在调用doInBackground()之前执行
         */
        @Override
        protected void onPreExecute() {
            Toast.makeText(context,"开始执行",Toast.LENGTH_SHORT).show();
        }
        /**
         * 后台运行的方法,可以运行非UI线程,可以执行耗时的方法
         */
        @Override
        protected Integer doInBackground(Void... params) {
            int i=0;
            while(i<10){
                i++;
                publishProgress(i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
            }
            return null;
        }
 
        /**
         * 运行在ui线程中,在doInBackground()执行完毕后执行
         */
        @Override
        protected void onPostExecute(Integer integer) {
            Toast.makeText(context,"执行完毕",Toast.LENGTH_SHORT).show();
        }
 
        /**
         * 在publishProgress()被调用以后执行,publishProgress()用于更新进度
         */
        @Override
        protected void onProgressUpdate(Integer... values) {
            tv.setText(""+values[0]);
        }
    }
}

 

3 HandlerThread

(1) HandlerThread 介绍

       HandlerThread是Google帮我们封装好的框架,可以用来执行多个耗时操作,而不需要多次开启线程。其内部实现为:

                           Handle + Thread + Loop

      HandlerThread 继承于 Thread,所以它本质就是个 Thread。与普通的 Thread 的差别就在于,它有个 Looper 成员变量。这个 Looper 其实就是对消息队列以及队列处理逻辑的封装。与普通线程不同的地方就是内部有loop。因为我们如果想在子线程中使用Handle,就必须在子线程中创建loop对象。 使用Looper.prepare();来创建当前线程的loop,接着使用 Looper.loop();来开启循环,这样我们才能在线程中创建Handle。而Handle的内部就是这样实现的。

     HandlerThread集合了Thread和Handler,集成了Looper和MessageQueue。当启动HandlerThread时,会同时生成Looper和MessageQueue,然后等待消息处理。它只是帮你调用了 Looper.prepare() 方法和 Looper.loop() 方法而已。也就是说如果你一个类继承了 HandlerThread,你可以像在主线程那样使用 Handler。

(2) HandleThread 使用

1) 创建HandlerThread的实例对象

HandlerThread handlerThread = new HandlerThread("myHandlerThread");

2) 启动我们创建的HandlerThread线程

handlerThread.start();

3) 将我们的handlerThread与Handler绑定在一起,就是将线程的looper与Handler绑定在一起,代码如下:

mThreadHandler = new Handler(mHandlerThread.getLooper()) {
    @Override
    public void handleMessage(Message msg) {
        checkForUpdate();
        if(isUpdate){
            mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
        }
    }
};

(3) HandlerThread 使用场景

        HandlerThread 所做的就是在新开的子线程中创建了 Looper,那它的使用场景就是 Thread + Looper 使用场景的结合,即:在子线程中执行耗时的、可能有多个任务的操作。比如说多个网络请求操作,或者多文件 I/O 等等。使用 HandlerThread 的典型例子就是 IntentService。 

      当我们需要一个工作线程,而不是把它当作一次性消耗品,用过即废的话,就可以使用它。

(4) HandlerThread 特点

       1)它就是一个帮我们创建 Looper 的线程,让我们可以直接在线程中使用 Handler 来处理异步任务。HandlerThread 继承自Thread,内部封装了Looper。

      2)首先Handler和HandlerThread的主要区别是Handler与Activity在同一个线程中,HandlerThread与Activity不在同一个线程,而是别外新的线程中(Handler中不能做耗时的操作)。
      3)通过将HandleThread的Loop对象传递给Handle对象,可以在handleMessage()中执行异步任务。 
      4)与线程池并发不同,HandleThread内部只有一个线程,是一个串行的队列。 
      5)优点是不会堵塞,减少了对性能的消耗。缺点是不能同时进行多任务处理,需等待处理,效率较低。

(5) Demo

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
/**
 * 在主线程main中创建handler对象,创建消息,并用handler向工作线程发送消息,在工作线程中处理消息;
 * 应用环境:比如下载;
 * @author gaohong
 *
 */
public class HandlerThreadActivity extends Activity {
	HandlerThread thread;
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        //创建工作线程并启动(工作线程带消息队列)
        thread=new HandlerThread("workThread");
        thread.start();
        Looper looper=thread.getLooper();
        
        //创建Handler对象并与工作线程的消息队列关联
        Handler handler=new Handler(looper){
        	@Override
        	public void handleMessage(Message msg) {
        		// TODO Auto-generated method stub
        		System.out.println("在"+Thread.currentThread().getName()+"中处理消息");
        		System.out.println("msg.obj="+msg.obj);
        	}
        };
        
        //创建Message对象
        Message msg=Message.obtain();
        msg.obj="在"+Thread.currentThread().getName()+"线程中发送消息";
        handler.sendMessage(msg);
    }
    
   @Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		thread.quit();
	}
}

 

4 IntentService 

(1) IntentService介绍     

        Service是作为后台服务运行再程序中的。但是Service他依然是运行在主线程中的,所以我们依然不能在Service中进行耗时操作。所以当我们在Service处理时,我们需要在Service中开启一个子线程,并且在子线程中运行。当然为了简化我们的操作,在Android中为我们提供了IntentService来进行这一处理。


     IntentService具有Service一样的生命周期,也提供了后台线程中的异步处理机制。IntentService可以看做是Service和HandlerThread的结合,完成任务后自动停止,适合需要在工作线程处理ui无关任务的场景。

       IntentServie继承自Service并处理异步请求的抽象类,用于执行后台耗时任务,优先级比单纯的线程高很多,适合执行一些高优先级任务,不容易被系统杀死,内部封装了HanlerThread和Handler。 封装的一个work-thread,处理接收到的Intent,它可以处理多个请求,但一次只能处理一个。如果启动IntentService多次,每一个耗时操作会以工作队列的方式在inentService的onHandleIntent回调方法中执行,依次执行,使用串行方式,执行完自动结束。

(2) IntentServie特点
     1) IntentService会自动启动一个WorkThread来处理后台耗时任务,它可以用于在后台执行耗时的异步任务,当任务完成后会自动停止。
     2) 后台耗时任务处理完成以后,会自动关闭Service(这样只需要启动IntentService即可,后面就不用关注IntentService的关闭了) 

     3) 拥有较高的优先级,不易被系统杀死(继承自Service的缘故),因此比较适合执行一些高优先级的异步任务

     4) 内部通过HandlerThread和Handler实现异步操作创建IntentService时,只需实现onHandleIntent和构造方法,onHandleIntent为异步方法,可以执行耗时操作。

    5) 当然IntentService也有其限制,多个任务会放到任务队列,顺序执行。所以任务会有先后顺序的限制,如果要几个任务同时并发处理,就不适合使用IntentService了

(3) Demo

public class MyIntentService extends IntentService {
    private static final String TAG = MyIntentService.class.getSimpleName();
    private boolean isRunning;
    private int count;

    public MyIntentService() {
        super(TAG);
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        String action = intent.getStringExtra("task_action");
        Log.e(TAG, "onHandleIntent: " + action);
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (action.equals("task_action111")) {
            Log.e(TAG, "onHandleIntent: " + action);
        }
    }

    @Override
    public void onDestroy() {
        Log.e(TAG, "onDestroy: ");
        super.onDestroy();
    }
}

 

三 总结

(1) 它们之间的关系                                                        

                                    

(2) 它们之间的区别

Handler: 它主要有两个用途,一是为了在将来某个时间点处理一个消息或者执行一个任务;二是将一个任务放入队列,以便它可以在另外的线程中执行。

AsyncTask: 封装了线程池和Handler,为 UI 线程与工作线程之间进行快速切换提供一种便捷机制。适用于当下立即需要启动,但是异步执行的生命周期短暂的使用场景。这个类的设计目的很明确,就是为了“执行一个较为耗时的异步任务(最多几秒钟),然后更新界面”。

                       Handler + 线程池 + 工作线程 

HandlerThread: 一个已经拥有了Looper的线程类,内部可以直接使用Handler。为某些回调方法或者等待某些任务的执行设置一个专属的线程,并提供线程任务的调度机制。使用场景就是 Thread + Looper 使用场景的结合,即在子线程中执行耗时的、可能有多个任务的操作。比如说多个网络请求操作,或者多文件 I/O 等等。

                       Handler + Thread + Loop

IntentService: 适合于执行由 UI 触发的后台 Service 任务,并可以把后台任务执行的情况通过一定的机制反馈给 UI。提供了后台线程中的异步处理机制,IntentService可以看做是Service和HandlerThread的结合,完成任务后自动停止,适合需要在工作线程处理ui无关任务的场景

                       HandlerThread+ Service

猜你喜欢

转载自blog.csdn.net/weixin_42093428/article/details/80822610