來玩Android執行緒
Rick
關於我
三竹資訊 編源人
Rick Wu (吳仁弘)
大家好 我是Rick Wu
今天我要說一個關於
玩執行緒卻被執行緒玩
的故事。
朋友說想寫一個APP,
將自己放在網路上的照片下載下來
醒醒吧!你沒有朋友
所以他就買了一本Android的書來參考
過沒多久他就跑來問說
為什麼我把圖片讀出來要等很久?
而且沒辦法進行其他操作
所以看了一下他的程式碼
Image img = loadImg(url);//讀取圖片
refresh();//刷新畫面
原因就出在你把所有程式都寫在UI Thread
Thread是Process中一系列有序的執行流程
一般情況下Android App由一個Process組成
Process可以有多個Thread
每個Thread會被分配一小段的執行時間
每執行完一個Thread就跳到下一個Thread
由於轉換的速度很快
這使得一個程式可以像是同時間處理多個事務
什麼是Thread
Android分成兩種Thread
-
UI Thread 控制畫面
-
Non-UI Thread 適合處理冗長任務
長任務都應該往後面站
寫在Background Thread
哪些可能是長任務呢?
長任務包含
-
檔案、資料庫處理
-
非同步處理(檔案上傳、下載)
-
運算處理(影像處理、大量數學運算)
-
排程(時間任務)
很快的他把程式改寫成這樣
new Thread(new Runnable(){
public void run(){
Image img = loadImg(url);//讀取圖片
refresh();//刷新畫面
}
}).start();
然後程式就死掉了
因為你不能在Non-UI Thread更新UI
進行了一個更新畫面的動作
透過Handler可以讓不同的Thread進行溝通
什麼是Handler?
Handler是Android特有的機制
透過Handler可以跟UI Thread進行溝通
Handler一次送出一個訊息給UI Thread處理
Runnable
Runnable
Handler
Handler
Message Queue
Looper
Message
Message
UI Thread
Message
就像一台投籃機一次處理一顆球
要注意的點是Handler送出的訊息是循序的,
因此有可能造成訊息處理時間過長導致畫面延遲
前面的球卡住後面就全部塞住惹
這樣的App容易得到一顆星
爛透惹
givemepass
2016/10/29
於是學會Handler以後
朋友又繼續開發他的App了
朋友: 我覺得程式碼很醜!
我: 蛤?
new Thread(new Runnable(){
@Override
public void run(){
//Task 1
runOnUiThread(new Runnable(){
@Override
public void run(){
//update ui
new Thread(new Runnable(){
@Override
public void run(){
//Task 2
runOnUiThread(new Runnable(){
@Override
public void run(){
//update ui
}
});
}
}).start();
}
});
}
}).start();
這不就是傳說中的
Callback Hell?
科科
HandlerThread
可以幫你解決這個問題
HandlerThread是一個Thread
內部維護了
MessageQueue和Looper
就跟UI Thread一樣
new Thread(new Runnable() {
public void run() {
Log.e(TAG, "A");
Looper.prepare();
new Handler().post(new Runnable(){
@Override
public void run() {
Log.e(TAG, "B1");
}
});
new Handler().post(new Runnable(){
@Override
public void run() {
Log.e(TAG, "B2");
Looper.myLooper().quit();
}
});
Looper.loop();
Log.e(TAG, "C");
((Activity) mContext).runOnUiThread(new Runnable() {
public void run() {
Log.e(TAG, "D");
}
});
}
}).start();
輸出為:
- A
- B1
- B2
- C
- D
改用HandlerThread來做
HandlerThread mHandlerThread = new HandlerThread("my handler thread");
mHandlerThread.start();
Handler mHandler = new Handler(mHandlerThread.getLooper());
mHandler.post(task1);
mHandler.post(task2);
// ... n-2 task
mHandler.post(taskN);
//...
mHandlerThread.quit();
如果1張圖1個Thread
100張圖就要開100個Thread?
Thread Pool
可以解決這個問題
什麼是Thread Pool?
一組任務佇列以及工作執行緒的結合
ThreadPoolExecutor自行定義
ThreadPoolExecutor executor = new ThreadPoolExecutor(
int corePoolSize,
int maxPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue
);
透過內建ThreadPool操作
固定尺寸執行器 : 固定大小。
Executors.newFixedThreadPool(2);
動態尺寸執行器 : 動態調整, 超過60秒沒做事就被刪掉。
Executors.newCachedThreadPool();
單一執行緒執行器 : 單一Thread, 後來的任務要排隊。
Executors.newSingleThreadExecutor();
ThreadPool的好處
Java提供了ThreadPoolExecutor讓我們使用, 有幾個優點:
-
Thread能保持存活, 等待新任務, 不會隨著任務建立再銷毀。
-
Thread Pool限制最大Thread數量, 避免系統浪費。
-
Thread的生命週期被Thread Pool控制。
不僅如此,
透過內建ExecutorService會回傳一個Future物件
你聽過Future Pattern嗎?
什麼是Future Pattern
Future Pattern是一種非同步處理的程式習慣, 一開始會先讓你取得目標物件,
但是內容尚未取得,此時可以先去處理其他事情, 等待結果處理完畢後,
再透過目標物件內的方法將結果取出。
舉例來說
到電影院看電影,
你會先拿到一張票(目標物件),
此時電影尚未開始,
因此先去樓下逛街或者買東西(做其他事情), 等到時間到了,
憑著這張票就可以進場看電影(實際的資料)。
等待時間
這是一段非常耗時的程式
其他工作
其他工作
Call
Other Call
other call return
call return
Client
Service
Data
這是一段非常耗時的程式
其他工作
其他工作
Call
Other Call
other call return
立即返回
Client
Service
Data
Request
Client
Server
Future
Data
Thread
Real
Data
Result
Future Object
程式內怎麼表示呢?
Java提供內建的Future
Interface Future<V>
All Known Implementing Classes:
ForkJoinTask, FutureTask, RecursiveAction,
RecursiveTask, SwingWorker
FutureTask(Callable<V> callable)
FutureTask(Runnable runnable, V result)
Future Pattern的優缺點
-
非同步處理Task
-
可讀性高
-
方法簡單容易操作
-
不易管理
-
不好重組再利用
Future搭配Thread使用
FutureTask<Void> futureTask = new FutureTask<>(()->{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hi");
}, null);
new Thread(futureTask).start();
try {
futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
Future搭配ThreadPool使用
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Object> future = executor.submit(new Callable<Object>(){
public Object call() throws Exception{
Object obj = doLongTask();
return obj;
}
});
Object result = future.get();
在操作上是否可以更彈性?
-
如果多個任務只要一個完成就處理怎麼做?
-
如果想要全部結果完成才處理怎麼做?
-
如果想要回來一個就處理一個怎麼做?
InvokeAll與InvokeAny的操作
Executor提供同時多個Thread並行的操作。
InvokeAll : 同時並行多個Thread, 並且透過blocking來取回每一個Task的結果。
InvokeAny: 同時並行多個Thread, 只要有一個回傳成功, 則終止剩下的Task。
用單執行緒, 那就確保送進去的任務按照順序回來
如果是用多執行緒, 就無法確保回來的順序。
ExecutorService executorService = Executors.newCachedThreadPool();
ExecutorCompletionService<String> completionService =
new ExecutorCompletionService<>(executorService);
for(int i = 0; i < 100; i++) {
completionService.submit(getTask(i));
}
//...
String s = completionService.take().get();
如果你想要回來一個Task處理一個Task,
可以使用ExecutorCompletionService
Executor的確是一個很方便的工具
但是全部都還是背景執行緒
如果我要更新UI還是要透過Handler來處理
另外我可能還需要進度條的顯示下載百分比
難道沒有更方便的工具嗎?
Android早就預料你會有這樣的需求
AsyncTask已經準備好等著你來用
跟UI Thread溝通的AsyncTask
想執行長時間運行的任務,
最後再跟UI Thread進行溝通,
Android將這些特性全部封裝在AsyncTask。
如何使用AsyncTask ?
class MyTask extends AsyncTask<Params, Progress, Result>{
protected void onPreExecute(){
// in main thread
}
protected Result doInBackground(Params... params){
// in background thread
}
protected void onProgressUpdate(Progress... progress){
// in main thread
}
protected void onPostExecute(Result result){
// in main thread
}
protected void onCancelled(Result result){
// in main thread
}
}
-
PENDING : 實體化但未執行execute。
RUNNING : execute已經呼叫。
FINISHED : onPostExecute或onCancelled已經被呼叫。
以上三個狀態都是不可逆的, 一旦進入了RUNNING狀態, 就無法再啟動新的執行,必須重新產生新的實體。
可透過AsyncTask.getStatus()取得狀態資訊。
AsyncTask的狀態
AsyncTask自訂Executor(範例)
AsyncTask允許你自訂執行器
//循序
executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, Object... objs);
//並行
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Object... objs);
//自訂
executeOnExecutor(CustomExecutor, Object... objs);
AsyncTask<Void, Void, Void>
背景執行緒無法跟UI執行緒溝通, 沒有任何回傳結果。
又或者只是實作doInBackground
這樣就只是個Background Thread
不建議的使用方式
初音只是個軟體
有這些工具就可以開發出
良好使用者體驗的APP了
太棒惹
AsyncTask也使用Future Pattern
public AsyncTask() {
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));
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occured while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}
抓到!
Q & A
Executor還不夠好用
- 任務與任務之間的彼此不相關, 造成前一任務的結果必須透過變數來進行連結, 造成複雜度變高以及過於耦合難以重用。
- 如果任務之間必須傳入類似的非同步物件, 則會造成Callback hell。
ExecutorService singleThread = Executors.newSingleThreadExecutor();
singleThread.submit(new Runnable() {
@Override
public void run() {
//處理Task
runOnUiThread(new Runnable() {
@Override
public void run() {
//處理Ui
}
});
}
});
singleThread.submit(new Runnable() {
@Override
public void run() {
//處理Task
runOnUiThread(new Runnable() {
@Override
public void run() {
//處理Ui
}
});
}
});
Callback Hell
readFileAsync(args[0],
content -> processContentAsync(content,
processedContent -> handleData(content,
c-> out.println(c) ,
ex -> ex.printStackTrace(), service) ,
ex -> ex.printStackTrace(), service),
ex -> ex.printStackTrace(), service);
CompletableFuture的專業處理
CompletableFuture
.runAsync(() -> task1)
.thenRunAsync(() -> task2)
.thenRunAsync(() -> task3)
.whenComplete((r, ex) -> System.out.println("done"));
CompletableFuture
只能運行在Java 8
Android N 才能使用Java 8
RxJava
List<Task> taskList = new ArrayList<>();
taskList.add(new Task("task 1"));
taskList.add(new Task("task 2"));
taskList.add(new Task("task 3"));
Observable.from(taskList)
.subscribe(new Observer<Task>() {
private boolean isTaskSuccess = true;
@Override
public void onCompleted() {
System.out.println("completed");
}
@Override
public void onError(Throwable e) {
System.out.println("error");
}
@Override
public void onNext(Task task) {
try {
if(!task.call() || !isTaskSuccess){
isTaskSuccess = false;
System.out.println(task.getName() + " failed.");
} else {
System.out.println(task.getName() + " success.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
Thank you.
來玩Android執行緒
By givemepass
來玩Android執行緒
- 2,122