Android 异步处理

Android异步机制:

同步:发送一个请求,等待返回,然后再发送下一个请求   
异步:发送一个请求,不等待返回,随时可以再发送下一个请求  

作用:

为了给用户带来良好的交互体验,在Android应用的开发过程中需要把繁重的任务(IO,网络连接等)放到其他线程中异步执行,达到不阻塞UI的效果。

几种实现方式:

1.   使用Thread+Handler实现非UI线程更新UI界面

2.   使用AsyncTask异步更新UI界面

3.   Handler+Looper+MessageQueue深入详解

 

 

Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面

概述:每个Android应用程序都运行在一个dalvik虚拟机进程中,进程开始的时候会启动一个主线程(MainThread),主线程负责处理和ui相关的事件,因此主线程通常又叫UI线程。而由于Android采用UI单线程模型,所以只能在主线程中对UI元素进行操作。如果在非UI线程直接对UI进行了操作,则会报错:

CalledFromWrongThreadException:only the original thread that createda view hierarchy can touch its views

Android主线程是线程不安全的。这句话的意思是:更新UI只能是主线程的工作,子线程更新UI是线程不安全的,所以android里非主线程操作主UI就会报错。为什么呢?因为子线程可能会有多个,多个线程同时操作一个控件可能会有冲突发生,所以android就限定了只有主线程可以操作UI。子线程想操作UI,可以,你告诉我(主线程),我来更新

Android UI单线程模型:(http://ictch.iteye.com/blog/1002873),这样是为了防止多个线程去更新view的数据,导致混乱

在开发Android应用时必须遵守单线程模型的原则:

UI操作并不是线程安全的并且这些操作必须在UI线程中执行。

在单线程模型中始终要记住两条法则:

 1. 不要阻塞UI线程

 2. 确保只在UI线程中访问Android UI工具包

 当一个程序第一次启动时,Android会同时启动一个对应的主线程(Main Thread),主线程主要负责处理与UI相关的事件,如:用户的按键事件,用户接触屏幕的事件以及屏幕绘图事件,并把相关的事件分发到对应的组件进行处理。所以主线程通常又被叫做UI线程。

 

比如说从网上获取一个网页,在一个TextView中将其源代码显示出来,这种涉及到网络操作的程序一般都是需要开一个线程完成网络访问,但是在获得页面源码后,是不能直接在网络操作线程中调用TextView.setText()的.因为其他线程中是不能直接访问主UI线程成员 。

android提供了几种在其他线程中访问UI线程的方法。

 Activity.runOnUiThread(Runnable )

 View.post( Runnable )

 View.postDelayed( Runnable,long )

 Hanlder (    

  Handler在默认情况下,实际上它和调用它的Activity是处于同一个线程的。并且在Activity的onCreate()方法中输出当前线程的id和名字。由于Handler运行在主线程中(UI线程中),它与子线程可以通过Message对象来传递数据,* 这个时候,Handler就承担着接受子线程传过来的(子线程用sedMessage()方法传弟)Message对象(里面包含数据),把这些消息放入主线程队列中,配合主线程进行更新UI。而post  安排消息或Runnable 在某个主线程中某个地方执行。post() ?将一个线程加入线程队列;sendMessage() 发送一个消息对象到消息队列。post 传入Runnable也就是callback方法,最终会放入消息队列,在loop -》 dispatchMesage-》中会判断callback != null ? 执行callback : ms,target.handleMesage();

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
        private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

Bundle和如何在新线程中处理消息

Invalidate和postInvalidate的区别 

android中实现view的更新有两组方法,一组是invalidate,另一组是postInvalidate,其中前者是在UI线程自身中使用,而后者在非UI线程中使用)

 这些类或方法同样会使你的代码很复杂很难理解。然而当你需要实现一些很复杂的操作并需要频繁地更新UI时这会变得更糟糕。

 

为了解决这个问题,Android 提供了一个工具类:AsyncTask不注意它会导致的问题:当在执行一些耗时的操作的时候,不能及时地分发事件,包括用户界面重绘事件。从用户的角度来看,应用程序看上去像挂掉了。更糟糕的是,如果阻塞应用程序的时间过长(现在大概是5秒钟)Android会向用户提示一些信息,即打开一个“应用程序没 有相应(application not responding)”的对话框。

Android为我们提供了消息循环的机制,我们可以利用这个机制来实现线程间的通信。那么,我们就可以在非UI线程发送消息到UI线程,最终让Ui线程来进行ui的操作。

对于运算量较大的操作和IO操作,我们需要新开线程来处理这些繁重的工作,以免阻塞ui线程。


Androd异步处理二:使用AsyncTask异步更新UI界面

AsyncTask是在Android SDK 1.5之后推出的一个方便编写后台线程与UI线程交互的辅助类。AsyncTask的内部实现是一个线程池,每个后台任务会提交到线程池中的线程执行,然后使用Thread+Handler的方式调用回调函数。任务在共做线程中做,然后分发到InternalHandler(属于主线程,所有类都属于主线程),调用回调方法更新UI。  

  public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        mStatus = Status.RUNNING;

        onPreExecute();// UI 线程

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }
  

  private static class InternalHandler extends Handler {
        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult result = (AsyncTaskResult) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);//UI 线程
                    break;
            }
        }
    }      

  mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                return postResult(doInBackground(mParams));//工作线程
            }
        };

   private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);//UI 线程
        }
        mStatus = Status.FINISHED;
    }

AsyncTask抽象出后台线程运行的五个状态,分别是:1、准备运行,2、正在后台运行,3、进度更新,4、完成后台任务,5、取消任务,对于这五个阶段,AsyncTask提供了五个回调函数:

1、准备运行:onPreExecute(),该回调函数在任务被执行之后立即由UI线程调用。这个步骤通常用来建立任务,在用户接口(UI)上显示进度条

2、正在后台运行:doInBackground(Params...),该回调函数由后台线程在onPreExecute()方法执行结束后立即调用。通常在这里执行耗时的后台计算。计算的结果必须由该函数返回,并被传递到onPostExecute()中。在该函数内也可以使用publishProgress(Progress...)来发布一个或多个进度单位(unitsof progress)。这些值将会在onProgressUpdate(Progress...)中被发布到UI线程。

3. 进度更新:onProgressUpdate(Progress...),该函数由UI线程在publishProgress(Progress...)方法调用完后被调用。一般用于动态地显示一个进度条。

4. 完成后台任务:onPostExecute(Result),当后台计算结束后调用。后台计算的结果会被作为参数传递给这一函数。

5、取消任务:onCancelled (),在调用AsyncTask的cancel()方法时调用



AsyncTask的构造函数有三个模板参数:

1.Params,传递给后台任务的参数类型。

2.Progress,后台计算执行过程中,进步单位(progress units)的类型。(就是后台程序已经执行了百分之几了。)

3.Result, 后台执行返回的结果的类型。

AsyncTask并不总是需要使用上面的全部3种类型。标识不使用的类型很简单,只需要使用Void类型即可 * private class DownloadFilesTask extends AsyncTask&lt;URL, Integer, Long&gt; {
 *     protected Long doInBackground(URL... urls) {
 *         int count = urls.length;
 *         long totalSize = 0;
 *         for (int i = 0; i < count; i++) {
 *             totalSize += Downloader.downloadFile(urls[i]);
 *             publishProgress((int) ((i / (float) count) * 100));
 *             // Escape early if cancel() is called
 *             if (isCancelled()) break;
 *         }
 *         return totalSize;
 *     }
 *
 *     protected void onProgressUpdate(Integer... progress) {
 *         setProgressPercent(progress[0]);
 *     }
 *
 *     protected void onPostExecute(Long result) {
 *         showDialog("Downloaded " + result + " bytes");
 *     }
 * }


Android异步处理三:Handler+Looper+MessageQueue深入详解

我们讲到使用Thread+Handler的方式来实现界面的更新,其实是在非UI线程发送消息到UI线程,通知UI线程进行界面更新,这一篇我们将深入学习Android线程间通讯的实现原理。

概述:Android使用消息机制实现线程间的通信,线程通过Looper建立自己的消息循环,MessageQueue是FIFO的消息队列,Looper负责从MessageQueue中取出消息,并且分发到消息指定目标Handler对象。Handler对象绑定到线程的局部变量Looper,封装了发送消息和处理消息的接口。

例子:在介绍原理之前,我们先介绍Android线程通讯的一个例子,这个例子实现点击按钮之后从主线程发送消息"hello"到另外一个名为” CustomThread”的线程。

代码下载

LooperThreadActivity.java

[java] view plain
copy

package com.zhuozhuo; 

import android.app.Activity; 
import android.os.Bundle; 
import android.os.Handler; 
import android.os.Looper; 
import android.os.Message; 
import android.util.Log; 
import android.view.View; 
import android.view.View.OnClickListener; 

public class LooperThreadActivity extends Activity{ 
/** Called when the activity is first created. */ 

private final int MSG_HELLO = 0; 
private Handler mHandler; 

@Override 
public void onCreate(Bundle savedInstanceState) { 
super.onCreate(savedInstanceState); 
setContentView(R.layout.main); 
new CustomThread().start();//新建并启动CustomThread实例 

findViewById(R.id.send_btn).setOnClickListener(new OnClickListener() { 

@Override 
public void onClick(View v) {//点击界面时发送消息 
String str = "hello"; 
Log.d("Test", "MainThread is ready to send msg:" + str); 
mHandler.obtainMessage(MSG_HELLO, str).sendToTarget();//发送消息到CustomThread实例 


}); 







class CustomThread extends Thread { 
@Override 
public void run() { 
//建立消息循环的步骤 
Looper.prepare();//1、初始化Looper 
mHandler = new Handler(){//2、绑定handler到CustomThread实例的Looper对象 
public void handleMessage (Message msg) {//3、定义处理消息的方法 
switch(msg.what) { 
case MSG_HELLO: 
Log.d("Test", "CustomThread receive msg:" + (String) msg.obj); 


}; 
Looper.loop();//4、启动消息循环 



main.xml 


[html] view plain
copy

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
android:orientation="vertical" 
android:layout_width="fill_parent" 
android:layout_height="fill_parent" 

<TextView 
android:layout_width="fill_parent" 
android:layout_height="wrap_content" 
android:text="@string/hello" 
/> 
<Button android:text="发送消息" android:id="@+id/send_btn" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
</LinearLayout> 



Log打印结果:



原理:

我们看到,为一个线程建立消息循环有四个步骤: 

1、 初始化Looper

2、 绑定handler到CustomThread实例的Looper对象

3、 定义处理消息的方法

4、 启动消息循环

下面我们以这个例子为线索,深入Android源代码,说明Android Framework是如何建立消息循环,并对消息进行分发的。

1、 初始化Looper : Looper.prepare()

Looper.java
[java] view plain
copy

private static final ThreadLocal sThreadLocal = new ThreadLocal(); 
public static final void prepare() { 
if (sThreadLocal.get() != null) { 
throw new RuntimeException("Only one Looper may be created per thread"); 

sThreadLocal.set(new Looper()); 


一个线程在调用Looper的静态方法prepare()时,这个线程会新建一个Looper对象,并放入到线程的局部变量中,而这个变量是不和其他线程共享的(关于ThreadLocal的介绍)。下面我们看看Looper()这个构造函数:

Looper.java
[java] view plain
copy

final MessageQueue mQueue; 
private Looper() { 
mQueue = new MessageQueue(); 
mRun = true; 
mThread = Thread.currentThread(); 


可以看到在Looper的构造函数中,创建了一个消息队列对象mQueue,此时,调用Looper. prepare()的线程就建立起一个消息循环的对象(此时还没开始进行消息循环)。

2、 绑定handler到CustomThread实例的Looper对象 : mHandler= new Handler()

Handler.java
[java] view plain
copy

final MessageQueue mQueue; 
final Looper mLooper; 
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(); 
if (mLooper == null) { 
throw new RuntimeException( 
"Can't create handler inside thread that has not called Looper.prepare()"); 

mQueue = mLooper.mQueue; 
mCallback = null; 



Handler通过mLooper = Looper.myLooper();绑定到线程的局部变量Looper上去,同时Handler通过mQueue =mLooper.mQueue;获得线程的消息队列。此时,Handler就绑定到创建此Handler对象的线程的消息队列上了。
3、定义处理消息的方法:Override public void handleMessage (Message msg){}

子类需要覆盖这个方法,实现接受到消息后的处理方法。

4、启动消息循环 : Looper.loop()

所有准备工作都准备好了,是时候启动消息循环了!Looper的静态方法loop()实现了消息循环。

Looper.java
[java] view plain
copy

public static final void loop() { 
Looper me = myLooper(); 
MessageQueue queue = me.mQueue; 

// Make sure the identity of this thread is that of the local process, 
// and keep track of what that identity token actually is. 
Binder.clearCallingIdentity(); 
final long ident = Binder.clearCallingIdentity(); 

while (true) { 
Message msg = queue.next(); // might block 
//if (!me.mRun) { 
// break; 
//} 
if (msg != null) { 
if (msg.target == null) { 
// No target is a magic identifier for the quit message. 
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); 

// Make sure that during the course of dispatching the 
// identity of the thread wasn't corrupted. 
final long newIdent = Binder.clearCallingIdentity(); 
if (ident != newIdent) { 
Log.wtf("Looper", "Thread identity changed from 0x" 
+ Long.toHexString(ident) + " to 0x" 
+ Long.toHexString(newIdent) + " while dispatching to " 
+ msg.target.getClass().getName() + " " 
+ msg.callback + " what=" + msg.what); 


msg.recycle(); 





while(true)体现了消息循环中的“循环“,Looper会在循环体中调用queue.next()获取消息队列中需要处理的下一条消息。当msg != null且msg.target != null时,调用msg.target.dispatchMessage(msg);分发消息,当分发完成后,调用msg.recycle();回收消息。

msg.target是一个handler对象,表示需要处理这个消息的handler对象。Handler的void dispatchMessage(Message msg)方法如下:

Handler.java
[java] view plain
copy

public void dispatchMessage(Message msg) { 
if (msg.callback != null) { 
handleCallback(msg); 
} else { 
if (mCallback != null) { 
if (mCallback.handleMessage(msg)) { 
return; 


handleMessage(msg); 



可见,当msg.callback== null 并且mCallback == null时,这个例子是由handleMessage(msg);处理消息,上面我们说到子类覆盖这个方法可以实现消息的具体处理过程。



总结:从上面的分析过程可知,消息循环的核心是Looper,Looper持有消息队列MessageQueue对象,一个线程可以把Looper设为该线程的局部变量,这就相当于这个线程建立了一个对应的消息队列。Handler的作用就是封装发送消息和处理消息的过程,让其他线程只需要操作Handler就可以发消息给创建Handler的线程。由此可以知道,在上一篇《Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面》中,UI线程在创建的时候就建立了消息循环(在ActivityThread的public static final void main(String[] args)方法中实现),因此我们可以在其他线程给UI线程的handler发送消息,达到更新UI的目的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值