從零寫一個線程切換工具

有的人在努力,有的人卻在拼命,不負當下

本篇博客主要是指引着寫一個簡單實用的線程切換工具,如果有更好的想法或者意見可以留言


我們需要帶着問題去學,看到題目不知道你是否有一下幾個問題:

  • 如何切換線程
  • 如何通知下一個需要進行的任務
  • 如何去管理這些任務
  • 如何能更好的更直觀的去看去使用

一、如何切換線程

首先我們要知道如何進行基礎的線程切換方法,如下:

  1. 主線程
    原理:比較通用的方法是獲取主線程的handler,然後使用post(runnable r)方法,從寫 run()方法,run()方法內的代碼就是在主線程內運行,即使你運行這段代碼在其他線程。

    代碼:

    //初始化基礎數據
    Looper looper=Looper.getMainLooper();  //構造主線程handler關鍵
    Handler handler=new Handler(looper); //將主線程的looper放入
    //運行在主線程的方法
    public static void runInUiThread(Runnable r) {
        if (handler != null) {
            handler.post(r);
        }
    }
  2. 子線程
    原理:正常情況下可以直接new Thread,然後調用start()方法,此處考慮到線程的重用,採用線程池。

    代碼:

    //初始化基礎數據
    private static ExecutorService mCacheExcutor = Executors.newCachedThreadPool();//初始化線程池,用於切換到線程
    //運行在子線程的方法
    public static void runInBackGroundThread(Runnable r) {
        if (mCacheExcutor != null) {
            mCacheExcutor.execute(r);
        }
    }

    通過如上的步奏就已經知道如何將線程切換至主線程和子線程了,下面我們將如何將任務聯繫起來。


二、如何通知下一個任務?
我們要做的不能是沒有什麼用途的工具,所以在此我們考慮一下什麼時候需要進行線程切換?舉個例子,如果你需要從數據庫或者網絡獲取數據,然後刷新ui,這時候你可以採用原始的線程方式,然後內部多次嵌套,代碼如下:

  1. 嵌套執行

    handler.post(new Runnable() {
            @Override
            public void run() {
            //顯示進度條
                new Thread() {
                    @Override
                    public void run() {
                        super.run();
                        //下載或者獲取內容
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                            //關閉進度條
                            }
                        });
                    }
                }.start();
            }
        });

    看到這樣的代碼只有絕望。。。估計沒有多少人願意讀,更不會想修改,當然我們也可以用google給封裝好的,其代碼如下:

  2. AsyncTask方式

    class DownloadTask extends AsyncTask<Void, Integer, Boolean> {  
    
    @Override  
    protected void onPreExecute() {  
        //主線程
    }  
    
    @Override  
    protected Boolean doInBackground(Void... params) {  
      //子線程
    }  
    
    @Override  
    protected void onProgressUpdate(Integer... values) {  
        //主線程
    }  
    
    @Override  
    protected void onPostExecute(Boolean result) {  
      //主線程 
    }  
    }  
    • 這個看起來確實挺直觀的,代碼邏輯也簡單了不少,可是如果需要多次切換呢,比如:主線程->子線程->主線程->子線程。。。這時候AsyncTask 就無法勝任了,當然目前還有個更加優秀的開源庫,本庫的思想也借鑑於此,這個庫更加酷炫(Rxjava),有興趣的可以看一下,我們開講我們的庫能幹什麼。
      我們來完成這個主線程->子線程->主線程->子線程變態任務,如果我們的庫去寫如何實現呢?代碼如下:
  3. 當前庫的寫法

    
    TaskManager.getNewInstance().addTask(new Task() {
            @Override
            public void runInTask() {
            //主線程
            }
        }).addTask(new Task(true) {
            @Override
            public void runInTask() {
            //子線程
            }
        }).addTask(new Task() {
            @Override
            public void runInTask() {
            //主線程
            }
        }).addTask(new Task(true) {
            @Override
            public void runInTask() {
            //子線程
            }
        }).start();
    • 現在我們開始解剖Task 的設計,代碼如下:
public abstract class Task implements Runnable {
    //下一個任務
    private Task afterTask;
    //標誌爲是否在線程中執行
    private boolean isBackGround = false;
    /**
     * 構造函數
     *
     * @param isBackGround 標誌:是不是子線程執行
     */
    public Task(boolean isBackGround) {
        this.isBackGround = isBackGround;
    }

    /**
     * 默認是ui線程
     */
    public Task() {
        this.isBackGround = false;
    }

    public Task getAfterTask() {
        return afterTask;
    }

    public boolean isBackGround() {
        return isBackGround;
    }

    @Override
    public void run() {
        if (isBackGround) {
            ThreadUtils.runInBackGroundThread(new Runnable() {
                @Override
                public void run() {
                    runInTask();
                    runAfterTask();
                }
            });

        } else {
            ThreadUtils.runInUiThread(new Runnable() {
                @Override
                public void run() {
                    runInTask();
                    runAfterTask();
                }
            });
        }
    }

    protected void runAfterTask() {
        if (afterTask != null) {
            afterTask.run();
        }
    }

    public abstract void runInTask();

    /**
     * 添加下一個task
     *
     * @param task 需要添加的task
     * @return 返回當前添加的task用於下次添加
     */
    public Task addNextTask(Task task) {
        setAfterTask(task);
        return task;
    }

    private void setAfterTask(Task afterTask) {
        this.afterTask = afterTask;
    }
}

主要方法在run()方法內,根據自身的線程狀態位來判斷當前需要在什麼環境下執行,然後回調runInTask(),也就是暴露到外部的方法,然後執行下一個任務的run()方法,這樣就完成了任務之間的關聯,而且通過剛纔講過的線程工具方法,進行線程的切換。

三、如何取管理這些任務?

  1. 從上面使用的時候的代碼,通過TaskManager進行任務的添加並且使用start()方法開啓任務。
    TaskManager代碼如下:
  2. TaskManager代碼
public class TaskManager {

    //第一個任務
    private Task firstTask = null;
    //上一個任務
    private Task lastTask = null;

    private TaskManager() {

    }

    public static TaskManager getNewInstance() {
        return new TaskManager();
    }

    public void clearTask() {
        firstTask = null;
        lastTask = null;
    }

    public TaskManager addTask(Task task) {
        if (firstTask == null) {
            lastTask = firstTask = task;
        } else {
            lastTask.addNextTask(task);
            lastTask = task;
        }

        return this;
    }

    public void start() {

        if (firstTask != null) {
            firstTask.run();
        } else {
            throw new NullPointerException("任務不能爲空!");
        }

    }


}

主要是兩個關鍵的地方,一個是記錄第一個,用於開啓整個任務,另一個是記錄上一個,用於添加下一個任務

四、如何讓邏輯更加簡練?

我們的庫採用的是鏈式調用,既保證了線程的切換,也保證了邏輯的連貫性,邏輯更容易理解和易懂。

源碼 csdn , 源碼 github

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章