第三章:android基础(1)-消息处理机制

在进入实战之前,先讲解一些android相关的基础知识,因为这些知识会在后续阶段频繁使用,这小节主要讲解的是android消息处理机制。

基础APP

在讲解之前,我们需要一个最基础的APP,我们使用Android Studio创建一个最简的工程,然后添加一个button,AS工程中app-> res-> layout-> activity_main.xml(text)的内容如下

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context=".MainActivity"
    android:orientation="vertical">


    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/send_mesesage" />
</LinearLayout>

xml文件就不进行讲解了,这里就不进行详细讲解了,总的来说,描述了一个总的界面,和一个按钮。
完成xml文件之后,我们在修改app-> java->com.example.administrator下的MainActivity.java文件,我们的目的比较简单,就是按下按钮的时候,执行一个函数,打印出一条信息即可,我们在AS工程中选中Button之后按shift+F1可以查看Button的使用方法(需要翻墙),编写以下代码:

代码如下:

package com.example.administrator.app_addons_0001_message;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
+   private  Button mButton;
+   private final String TAG = "MessageTest";
+    private  int ButtonCount = 0;
+   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

+          mButton = findViewById(R.id.button);
+          mButton .setOnClickListener(new View.OnClickListener() {
+               public void onClick(View v) {
+                    Log.d(TAG,"Send Message"+"");
                     ButtonCount++;
            }
        });
    }
}

这样,我么编译运行程序之后,每次点击按钮就会打印“Send Message”,则代表实验完成。

android线程

方法一(创建线程)

该处只针对android线程做简单的讲解,什么是线程,现场的作用是什么,这些基础知识,请另外查询其他的资料。在我们初始创建的APP中,其实就是一个线程,该进程中只有一个线程,我们称该线程为主线程,好比如主线程会执行MainActivity.java文件中 protected void onCreate(Bundle savedInstanceState)方法。那么我们还需要其他的线程怎么办呢?一般我们是在主线程中创建其他的线程。为了能够深入的了解android的线程,接下来我们会使用多种方法创建线程
在onCreate函数中添加如下代码:

  +   mythread = new Thread(new MyRunndble(), "MessageTestThread");
  +   mythread.start();

该代码也很好理解,就是创建一个线程,然后开始该线程,如果通过shift+F1查看我们可以知道,其中new MyRunndble()为Runnable是一个接口类型,其中Runnable有一个run方法,该方法为线程的主体循坏方法,那么我们参考Develop(shift+F1查看到的类容),在MainActivity.java中添加如下代码实现该接口:

     private Thread mythread = null;
+    private final String TAG = "MessageTest";    
+ 
+    class MyRunndble implements Runnable {
+
+         @Override
+         public void run() {
+             int count = 0;
+             for (;;){
+                 Log.d(TAG,"MyThread1 " + count);
+                 count ++;
+                 try {
+                     Thread.sleep(3000);
+                 } catch (InterruptedException e) {
+                     e.printStackTrace();
+                 }
+             }
+         }
+     } 

该主体函数我们使用一个无限循环,做一个简单的打印函数,添加以上代码之后,我们运行APP程程序,可以看到,没三秒会出现"MyThread1 x "的打印信息,表示实验成功。但是我们直接使用Runnable去实现该线程的主体,我们的线程中只有一个主体run函数,为了摆脱这种局限性我们可以使用下面的方法去实现线程。

方法二(创建线程)

我们自己编写一个类,该类继承于Thread,在public class MainActivity extends AppCompatActivit类中添加如下代码:

    class MyThread extends Thread
    {
        public void run()
        {
            int count = 0;
            for (;;){
                Log.d(TAG,"MyThread2 " + count);
                count ++;
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
    }

现在我们只重写run方法,后续阶段再添加其他方法,定义这个类之后,我们在onCreate方法中进行实例化并且开始该线程:

+		private MyThread mythread2 = null;
+       mythread2 = new MyThread();
+       mythread2.start();

然后我们编译运行APP可以看到两个线程的打印信息,表示实验成功。

机制框架

在android系统中,线程使用同通信的本质,实质是使用pipe(管道:想了解内部机制,请查询其他资料),一个写一个读。即一个发送消息,一个处理消息,那么我们要做的至少要完成以下三点:

1.创建消息(MessageQueue)
2.发送者(Handler)
3.处理者(Looper)

Looper

前面我们已经通过两种方法,创建了两个线程,分别为mythread = new Thread(new MyRunndble(), “MessageTestThread”),mythread2 = new MyThread(),现在我们把mythread2作为接受者,即Looper,主线程作为发送者即Handler,在我们的APP中,有两个线程为接受者,在主线程发送消息的时候肯定需要指定一个接受者(Looper),那么怎么指定呢?在 class MyThread 中添加如下代码:

+ 		private Looper mLooper;
+       Looper getLooper(){
+            return mLooper;
+       }

在MyThread编写一个getLooper()的方法,主线程需要发送消息给那个次线程,调用MyThread类中的getLooper方法即可,前面提到过Thread的主体循环方法为run方法,我们对他进行重写,代码如下

    public void run()
    {
        super.run();
        Looper.prepare();
        Looper.loop();
    }

首先调用Looper.prepare(),使用(Ctrl+Alt+b)查看prepare()可以找到如下带代码

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

可以知道,调用Looper.prepare()他会创建一个消息队列,该消息队列会把所有接受到消息全部保存下来,然后还掉调用了 Looper.loop(),代码过于臃肿,此处只粘贴部分代码如下:

        for (;;) {
            Message msg = queue.next(); // might block
            final long traceTag = me.mTraceTag;
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            msg.target.dispatchMessage(msg);

,其主要目的是从消息队列中取出消息进行处理,如果纤细队列为空,则进行睡眠等待。现在我们接受者已经实现了。接下来我们实现发送者(Handler)

Handler

在主线程中,即onCreate(Bundle savedInstanceState)方法中,添加如下代码:

        mHandler = new Handler(mythread2.getLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                Log.d(TAG,"get Messsage"+mMessageCount);
                mMessageCount++;
                return false;
            }
        });

实例化一个Handler对象mHandler,并且绑定消息的接受者(Looper),已经一个回调方法Handler.Callback(),该方法会在子线程接收到消息的时候被调用,我们在该处仅仅打印出get Messsage x信息。

Looper优化

前面在class MyThread中,只是实现了简单的功能,但是我们还有许多方面没有考虑到,完整办的代码应该如下

    class MyThread extends Thread
    {
        private Looper mLooper;
        public void run()
        {
            super.run();
            Looper.prepare();
            synchronized (this) {
                mLooper = Looper.myLooper();
                notifyAll();
            }
            mLooper =  Looper.myLooper();
            Looper.loop();
        }
        public  Looper getLooper(){
            if (!isAlive()) {
                return null;
            }

            // If the thread has been started, wait until the looper has been created.
            synchronized (this) {
                while (isAlive() && mLooper == null) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                    }
                }
            }
            return mLooper;
        }
    }

为什么要这样去实现class MyThread呢?我们先看onCreate的代码顺序:

      mythread = new Thread(new MyRunndble(), "MessageTestThread");
      mythread.start();

      mythread2 = new MyThread();
      mythread2.start();

      mHandler = new Handler(mythread2.getLooper(), new Handler.Callback() {

我们先创建次线程,然后开始线程,通过前面的介绍,我们知道Looper的实例化是在次线程中实现的,如果主线线程在次线程没有实例化Looper之前调用了mythread2.getLooper()函数,那么此时发送的消息的接受者Looper则为空,有可能导致程序出错,故此我们在MyThread中的getLooper()方法中,先使用if (!isAlive())判断该Looper 是否有效,如果无效着进入等待状态。同时在run()方法中,调用了 mLooper = Looper.myLooper()实例化之后,再调用notifyAll()唤醒等待。
最后我们在 mButton.setOnClickListener添加如下类容

      mButton.setOnClickListener(new View.OnClickListener() {
               public void onClick(View v) {
                   Log.d(TAG,"Send Message"+ButtonCount);
                   ButtonCount++;
+                  Message msg = new Message();
+                  mHandler.sendMessage(msg);
          }
      });

每次点击按钮的时候,都发送一个消息给次线程处理,
这样我们的代码就完善了,编译运行APP,点击button查看打印我们可以知道:

D/MessageTest: Send Message0
D/MessageTest: get Messsage0

每次点击button,发送一个消息给子线程,子进程接受到消息后进行处理。以上主要参考HandlerThread.java(通过Ctrl+Shift+n可查找)实现。

简易方法

以上的代码是我们参考HandlerThread进行编写,那么我们可不可以直接使用HandlerThread实现呢,当然是可以的,在public class MainActivity添加如下代码:

                mMessageCount++;
                return false;
            }
        });
+	  private HandlerThread mythread3;
+	  mythread3 = new HandlerThread("MessageTestThread3");
+     mythread3.start();
+     mHandler3 = new Handler(mythread3.getLooper());

创建第三个线程接受消息。

       Message msg = new Message();
       mHandler.sendMessage(msg);
+
+      mHandler3.post(new Runnable() {
+          @Override
+          public void run() {
+              Log.d(TAG,"get Messsage for Thread3"+mMessageCount);
+              mMessageCount++;
+           }
+      });

每次点击按钮在发送消息给mHandler2的时候,也发送给mHandler3,mHandler3接受到消息后,执行mHandler3.post的run方法,打印”get Messsage for Thread3 x“信息。
编译运行APP后,每次点击按钮可以同时看到两个子线程的打印的信息。

 D/MessageTest: Send Message0
 D/MessageTest: get Messsage0
 D/MessageTest: get Messsage for Thread31

则代表实验成功。

小节回顾

本小节主要介绍了android基础知识中的消息处理机制,以及线程的使用方法。

猜你喜欢

转载自blog.csdn.net/weixin_43013761/article/details/86771799