Android之——任意時刻從子線程切換到主線程的實現(插曲)

來源:互聯網
上載者:User

Android之——任意時刻從子線程切換到主線程的實現(插曲)

 
一、引入

在Android開發中常常會遇到網路請求資料庫資料準備等一些耗時的操作;而這些操作是不允許在主線程中進行的。因為這樣會堵塞主線程導致程式出現未響應情況。

所以只能另起一個子線程進行這些耗時的操作,完成後再顯示到介面。眾所周知,介面等控制項操作只能在主線程中完成;所以不可避免的需要從子線程切換到主線程

二、方法

對於這樣的情況在Android 中比較常見的是使用AsynTask類或者 Handler來進行線程切換;而其中AsynTask是官方封裝的類,較為簡單,效率也比較可以,但是並不適合所有的情況,至少我使用了一兩次後就再也沒有使用了。使用 Handler可以說是最萬能的方式,其原理是訊息迴圈,在主線程中建立Handler 變數時,就會啟動Handler訊息迴圈,一個個的處理訊息佇列中的任務。但是其也有棘手的時候;其棘手的地方就是麻煩。

每次都需要去建立一個 Handler 類,然後使用voidhandleMessage(Messagemsg) 方法把訊息取出來進行介面操作,而其中還要遇到參數的傳遞等問題,說起來真的是挺麻煩的。

三、想法

既然有著這麼多的問題,但是又有其的優勢,我們何不自行封裝一次呢?

這裡我梳理一下思路:

還是使用 Handler進行線程切換在子線程中能通過簡單的調用就切換到主線程進行工作在子線程切換到主線程時,子線程進入阻塞直到主線程執行完成(知道為什麼有這樣的需求嗎?)一定要保證其效率主線程的執行要有時間限制,不能執行太長時間導致主線程阻塞

我能想到的就是這些;觀眾老爺們咋樣?可否還有需求?

說幹就幹,梳理一下實現方法

使用Handler 實現,既然這樣那麼主方法當然就是採用繼承Handler 來實現而要簡單同時又要能隨時進入方法 那麼對外採用靜態方法是個不錯的選擇而要保證效率的話,那就不能讓Handler 的訊息佇列過於太多,但是又要滿足能隨時調用,那麼採用外部 Queue更具情況有阻塞與不阻塞子線程兩種情況,那麼採用兩個 Queue吧,分開來好一點要保證不能長時間在主線程執行那麼對於隊列的執行一定要有時間限制加一個時間變數吧當然最後考慮了一下,既然要簡單那麼傳入參數採用Runnable 是很爽的四、代碼

 

/** * @author liuyazhuang * */public class ToolKit {    /**     * Asynchronously     *     * @param runnable Runnable Interface     */    public static void runOnMainThreadAsync(Runnable runnable) {    }    /**     * Synchronously     *     * @param runnable Runnable Interface     */    public static void runOnMainThreadSync(Runnable runnable) {    }}

 

兩個對外的方法簡單來說就是這樣了;但是其功能實現就需要使用繼承Handler了。

建立類HandlerPoster,繼承自Handler:

 

/** *  @author liuyazhuang * */final class HandlerPoster extends Handler {    private final int ASYNC = 0x1;    private final int SYNC = 0x2;    private final Queue asyncPool;    private final Queue syncPool;    private final int maxMillisInsideHandleMessage;    private boolean asyncActive;    private boolean syncActive;    HandlerPoster(Looper looper, int maxMillisInsideHandleMessage) {        super(looper);        this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;        asyncPool = new LinkedList<>();        syncPool = new LinkedList<>();    }    void dispose() {        this.removeCallbacksAndMessages(null);        this.asyncPool.clear();        this.syncPool.clear();    }    void async(Runnable runnable) {        synchronized (asyncPool) {            asyncPool.offer(runnable);            if (!asyncActive) {                asyncActive = true;                if (!sendMessage(obtainMessage(ASYNC))) {                    throw new GeniusException(Could not send handler message);                }            }        }    }    void sync(SyncPost post) {        synchronized (syncPool) {            syncPool.offer(post);            if (!syncActive) {                syncActive = true;                if (!sendMessage(obtainMessage(SYNC))) {                    throw new GeniusException(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 = asyncPool.poll();                    if (runnable == null) {                        synchronized (asyncPool) {                            // Check again, this time in synchronized                            runnable = asyncPool.poll();                            if (runnable == null) {                                asyncActive = false;                                return;                            }                        }                    }                    runnable.run();                    long timeInMethod = SystemClock.uptimeMillis() - started;                    if (timeInMethod >= maxMillisInsideHandleMessage) {                        if (!sendMessage(obtainMessage(ASYNC))) {                            throw new GeniusException(Could not send handler message);                        }                        rescheduled = true;                        return;                    }                }            } finally {                asyncActive = rescheduled;            }        } else if (msg.what == SYNC) {            boolean rescheduled = false;            try {                long started = SystemClock.uptimeMillis();                while (true) {                    SyncPost post = syncPool.poll();                    if (post == null) {                        synchronized (syncPool) {                            // Check again, this time in synchronized                            post = syncPool.poll();                            if (post == null) {                                syncActive = false;                                return;                            }                        }                    }                    post.run();                    long timeInMethod = SystemClock.uptimeMillis() - started;                    if (timeInMethod >= maxMillisInsideHandleMessage) {                        if (!sendMessage(obtainMessage(SYNC))) {                            throw new GeniusException(Could not send handler message);                        }                        rescheduled = true;                        return;                    }                }            } finally {                syncActive = rescheduled;            }        } else super.handleMessage(msg);    }}
下面來說說這個我花了很大時間弄出來的類。

 

 

類的變數部分:

 

兩個標識,兩個隊列,兩個執行狀態,一個時間限制;很好理解吧?標識為了區別分別是處理那個隊列使用;隊列當然是裝著任務了;執行狀態是為了避免重複發送訊息導致訊息佇列過多;時間限制這個最好理解了。

下面來說說方法部分

建構函式HandlerPoster(Looper_looper,int_maxMillisInsideHandleMessage):

傳入兩個參數,分別是 Looper,用於初始化到主線程,後面的是時間限制;然後初始化了兩個隊列。

銷毀函數void_dispose():首先去除掉沒有處理的訊息,然後清空隊列。

添加非同步執行方法void_async(Runnable_runnable):

 void async(Runnable runnable) {        synchronized (asyncPool) {            asyncPool.offer(runnable);            if (!asyncActive) {                asyncActive = true;                if (!sendMessage(obtainMessage(ASYNC))) {                    throw new GeniusException(Could not send handler message);                }            }        }    }

可以看見進入方法後第一件事兒就是進入同步狀態,然後調用asyncPool.offer(runnable);把任務寫入到隊列。之後判斷當前是否處於非同步任務執行中,如果不是:立刻改變狀態,然後發送一個訊息給當前Handler,當然不要忘記了傳入標識。當然為了效率其訊息的構造也是通過obtainMessage(ASYNC)方法來完成,為的就是不過多建立新的Message,盡量使用當前隊列中閒置訊息。

添加同步執行方法void_sync(SyncPost_post):

void sync(SyncPost post) {        synchronized (syncPool) {            syncPool.offer(post);            if (!syncActive) {                syncActive = true;                if (!sendMessage(obtainMessage(SYNC))) {                    throw new GeniusException(Could not send handler message);                }            }        }    }

可以看到,這裡傳入的並不是Runnable 而是SyncPost這是為了同步而對Runnable進行了一次封裝後的類;後面介紹。同樣是進入同步,添加,判斷,發送訊息。

任務執行者@Override_void_handleMessage(Message_msg):

這裡是複寫的Handler的訊息處理方法,噹噹前Handler訊息佇列中有訊息的時候將會按照順序一個個的調用該方法。

分段來看:

 if (msg.what == ASYNC) {            boolean rescheduled = false;            try {                long started = SystemClock.uptimeMillis();                while (true) {                    Runnable runnable = asyncPool.poll();                    if (runnable == null) {                        synchronized (asyncPool) {                            // Check again, this time in synchronized                            runnable = asyncPool.poll();                            if (runnable == null) {                                asyncActive = false;                                return;                            }                        }                    }                    runnable.run();                    long timeInMethod = SystemClock.uptimeMillis() - started;                    if (timeInMethod >= maxMillisInsideHandleMessage) {                        if (!sendMessage(obtainMessage(ASYNC))) {                            throw new GeniusException(Could not send handler message);                        }                        rescheduled = true;                        return;                    }                }            } finally {                asyncActive = rescheduled;            }        }

進入後首先判斷是否是進行非同步處理的訊息,如果是那麼進入該位置。進入後我們進行了try_finally有一個變數long_started用於標識開始時間。當執行一個任務後就判斷一次如果超過了每次佔用主線程的時間限制,那麼不管隊列中的任務是否執行完退出,同時發起一個新的訊息到Handler迴圈隊列。在while部分,我們從隊列取出一個任務,採用Poll方法;判斷是否為空白,如果為空白進入隊列同步塊;然後再取一次,再次判斷。如果恰巧在進入同步隊列之前有新的任務來了,那麼第二次取到的當然就不是 NULL也就會繼續執行下去。反之,如果還是為空白;那麼重設當前隊列的狀態為false同時跳出迴圈。

下面來看第二部分:

else if (msg.what == SYNC) {            boolean rescheduled = false;            try {                long started = SystemClock.uptimeMillis();                while (true) {                    SyncPost post = syncPool.poll();                    if (post == null) {                        synchronized (syncPool) {                            // Check again, this time in synchronized                            post = syncPool.poll();                            if (post == null) {                                syncActive = false;                                return;                            }                        }                    }                    post.run();                    long timeInMethod = SystemClock.uptimeMillis() - started;                    if (timeInMethod >= maxMillisInsideHandleMessage) {                        if (!sendMessage(obtainMessage(SYNC))) {                            throw new GeniusException(Could not send handler message);                        }                        rescheduled = true;                        return;                    }                }            } finally {                syncActive = rescheduled;            }        } else super.handleMessage(msg);

 

首先還是判斷,如果是同步任務訊息就進入,如果還是不是 那麼只有調用super.handleMessage(msg);了。從上面的處理部分可以看出來其處理的過程與第一部分可以說是完全一樣的。只不過是從不同隊列取出不同的類SyncPost,然後判斷執行,以及發送不同標識的訊息;可以說如果懂了第一部分,這部分是毫無營養的。

這裡就有問題了,既然方法操作流程一樣,那麼同步與非同步是在哪裡進行區分的?

這裡就要看看SyncPost了:

/** *  @author liuyazhuang * */final class SyncPost {    boolean end = false;    Runnable runnable;    SyncPost(Runnable runnable) {        this.runnable = runnable;    }    public void run() {        synchronized (this) {            runnable.run();            end = true;            try {                this.notifyAll();            } catch (Exception e) {                e.printStackTrace();            }        }    }    public void waitRun() {        if (!end) {            synchronized (this) {                if (!end) {                    try {                        this.wait();                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        }    }}

首先看看SyncPost的建構函式:

是不是傳入一個Runnable介面?所以說是對Runnable 的簡單封裝。

可以看見其public_void_run()方法:

在該方法中我們進入了同步塊,然後調用Runnable介面的run方法。同時在執行完成後將其中的一個狀態變數進行了改變boolean_end=true;然後調用this.notifyAll();通知等待的部分可以繼續了,當然有這樣的情況;假如在進入該同步塊的時候子線程還未執行到this.wait();部分呢?所以我們為此準備了end和try。

然後看看public_void_waitRun()方法:

在這個中,我們首先判斷狀態,如果狀態已經變了,那麼證明子線程執行到此處時,主線程以及執行了void_run()。所以也就不用進入同步塊進行等待了,不然那還不等死啊?反之就進入進行等待直到主線程調用this.notifyAll();

五、激情部分

回到類ToolKit

/** *  @author liuyazhuang * */public class ToolKit {    private static HandlerPoster mainPoster = null;    private static HandlerPoster getMainPoster() {        if (mainPoster == null) {            synchronized (ToolKit.class) {                if (mainPoster == null) {                    mainPoster = new HandlerPoster(Looper.getMainLooper(), 20);                }            }        }        return mainPoster;    }    /**     * Asynchronously     * The child thread asynchronous run relative to the main thread,     * not blocking the child thread     *     * @param runnable Runnable Interface     */    public static void runOnMainThreadAsync(Runnable runnable) {        if (Looper.myLooper() == Looper.getMainLooper()) {            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     *     * @param runnable Runnable Interface     */    public static void runOnMainThreadSync(Runnable runnable) {        if (Looper.myLooper() == Looper.getMainLooper()) {            runnable.run();            return;        }        SyncPost poster = new SyncPost(runnable);        getMainPoster().sync(poster);        poster.waitRun();    }    public static void dispose() {        if (mainPoster != null) {            mainPoster.dispose();            mainPoster = null;        }    }}

 

其中就一個靜態變數HandlerPoster

然後一個初始化部分HandlerPoster_getMainPoster()這裡採用同步的方式進行初始化,用於適應多線程同時調用情況;當然在初始化的時候我們傳入了

mainPoster=newHandlerPoster(Looper.getMainLooper(),20); 這裡就決定了是在主線程執行的HandlerPoster,同時指定主線程單次已耗用時間為20毫秒。

在方法void_runOnMainThreadAsync(Runnable_runnable)中:

首先判斷調用該方法的是否是主線程,如果是那還弄到隊列中執行幹嘛?直接執行啊;如果是子線程就調用getMainPoster().async(runnable);追加到隊列中執行。

而在方法void_runOnMainThreadSync(Runnable_runnable)中:

public static void runOnMainThreadSync(Runnable runnable) {        if (Looper.myLooper() == Looper.getMainLooper()) {            runnable.run();            return;        }        SyncPost poster = new SyncPost(runnable);        getMainPoster().sync(poster);        poster.waitRun();    }
同樣是線程判斷,然後進行封裝,然後丟進隊列中等待執行,而在該方法中調用poster.waitRun();進行等待;直到主線程執行了SyncPost類的run方法。最後當然留下了一個銷毀方法;媽媽說要學會清理不留垃圾:void_dispose()

OK,完成了。

// Runnable 類實現其中 run() 方法// run() 運行在主線程中,可在其中進行介面操作// 同步進入主線程,等待主線程處理完成後繼續執行子線程ToolKit.runOnMainThreadSync(Runnable runnable);// 非同步進入主線程,無需等待ToolKit.runOnMainThreadAsync(Runnable runnable);
對外就是這麼兩個方法,簡單便捷啊;

 

 

 




聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.