Android异步消息处理机制

Android多线程

1 多线程的使用

  • Android 主线程: Android主线程也可以称为UI线程,其实就是ActivityThread,该主线程有点类似于Java中的main函数。Android中的主线程用于处理四大组件的稳定运行和一系列事件的处理,比如系统事件,用户输入事件,视图的渲染等等。因此,为了减轻主线程的负担,Android中将一些任务量大的工作单独开辟线程去跑。
  • 子线程:为了减轻主线程的负担,将一些耗时的操作放在子线程中运行,比如访问数据库等等的一些耗时操作,如果将这些耗时操作放在主线程中运行,会发生阻塞,如果阻塞的时间过长(通常大于5秒),会导致主线程无法响应,这时候Android系统会弹出ANR提示。

2 异步消息处理

通过上述解释可以清楚的知道,在处理耗时任务时,为了避免出现ANR,必须要将这些耗时任务放在子线程中去执行。但是,如果子线程中得到结果与UI控件关联,这时候如果直接在子线程中更新UI,Android系统同样会弹出ANR提示,因为UI控件是不安全的,为什么说Android的UI控件是不安全的?

首先得理解什么是线程安全,什么是线程不安全。线程安全就是在多个线程共享一个数据时,当某一个线程访问该数据时,通过加锁机制进行保护,不允许其它线程进行访问,直至访问结束后,才会安排其它线程进行访问,这样会使得被共享的数据在某一段时间内只会被一个线程访问,不会导致数据不一致,但是,可以看出通过这种线程安全的方式肯定会影响数据的访问效率。线程不安全是不采用加锁机制,允许多个线程同时访问共享数据,这种情况下就会导致共享数据被任意更改,但是在线程不安全情况下,数据的访问效率要高于在线程安全情况下。

考虑到系统的整体性能,Android选择了线程不安全,为了进一步避免线程不安全带来的不安全问题,Android系统规定只能在UI线程中更新UI控件的状态。所以,在子线程中更新UI控件状态,Android系统会弹出ANR。

总结上述可以得知会有以下两种情况导致出现ANR:

  • 主线程中执行耗时任务
  • 子线程中直接操作UI控件

在很多情况下,子线程的运行结果与UI控件的状态有关。这时候,如果想要在子线程中达到更新UI控件的目的,同时又的避免出现ANR,就需要采用异步消息处理机制,这是Android特有的一种线程间通信机制。

2.1 Handler+Thread

  • 工作原理:Android的异步消息处理机制就是Handler消息机制。这种消息机制简要分为如下几个部分:

    • 首先主线程ActivityThread在启动的时候,会默认创建一个Looper,有了这个Looper,才能正常创建Handler对象。如果自己在单独的子线程中创建一个Handler对象之前没有创建Looper,会发生什么情况呢,下面是Handler的构造方法,可以看出当Looper为null时,会报异常提示:Can’t create handler inside thread that has not called Looper.prepare()。那么主线程创建的时候,什么时候创建的Looper的呢?透过下面的main方法可以发现执行了这一条语句:Looper.prepareMainLooper(),而这条语句的目的就是创建一个Looper。总结上述得知,主线程中创建Handler对象时,不需要创建Looper,但是如果在子线程创建Handler时,需要提前创建Looper。
    //Handler的构造方法
    public Handler() {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
        mLooper = Looper.myLooper();
        //如果没有创建Looper,则报异常
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = null;
    }
    
    //主线程的main方法
    public static void main(String[] args) {
        SamplingProfilerIntegration.start();
        CloseGuard.setEnabled(false);
        Environment.initForCurrentUser();
        EventLogger.setReporter(new EventLoggingReporter());
        Process.setArgV0("");
        //创建Looper
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        AsyncTask.init();
        if (false) {
            Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    
    • 通过下述源码发现,在构造Looper的同时会创建一个MessageQueue,MessageQueue是一个消息队列,其用于将接受到的消息形成一个队列,并通过提供入列和出列的方法用于内部消息的不断更新。Looper的作用是不断轮询来自MessageQueue的数据,轮询到数据后,再将数据传递给Handler。
    //Looper的构造方法
    private Looper(boolean quitAllowed) {
        //通过这里可以看出构造了一个消息队列
         mQueue = new MessageQueue(quitAllowed);
         mThread = Thread.currentThread();
    }
    
    • 创建好Handler对象后,就可以通过sendMessage等方法发送消息。通过上一步知道,MessageQueue用于接收消息,那么这里发送的消息肯定与MessageQueue有关。透过源码可以发现,sendMessage方法调用后,会调用到sendMessageAtTime。在sendMessageAtTime方法中,被传递进来的有两个参数,其中第一个参数就是sendMessage发送的消息,第二个参数为系统开机到当前的时间,接着函数内部调用MessageQueue类提供的入列方法enqueueMessage。
    //Handler发送消息后最终总是会调用的方法
    public boolean sendMessageAtTime(Message msg, long uptimeMillis)
    {
        boolean sent = false;
        MessageQueue queue = mQueue;
        if (queue != null) {
            msg.target = this;
            //调用MessageQueue的enqueueMessage方法将消息插入消息队列
            sent = queue.enqueueMessage(msg, uptimeMillis);
        }
        else {
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
        }
        return sent;
    }
    
    • 在enqueueMessage方法中,会根据接收到的消息和时间参数,通过时间前后将消息进行排列,其内部通过链表的形式进行存储,并且较早发送的消息指向较晚发送的消息。如图所示:

Android异步消息处理机制_第1张图片

  • 消息发送并进入了队列,同时Looper在不断的轮询来自MessageQueue的数据,下面是Looper提供的轮询方法loop,通过该方法可以发现该方法中有一个死循环,不断的调用MessageQueue的next方法,该方法会返回消息队列中的下一个消息,next方法如果不返回消息,则会一直处理阻塞状态,直到有消息到来,才会继续往下进行。接着往下看,当从消息队列中轮询到消息后,会调用dispatchMessage方法,该方法是Handler所提供,所以代码中的msg.target其实就是Handler对象。
//Looper提供的轮询方法
public static final void loop() {
    Looper me = myLooper();
    MessageQueue queue = me.mQueue;
    while (true) {
        //调用MessageQueue提供的next方法
        Message msg = queue.next(); // might block
        if (msg != null) {
            if (msg.target == null) {
                return;
            }
            if (me.mLogging!= null) me.mLogging.println(
                    ">>>>> Dispatching to " + msg.target + " "
                    + msg.callback + ": " + msg.what
                    );
            msg.target.dispatchMessage(msg);
            if (me.mLogging!= null) me.mLogging.println(
                    "<<<<< Finished to    " + msg.target + " "
                    + msg.callback);
            msg.recycle();
        }
    }
}
  • 调用dispatchMessage方法后,就会调用到handleMessage方法,dispatchMessage源码如下。
//下面方法用于处理轮询到的消息
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
  • 使用案例:

    public class MainActivity extends AppCompatActivity implements View.OnClickListener{
        TextView textView;
        Button button0;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            button0 = (Button)findViewById(R.id.send);
            textView = (TextView) findViewById(R.id.display);
            button0.setOnClickListener(this);
        }
    
        private Handler handler = new Handler(){
            @Override
            public void handleMessage(@NonNull Message msg) {
                switch (msg.what) {
                    case 1:
                        int all = msg.arg1+msg.arg2;
                        textView.setText(""+all);
                        break;
                }
            }
        };
    
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.send:
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            Message message = new Message();
                            message.what = 1;
                            message.arg1 = 1000;
                            message.arg2 = 1000;
                            handler.sendMessage(message);
                        }
                    }).start();
                    break;
                default:
                    break;
            }
        }
    }	
    

2.2 AsyncTask

  • 工作原理:AsyncTask是一个抽象类,其对Handler和Thread进行了封装,方便使用者使用,AsyncTask会在后台执行执行相应的任务并将执行结果发送给主线程,从而根据执行结果在主线程中更新UI控件的状态。。在使用时候,需要创建子类并继承它,这个类提供四个非常关键的方法,分别是onPreExecutedoInBackgroundonProgessUpdataonPostExecute,在继承的时候需要重新实现这几个方法。此外,在继承AsyncTask的时候,可以为AsyncTask指定三个泛型参数,这三个泛型参数分别如下:

    • Params:在执行任务时需要传入的参数类型,即执行execute传入的参数
    • Progress:任务执行过程中,传回主线程中的参数对应的类型
    • Result:任务执行结束后,返回数据的类型

    在继承类时,要根据具体任务的逻辑重写上述提到的几个方法,下面对这几个方法进行分析:

    • onPreExecute:这个方法会在执行了execute方法之后立即执行,这个方法的执行时间先于任务执行时间。此外,这个方法是在主线程中执行的,通常用于界面控件的初始化操作。
    • doInBackground(Params… params):这个方法会在执行上述onPreExecute方法后,立即执行。该方法是运行在子线程中的,可以在内部执行耗时的任务。该方法的参数类型是Params,并且是一个不定长的数组,其次如果Result类型不是Void类型时,还可以返回一个Result类型的参数。除此之外,在该方法内的任务执行过程中得到的一些数据与UI空间的状态相关联情况下,这时候如果需要更新UI控件状态,肯定不能在该方法内部更新UI控件,这就需要用到publishProgress(Progress… )方法,该方法可以将数据传递到UI线程,并在UI线程中调用onProgressUpdate方法更新UI。
    • onProgressUpdate(Progeress… values):通过上述可以知道,当在doInBackground中调用publishProgress(Progress… ),会在UI线程中调用onProgressUpdate方法,所以在该方法内可以进行UI的更新。
    • onPostExecute(Result result):当doInBackground方法执行任务结束后,会UI线程中调用该方法,用于进行任务结束后的一些操作。此外,当doInBackground方法有返回值时,会将该返回值作为参数传入onPostExecute方法内部,可以利用该返回值进行相应的UI控件更新。
  • 使用案例:

    public class MainActivity extends AppCompatActivity implements View.OnClickListener{
        
        Button button0;
        ProgressBar progressBar;
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            button0 = (Button)findViewById(R.id.startTask);
            button0.setOnClickListener(this);
            progressBar = (ProgressBar) findViewById(R.id.progress);
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.startTask:
                    DownLoadTask downLoadTask = new DownLoadTask();
                    downLoadTask.execute();
                    break;
                default:
                    break;
            }
        }
    
        public class DownLoadTask extends AsyncTask<String, Integer,String> {
            @Override
            protected String doInBackground(String... strings) {
                int num = 0;
                while (num<100) {
                    num++;
                    try {
                        Thread.sleep(1);
                        publishProgress(num);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
                return null;
            }
    
            @Override
            protected void onPostExecute(String s) {
                super.onPostExecute(s);
            }
    
            @Override
            protected void onProgressUpdate(Integer... values) {
                progressBar.setProgress(values[0]);
            }
    
            @Override
            protected void onPreExecute() {
                super.onPreExecute();
            }
        }
    }
    

你可能感兴趣的:(Android)