Android 高效能執行緒
Rick 三竹資訊
今天會說
Thread
Handler
HandlerThread
Thread Pool
AsyncTask
Service
如果有時間會偷渡一下
Binder
AIDL
Messenager
為什麼要用執行緒?
情境-排隊買早餐
B先生:燒餅1000個 豆漿1000個
A小姐:饅頭1個 無糖豆漿1杯
C歐巴桑:蛋餅2份 奶茶大杯 快點好嗎?
由於B先生買太多早餐
所以C歐巴桑只好改吃午餐 ㄏㄏ
使用一個Thread可以這樣寫
public class MyTask implements Runnable {
public void run() {
//execute your task
}
}
//...
public static void main(){
new Thread(new MyTask()).start();
}
所以你可以這樣安排買早餐
public class Task1 implements Runnable {
public void run() {
//蛋餅*1000 豆漿*1000
}
}
public class Task2 implements Runnable {
public void run() {
//饅頭*1 豆漿*1
}
}
//歐巴桑去隔壁買惹...
多執行緒優缺點
優點
同時進行多個任務, 效率高。
缺點
程式複雜度變高。
增加資源消耗(context switch、memory)。
資料不一致(執行緒不安全)。
執行緒不安全
int mVar = 0;
public class Task1 implements Runnable{
public void run(){
mVar++;
}
}
public class Task1 implements Runnable{
public void run(){
mVar--;
}
}
資料不一致
mVar 會因為共同存取的關係
最終結果可能會出現 -1, 0, 1。
你需要一把鎖(Lock)
Java的關鍵字synchronized可以幫你建立一個臨界空間, 防堵同時共用相同記憶體區段,
所造成的資料不一致。
可是...
卻會產生一些問題
情境-排隊上廁所
popo
pipi
Blocking
Deadlock
paper?
toilet?
解法:當PM看書
我的Java程式比你的快10倍:從概念到工具的極度優化
http://www.books.com.tw/products/0010654720
Java Threads 深度探討
http://www.tenlong.com.tw/items/9867794486?item_id=24928
等待另一個Task的結果
我們必須採取非同步(Asynchronous)的方式來處理
一般會有兩種處理方式
回呼(callback)
輪詢(polling)
物件如何交換資料?
傳遞給另一個實體引用
class A{
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
class B{
private A a;
public B(A a){
this.a = a;
}
}
class A{
private C c;
public A(C c){
this.c = c;
}
}
class B{
private C c;
public B(C c){
this.c = c;
}
}
透過第三方協力實體存取
Activity怎麼交換資料?
由於Activity是Framework幫我們建立的, 因此取不到實體。
Android提供了兩種方式讓開發者來交換資料
-
Application
-
Intent
Android如何傳遞訊息?
-
Handler
-
AsyncTask
-
Binder
-
Service
-
IntentService
-
其他還有一些
邪魔歪道非官方的方法(EventBus、Otto、AndroidBus、RxJava…等)
Android一開始會建立一個Main Thread,
專門用來處理UI,
因此必須把處理非UI的任務,
丟到其他Thread去執行,
否則會造成UI Thread在循序處理時,
後面任務在等待前面任務完成才能繼續往下執行。
Android Thread的運作
UI沒有及時處理會怎樣?
如果需要更新畫面怎麼辦?
如果Background Thread執行完畢, 需要畫面進行更新,
則會透過Handler來通知Main Thread。
Handler機制
Handler是Android特有的機制,
透過他可以跟Main Thread進行溝通。
new Thread(new Runnable() {
public void run() {
//這邊是背景thread在運作, 這邊可以處理比較長時間或大量的運算
((Activity) mContext).runOnUiThread(new Runnable() {
public void run() {
//這邊是呼叫main thread handler幫我們處理UI部分
}
});
}
}).start();
new Handler(mContext.getMainLooper()).post(new Runnable(){
public void run(){
//處理少量資訊或UI
}
});
view.post(new Runnable(){
public void run(){
//更新畫面
}
});
Runnable
Handler
Handler
Message Queue
Message
Message
Message
Looper
UI Thread
Runnable
自行定義Looper機制
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輕鬆使用Looper機制
HandlerThread handlerThread = new HandlerThread("HandlerThread");
handlerThread.start();
Handler mHandler = new Hanlder(handlerThread.getLooper()){
public void handleMessage(Message msg){
super.handleMessage(msg);
switch(msg.what){
case 1:
Logger.e("message receive");
break;
}
}
}
handler.sendEmptyMessage(1);
//或者
new Hanlder(handlerThread.getLooper())
.post(new Runnable(){
public void run(){
//長時間任務1
//長時間任務2
}
});
HandlerThread優缺點(範例)
優點
執行緒安全(循序執行)。
實作簡單。
程式碼乾淨。
缺點
它就只是一個執行緒 無法並行處理任務
(os: 一個不夠快 那你有沒有開第二個啊?)
任務要循序執行且更新畫面
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();
如果改用HandlerThread來做
HandlerThread mHandlerThread = new HandlerThread("my handler thread");
mHandlerThread.start();
Handler mHandler = new Handler(mHandlerThread.getLooper());
mHandler.post(task1);
// ... n-1 task
mHandler.post(taskN);
並行Thread可以使用ThreadPool
ThreadPool的好處
Java提供了ThreadPoolExecutor讓我們使用, 有幾個優點:
-
Thread能保持存活, 等待新任務, 不會隨著任務建立再銷毀。
-
Thread Pool限制最大Thread數量, 避免系統浪費。
-
Thread的生命週期被Thread Pool控制。
(OS: 你的需求我們都聽到了)
ThreadPoolExecutor自行定義
ThreadPoolExecutor executor = new ThreadPoolExecutor(
int corePoolSize,
int maxPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue
);
core pool size(核心緩衝池數量):
Thread數量不會低於這個數字。
maxumum pool size(最大緩衝池數量):
Thread pool的Thread最大數量
可根據底層硬體來決定數量。
int N = Runtime.getRuntime().availableProcessors();
keep-alive time(最大閒置時間):
超過閒置時間, 系統會回收core Thread數量以上的Thread。
task queue type(任務佇列類型):
根據策略不同, 所用的演算法也會不同。
內建Executor
固定尺寸執行器 : 固定大小。
Executors.newFixedThreadPool(2);
動態尺寸執行器 : 動態調整, 超過60秒沒做事就被刪掉。
Executors.newCachedThreadPool();
單一執行緒執行器 : 就一個Thread, 後來的任務要排隊。
Executors.newSingleThreadExecutor();
開發處理圖片的方法給內部使用
public Bitmap getRemoteBmp(String url){
final Data result = fetchData(url);
byte[] image = downloadAvatar(data.getUrl());
Bitmap bmp = decodeImg(img);
//to do something
return bmp;
}
你可以用這樣寫
但是會造成一些缺點
不易重新使用
資源無法控制
容易造成callback hell
呼叫者不知道是否要自己開Thread執行
Callable搭配Future
Future未來可能會回傳結果,
透過blocking直到long task完成, 回傳物件。
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。
ExecutorCompletionService的使用(範例)
ExecutorCompletionService可以包裹Callable的Task,
當任務結束以後, 可回傳單一結果,
直到所有任務都結束, 則進行關閉。
跟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
}
}
參數解說
-
Params 輸入背景任務的資料。
-
Progress 由Background Thread跟UI Thread報告進度。
-
如果要跟UI Thread報告進度, 則可呼叫publishProgress方法。
-
Result 由Background Thread跟UI Thread報告結果。
AsyncTask的狀態
PENDING : 實體化但未執行execute。
RUNNING : execute已經呼叫。
FINISHED : onPostExecute或onCancelled已經被呼叫。
以上三個狀態都是不可逆的, 一旦進入了RUNNING狀態, 就無法再啟動新的執行,必須重新產生新的實體。
可透過AsyncTask.getStatus()取得狀態資訊。
AsyncTask執行順序
API Level | execute | executeOnExecutor |
---|---|---|
1-3 | 循序 | 無 |
4-10 | 並行 | 無 |
11-12 | 並行 | 循序/並行(可自訂) |
13+ | 循序 | 循序/並行(可自訂) |
AsyncTask自訂Executor(範例)
AsyncTask允許你自訂執行器, 可參考前面所講的內建執行器
//循序
executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, Object... objs);
//並行
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Object... objs);
//自訂
executeOnExecutor(CustomExecutor, Object... objs);
沈默的AsyncTask
AsyncTask<Void, Void, Void>
背景執行緒無法跟UI執行緒溝通, 沒有任何回傳結果。
毫無反應就只是個NPC。
不溝通的AsyncTask
只實做doInBackground
沒有提供報告進度或結果, 就只是個背景任務,
就像初音只是個軟體一樣。
Process的生命週期
根據官網所提供的資訊, Android在運行多個行程, 會將行程區分成五種等級, 依據等級來判斷該行程是否可進行回收。
Foreground Process
Visible Process
Service Process
Background Process
Empty Process
Foreground Process!
-
當Process內的Activity進入了onResume。
-
某個Service綁定了某個Activity且是處於onResume的狀態。
-
Service使用了startForeground方法
-
正在執行onCreate、onStart或onDestroy
-
BroadcastReceiver正在執行onReceive
Visible Process!
-
某Activity進入了onPause, 但是User仍看的到(Dialog)。
-
綁定某個Visible Activity的service。
Service Process!
-
已經使用startService或者bindService(通常是音樂撥放或者網路下載)
Background Process!
-
Activity進入onStop, 且已經不可見, 系統會根據LRU演算法, 對Background Process進行回收。
Empty Process!
-
沒有任何活動的Process, 系統優先回收的Process。
為什麼要使用Service?
-
將元件的生命週期跟Thread的生命週期分開(在Thread結束前無法釋放物件導致Memory leak)
-
當一個Process內只剩下Thread在執行, 避免Process被系統意外回收, 導致Thread被提前結束
Service的生命週期
Service分成兩種形式
啟動(Start)的Service
public class ServiceDemo extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
start service解說
-
啟動的service非常簡單, 只要覆寫onBind並且回傳null, 接著再多覆寫onStartCommand, 將Thread寫進此方法即可。
-
透過startService以及stopService控制啟動即結束, 也可以透過Service.stopSelf自行結束。
Service的重啟
有時候在系統記憶體吃緊的時候, 會將系統某些Service收起來, 這時候有幾個參數可以讓系統替你重新啟動Service。
-
START_STICKY : Service被殺掉, 系統會重啟, 但是Intent會是null。
-
START_NOT_STICKY : Service被系統殺掉, 不會重啟。
-
START_REDELIVER_INTENT : Service被系統殺掉, 重啟且Intent會重傳。
連繫(Bound)的Service
private LoaclServiceConnection mLoaclServiceConnection = new LoaclServiceConnection();
public class ServiceDemo extends Service {
private MyBinder mBinder = new MyBinder();
private class MyBinder extends Binder{
public ServiceDemo getService(){
return ServiceDemo.this;
}
}
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
}
連繫(Bound)的Service(續)
private class LoaclServiceConnection implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//透過Binder調用Service內的方法
}
@Override
public void onServiceDisconnected(ComponentName name) {
//service 物件設為null
}
}
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
bindService(new Intent(this, ServiceDemo.class),
mLoaclServiceConnection, Context.BIND_AUTO_CREATE);
}
}
IntentService(範例)
public class IntentServiceDemo extends IntentService {
public IntentServiceDemo(String name) {
super(name);
}
@Override
protected void onHandleIntent(Intent intent) {
//背景執行緒
}
}
IntentService解說
如果你需要Service循序執行任務, 可以透過IntentService來完成, 背景是透過一個HandlerThread來進行排程。
不需要使用的時候, 系統會自動幫你回收。
IntentService vs. Service
Service
由Client端控制 : User自行控制啟動跟結束
並行任務執行 : 啟動多個Thread
循序且可以重新安排的任務 : 任務可以被賦予優先權 e.g. 音樂服務
IntentService
循序執行
比較
執行緒安全 | 平行處理 | 可與UI溝通 | |
---|---|---|---|
Thread | no | yes | no |
HandlerThread | yes | no | no |
Thread Pool | no | yes | no |
AsyncTask | no | yes | yes |
Service | no | yes | no |
IntentService | yes | no | no |
Q & A
跨行程
Binder解說
Binder能讓應用程式在不同行程裡的執行緒之間傳遞函式與資料(方法呼叫)。
客戶端行程呼叫transact方法, 伺服端則會在onTransact接收。
交易資料由Parcel物件所組成,
根據下面網站效能比較, Parcelable比Serializable和GSON更有效率。
http://blog.prolificinteractive.com/2014/07/18/why-we-love-parcelable/
透過下列網站可以輕鬆將物件轉換成Parcelable。
http://www.parcelabler.com/
AIDL(Android Interface Definition Language)
AIDL(Android介面定義語言): 當行程想開放功能性給其他行程時, 可透過AIDL來進行定義, 編譯後會產生支援行程間溝通(IPC, Interprocess communication)的java檔案。
同步RPC(Remote Procedure Call)
如果你在aidl內定義
interface IMyAidlInterface {
String getThreadName();
}
同步RPC
server端以實作方式實現
private IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub(){
@Override
public String getThreadName() throws RemoteException {
return MyActivity.class.getName();
}
};
AIDL客戶端接收
優點: 單純、好實做、執行緒安全
缺點: 用戶端會被Blocking
try {
IMyAidlInterface mIMyAidlInterface = IMyAidlInterface.Stub.asInterface(binder);
String threadName = mIMyAidlInterface.getThreadName();
} catch (RemoteException e) {
e.printStackTrace();
}
非同步RPC(範例)
如果要實做非同步, 方法宣告為oneway且回傳值就都是void,
取而代之的是Callback。
Server端
定義AIDL
import personal.givemepass.myapplication.IMyAidlInterfaceCallback;
interface IMyAidlInterface {
oneway void getThreadName(IMyAidlInterfaceCallback callback);
}
Server端(續)
實做Server端
private final IMyAidlInterface.Stub server = new IMyAidlInterface.Stub(){
@Override
public void getThreadName(IMyAidlInterfaceCallback callback) throws RemoteException {
String name = "aaa";
callback.handleMessage(name);
}
};
Client端RPC
定義AIDL
interface IMyAidlInterfaceCallback {
void handleMessage(String name);
}
Client端RPC(續)
實做Client端
private IMyAidlInterfaceCallback.Stub mClient = new IMyAidlInterfaceCallback.Stub(){
@Override
public void handleMessage(String name) throws RemoteException {
//client 接收
}
};
Messenger(範例)
Messenger是Android提供處理IPC最簡單的方式, 實際上它底層也是以AIDL為架構,
透過Messenger會在內部使用Handler將任務排列到Thread的Message Queue執行,
是屬於執行緒安全的機制, 也可以透過Message來進行雙向溝通。
Messenger 架構
Binder vs. AIDL vs. Messenger
Binder主要是Android為了IPC而開發出來的。
AIDL跟Messenger最終還是會透過Binder進行IPC。
如果想要同時執行多個執行緒, 那麼AIDL是比較適合的。
如果只是想要傳遞訊息, 並且實做簡單且執行緒安全, 則可以採用Messenger。
Thank you.
Android 高效能執行緒
By givemepass
Android 高效能執行緒
- 5,537