您的位置:首页 > 移动开发 > Android开发

Android 子线程中操作UI【原创】

2015-01-30 00:00 453 查看
开发Android的都知道,自2.2之后,谷歌工程师规定了子线程不允许更新UI,原因说起来也简单,可以想一想,如果多个子线程同时操作UI,那么该听谁的呢,所以,现在在子线程中写关于操作UI的代码,运行就回报异常。

我们都知道耗时的操作,例如数据库读写大量数据,网络请求,这些都比较耗时间,一般情况下都会放入子线程里面操作,这样不会阻塞UI。但是每次当我们操作完这些数据之后,就要更新UI的数据,但是上面说了,子线程是不允许操作UI的,所以我们都想到了handler,创建一个handler对象,重写handlermessage方法,在里面过滤比对消息,再对UI进行操作。

可以想一下,每次new一个线程操作完数据,还得创建Message对象与handler对象,实在是麻烦啊。怎么办,有人说用

AsyncTask,AsyncTask是官方封装的一个比较好的异步操作,但是一些大牛貌似不喜欢用,我写了不少次,后来也不想去写了,每次都要定义后传入参数类型,返回值类型等,代码稍微变动,改起来也是有点麻烦。

Thread用起来灵活方便,现在就是刷新UI有点小麻烦,针对这个问题,参考一下网上资料与思路,整理完善了一个工具类,可以直接在子线程里刷新UI,一起来看看把

/*
add        增加一个元索                     如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove   移除并返回队列头部的元素    如果队列为空,则抛出一个NoSuchElementException异常
element  返回队列头部的元素             如果队列为空,则抛出一个NoSuchElementException异常
offer       添加一个元素并返回true       如果队列已满,则返回false
poll         移除并返问队列头部的元素    如果队列为空,则返回null
peek       返回队列头部的元素             如果队列为空,则返回null
put         添加一个元素                      如果队列满,则阻塞
take        移除并返回队列头部的元素     如果队列为空,则阻塞 
 */

package com.xdroid.utils;

import java.util.LinkedList;
import java.util.Queue;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;

/**
 * Created by Robin
 * on 2015-01-28 10:31:15.
 */
final class UIKitHandlerPoster extends Handler {
    private static final int ASYNC = 0x1;   //异步
    private static final int SYNC = 0x2;  //同步
    private final Queue<Runnable> mAsyncPool;  //异步Runnable任务队列
    private final Queue<UIKitSyncPost> mSyncPool;  //同步Runnable任务队列
    private final int mMaxMillisInsideHandleMessage;  //占用主线程的时间限制
    private boolean isAsyncActive;  //当前是否处于异步任务执行中
    private boolean isSyncActive;  //当前是否处于同步任务执行中

    UIKitHandlerPoster(Looper looper, int maxMillisInsideHandleMessage) {
        super(looper);
        this.mMaxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
        mAsyncPool = new LinkedList<Runnable>();
        mSyncPool = new LinkedList<UIKitSyncPost>();
    }

    /**
     * 去除掉没有处理的消息,从队列彻底移除所有元素
     */
    void dispose() {
        this.removeCallbacksAndMessages(null);
        this.mAsyncPool.clear();
        this.mSyncPool.clear();
    }

    /**
     * 加锁添加一个异步Runnable任务
     * @param runnable
     */
    void async(Runnable runnable) {
        synchronized (mAsyncPool) {
            mAsyncPool.offer(runnable);  //添加一个Runnable任务并返回true  如果队列已满,则返回false
            if (!isAsyncActive) {  //判断当前是否处于异步任务执行中,如果不是:立刻改变状态,然后发送一个消息给当前Handler
                isAsyncActive = true;
                if (!sendMessage(obtainMessage(ASYNC))) {
                    throw new XDroidException("Could not send handler message");
                }
            }
        }
    }

    /**
     * 加锁添加一个同步任务
     * @param post
     */
    void sync(UIKitSyncPost post) {
        synchronized (mSyncPool) {
            mSyncPool.offer(post);
            if (!isSyncActive) {
                isSyncActive = true;
                if (!sendMessage(obtainMessage(SYNC))) {
                    throw new XDroidException("Could not send handler message");
                }
            }
        }
    }

    @Override
    public void handleMessage(Message msg) {
        if (msg.what == ASYNC) {//判断是否是进行异步处理的消息,如果是那么进入该位置。
            boolean rescheduled = false;  
            try {
                long started = SystemClock.uptimeMillis();   //标识开始时间
                while (true) {
                    Runnable runnable = mAsyncPool.poll();   //移除并返问队列头部的Runnable任务 如果队列为空,则返回null
                    if (runnable == null) {
                        synchronized (mAsyncPool) {
                            // Check again, this time in synchronized
                            runnable = mAsyncPool.poll();
                            if (runnable == null) {
                                isAsyncActive = false;
                                return;
                            }
                        }
                    }
                    runnable.run();
                    long timeInMethod = SystemClock.uptimeMillis() - started;  //任务执行结束时间与任务开始时间差
                    if (timeInMethod >= mMaxMillisInsideHandleMessage) {   //当执行一个任务后就判断一次如果超过了每次占用主线程的时间限制,那么不管队列中的任务是否执行完成都退出,同时发起一个新的消息到Handler循环队列
                        if (!sendMessage(obtainMessage(ASYNC))) {
                            throw new XDroidException("Could not send handler message");
                        }
                        rescheduled = true;
                        return;
                    }
                }
            } finally {
                isAsyncActive = rescheduled;
            }
        } else if (msg.what == SYNC) {
            boolean rescheduled = false;
            try {
                long started = SystemClock.uptimeMillis();
                while (true) {
                    UIKitSyncPost post = mSyncPool.poll();
                    if (post == null) {
                        synchronized (mSyncPool) {
                            // Check again, this time in synchronized
                            post = mSyncPool.poll();
                            if (post == null) {
                                isSyncActive = false;
                                return;
                            }
                        }
                    }
                    post.run();
                    long timeInMethod = SystemClock.uptimeMillis() - started;
                    if (timeInMethod >= mMaxMillisInsideHandleMessage) {
                        if (!sendMessage(obtainMessage(SYNC))) {
                            throw new XDroidException("Could not send handler message");
                        }
                        rescheduled = true;
                        return;
                    }
                }
            } finally {
                isSyncActive = rescheduled;
            }
        } else super.handleMessage(msg);
    }
}

原理也好理解,就是创建一个Runnable的任务队列,需要执行的时候把消息发送到handler,然后子啊handlerMessage里面从队列中取出Runnable任务执行,这里面有一个异步与同步的方法,异步好理解,传入一个Runnable接口实现类就可以了,那么同步UIKitSyncPost呢,下面是对Runnable的一个简单的封装,实现同步。

package com.xdroid.utils;

/**
 * 同步任务类,对Runnable的简单封装
 * Created by Robin
 * on 2015-01-28 10:32:05.
 */
final class UIKitSyncPost {
    private Runnable mRunnable; 
    private boolean isEnd = false;  //是否执行结束
    UIKitSyncPost(Runnable runnable) {
        this.mRunnable = runnable;
    }

    /**
     * 加锁执行Runnable任务
     */
    public void run() {
        if (!isEnd) {
            synchronized (this) {
                if (!isEnd) {
                    mRunnable.run();
                    isEnd = true;
                    try {
                        this.notifyAll();  //唤醒其他所有线程
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 等待状态,等待其他线程执行完毕
     */
    public void waitRun() {
        if (!isEnd) {
            synchronized (this) {
                if (!isEnd) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 等待状态,等待其他线程执行完毕
     * @param time 等待时间
     * @param cancel 是否取消
     */
    public void waitRun(int time, boolean cancel) {
        if (!isEnd) {
            synchronized (this) {
                if (!isEnd) {
                    try {
                        this.wait(time);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        if (!isEnd && cancel)
                            isEnd = true;
                    }
                }
            }
        }
    }
}

最后是三个公开的方法,如下代码

package com.xdroid.utils;

import android.os.Looper;

/**
* UI工具类,用于在子线程中操作UI
* Created by Robin
* on 2015-01-28 10:21:53.
*/
final public class UIKit {
private static UIKitHandlerPoster mainPoster = null;

private static UIKitHandlerPoster getMainPoster() {
if (mainPoster == null) {
synchronized (UIKit.class) {
if (mainPoster == null) {
mainPoster = new UIKitHandlerPoster(Looper.getMainLooper(), 20);  //决定是在主线程执行的HandlerPoster,同时指定主线程单次运行时间为20毫秒
}
}
}
return mainPoster;
}

/**
* Asynchronously
* The child thread asynchronous run relative to the main thread,
* not blocking the child thread
* 子线程异步运行相对于主线程
* 不阻塞子线程
* 在子线程中调用,直接操作UI,不阻塞子线程,即异步切换到主线程,无需等待
* @param runnable Runnable Interface
*/
public static void runOnMainThreadAsync(Runnable runnable) {
if (Looper.myLooper() == Looper.getMainLooper()) {  //首先判断调用该方法的是否是主线程,如果是,就直接执行;如果是子线程就调用getMainPoster().async(runnable);追加到队列中执行
runnable.run();
return;
}
getMainPoster().async(runnable);
}

/**
* Synchronously
* The child thread relative thread synchronization operation,
* blocking the child thread,
* thread for the main thread to complete
* 子线程相对线程同步操作
* 阻塞子线程
* 等待主线程执行完毕
* 在子线程中操作UI,同时阻塞子线程
* @param runnable Runnable Interface
*/
public static void runOnMainThreadSync(Runnable runnable) {
if (Looper.myLooper() == Looper.getMainLooper()) {  //首先判断调用该方法的是否是主线程,如果是,就直接执行;如果是子线程就调用getMainPoster().async(runnable);追加到队列中执行
runnable.run();
return;
}
UIKitSyncPost poster = new UIKitSyncPost(runnable);
getMainPoster().sync(poster);
poster.waitRun();  //进行等待;直到主线程执行了SyncPost类的run方法。
}

/**
* Synchronously
* The child thread relative thread synchronization operation,
* blocking the child thread,
* thread for the main thread to complete
* But the child thread just wait for the waitTime long.
*
* @param runnable Runnable Interface
* @param waitTime wait for the main thread run Time
* @param cancel   on the child thread cancel the runnable task
*/
public static void runOnMainThreadSync(Runnable runnable, int waitTime, boolean cancel) {
if (Looper.myLooper() == Looper.getMainLooper()) {  //如果当前是主线程
runnable.run();
return;
}
UIKitSyncPost poster = new UIKitSyncPost(runnable);
getMainPoster().sync(poster);
poster.waitRun(waitTime, cancel);
}

public static void dispose() {
if (mainPoster != null) {
mainPoster.dispose();
mainPoster = null;
}
}
}

当我们要在子线程中刷新UI的时候,只需要在子线程中调用,这三个公开的方法即可,

runOnMainThreadAsync是异步,调用这个方法,主线程不会阻塞会直接调用Runnable中的刷新UI代码,不会与主线程干扰。

runOnMainThreadSync是同步,调用这个方法主线程会阻塞

最后一个带参数的同步方法设置了阻塞时间,这样可以防止主线程阻塞时间过久导致ANR错误。

看下调用方式吧:

package com.example.test2;

import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.view.annotation.ViewInject;
import com.xdroid.utils.UIKit;

import android.app.Activity;
import android.app.AlertDialog;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends Activity {

@ViewInject(R.id.tv)
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.inject(this);
new Thread(){
@Override
public void run() {

UIKit.runOnMainThreadAsync(new Runnable() {

@Override
public void run() {
AlertDialog.Builder builder=new AlertDialog.Builder(MainActivity.this);
builder.setTitle("hah");
builder.create().show();
}
});

};
}.start();
}
}

 



上面代码就是一个在子线程里弹出一个Dialog的用法,无需再写handler与Message对象了,用起来爽歪歪。

对这个感兴趣的,可以看看代码注释。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: