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 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异步消息处理机制
文章图片

  • 消息发送并进入了队列,同时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控件更新。
  • 【Android异步消息处理机制】使用案例:
    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, 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(); } } }

    推荐阅读