編輯:關於Android編程
在程序開發中,為了讓程序表現的更快更流暢,我們會使用多線程來提升應用的並發性能。但多線程並發代碼是一個棘手的問題,線程的生命周期處理不好就會造成內存洩漏。
new Thread(){
@Override
public void run() {
doSomeThing();
}
}.start();
上面給出了一個最簡單的新建線程並執行任務,也是最不好管理的。為什麼?run裡面的方法很可能很耗時,直接new出來的線程沒有一個對象去接收其句柄,連打斷都做不到,只能等待內部錯誤停止或者任務執行完畢。就算不使用匿名類的方式操作,停止線程光用interrupt()是不夠的,該方法只有在線程調用sleep()、join()、wait()交出時間片(阻塞)的時候,它將接收到一個中斷異常(InterruptedException),從而提早結束該線程。所以一般都是在run方法裡各種操作之前先判斷下標志位,是否需要繼續執行任務,向外暴露這個標志位的接口,讓其變成可控線程。當然普通的thread是滿足不了你的,你需要繼承thread自行處理標志位的操作,當然如果寫成內部類的話最好寫成靜態內部類,這樣避免持有外部類的引用造成洩漏。
經常new Thread().start()的話會加大性能的開銷,雖然並發量上去了(不要無止盡的新建線程,處理線程跟cpu核數有關,線程數超出CPU核數過多反而會降低效率,並發得不償失,因為切換線程是要切換上下文環境的,此操作開銷十分大),但是開銷變大了。所以管理好線程的個數、復用成為重中之重。
你以為這樣就結束了?
打出上面那段話我突然想起小時候看的《西游記後傳》裡的一句經典台詞——我還沒出力呢,你就趴下了。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMiBpZD0="android多線程性能優化前篇-ui線程源碼剖析">Android多線程性能優化前篇-UI線程源碼剖析
ps:其實真正的標題在這(>_<:.),其實原本沒打算寫ui線程的,在整合資料的時候看到了,就順便寫寫吧,沒想到一寫就寫那麼多…標題都對不上號了,只好修改了
上面說了Thread不足的地方,各位可以思考下有什麼解決方案,下篇我再給大家介紹幾個方案。
操作UI線程
- Activity.runOnUiThread(Runnable)
- View.post(Runnable) 、 View.postDelay(Runnable , long)
- Handler
- AsyncTask
- IntentService
首先是Handker這個大家天天用應該不用我說了吧
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
上面給出了runOnUiThread的源碼,看到關鍵對象mHandler就知道了,當前線程如果不在主線程(UI線程)就調用activity內的全局handler進行UI操作。PS:在我打開源碼之前就想到裡裡面估計也是有一個Handler來執行UI操作,沒想到打開居然中標了,感覺最近還是有些許提升的。哈哈,裝完逼,我繼續講~
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().post(action);
return true;
}
public boolean postDelayed(Runnable action, long delayMillis) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.postDelayed(action, delayMillis);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
return true;
}
從源碼可以看出當attachInfo不為空的時候會調用其內部持有的handler進行UI操作。那AttachInfo是什麼鬼呢?
/**
* A set of information given to a view when it is attached to its parent
* window.
*/
final static class AttachInfo{
//...
}
//在View中找到該終類,注釋說明將一組信息附於父窗口
/**
* Creates a new set of attachment information with the specified
* events handler and thread.
*
* @param handler the events handler the view must use
*/
AttachInfo(IWindowSession session, IWindow window, Display display,
ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
mSession = session;
mWindow = window;
mWindowToken = window.asBinder();
mDisplay = display;
mViewRootImpl = viewRootImpl;
mHandler = handler;
mRootCallbacks = effectPlayer;
}
看到該構造就能知道很多東西了,會在別的地方傳進一個handler,並且這個是ui線程的,為什麼,這是根據注釋猜測其來源應該是一個父視圖(不是UI線程早就報錯了>_<),在View中是找不到這個構造的調用的,既然是父視圖那就找找看吧,ViewParent是個接口pass,ViewGroup也找不到pass,並且View和ViewGroup裡的注釋也都沒有線索,接著又是各種搜跟視圖的Window、PhoneWindow、DecorView等地方全都找不到,後面找著找著又回到了我之前找到的ViewRootImpl的這個類,打算後面一些寫的,沒想到居然在這裡找到了。我的淚啊猶如西湖的水~
ViewRootImpl首先這個類在正常情況下是看不到的,為什麼?因為@hide 了呗,被隱藏起來了,我怎麼找到的呢?打開sdk目錄下的sources(前提是你有下源碼),找到對應的api的目錄裡,打開搜索搜索類名,哈哈這樣以後遇到打不開源碼找不到的類你也可以用這種方法試試了,說不定就隱藏在源碼中,至於怎麼在ide中查看,我看到有人改源碼吧@hide去掉,然後再編成android.jar 哈哈 這樣太累了,多人開發的話 別人沒有這個jar是要報錯的(直接使用一些hide了的類),反射的話就沒問題。至於其他方法求大神告知~(我猜想應該有這類插件把@hide字符串過濾的類顯示出來)
public ViewRootImpl(Context context, Display display) {
//...
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
//...
}
final class ViewRootHandler extends Handler {
//...
}
final ViewRootHandler mHandler = new ViewRootHandler();
在構造ViewRootImpl的時候就會new AttachInfo並把信息放入,在後面看到了mHandler的實現,繼承了Handler,構造並沒有修改,按照前一篇介紹Handler的文章裡提到的,Handler的默認構造獲取當前線程的Looper進行關聯,從ViewRootImpl的構造和傳入的Context就可以知道這肯定是運行在UI線程的構造。
接下來就是ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
RunQueue這個也在ViewRootImpl內部,裡面維護了一個HandlerAction的ArrayList,HandlerAction這個裡面封裝的是runnable action 和 long delayMillis,就是一個任務。postDelayed()就是把任務加入到集合中,這個add操作是synchronized塊進行同步的。
void executeActions(Handler handler)這個方法傳入一個handler用於執行集合中的任務。PS:至於源碼我就不貼了,讓大家自個動手試試搜hide的類。
AsyncTask
這個類想必大家都很熟悉了吧,我做Android以來第一個接觸的前後台都能耍的線程就是它,不過後來就很少用了,至於原因代碼書寫起來比較麻煩,看著不夠優雅,生命周期管理麻煩每個AsyncTask都需要處理等原因(ps:說白了你就是懶!為什麼不直說呢)。
先來看看其需要重寫的幾個方法
protected abstract Result doInBackground(Params... params);
protected void onPreExecute() {
}
protected void onProgressUpdate(Progress... values) {
}
protected void onPostExecute(Result result) {
}
doInBackground作為AsyncTask必須重寫的關鍵函數,該方法運行於後台線程。
//於AsyncTask構造函數中
mWorker = new WorkerRunnable() {
public Result call() throws Exception {
mTaskInvoked.set(true);
//設置該線程優先級為後台線程
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
return postResult(doInBackground(mParams));
}
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult(this, result));
message.sendToTarget();
return result;
}
mFuture = new FutureTask(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);
}
}
};
WorkerRunnable只是一個實現了Callable接口的抽象類(然而實際實現推遲到了子類)。
FutureTask是一個實現了RunnableFuture,看名字就知道跟Runnable有關,其繼承了Runnable, Future這兩個接口。new FutureTask時把Callable放入用作為全局參數,在run中調用Callable.call方法來實現線程啟動是運行call,即運行doInBackground。
又看到Handler類真是親切啊(怎麼到處都有你,誰讓我要更新UI呢…)
該Handler只是重寫了handleMessage方法,調用AsyncTask各方法。
public void handleMessage(Message msg) {
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
啟動AsyncTask
public final AsyncTask execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
public final AsyncTask executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
mStatus = Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
在調用execute方法執行時會調用靜態的全局線程池來執行該方法,在執行之前運行onPreExecute(),然後才在線程池中執行mFuture即Runnable。
在duInBackgound中調用pushlishProgress方法,通過handler執行UI操作
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult
AsyncTaskResult中持有AsyncTask對象
在初始化WorkerRunnable時實現的Call函數中return postResult(doInBackground(mParams));
即doInBackground執行之後執行postResult。postResult也沒做什麼就是用handler發了一個消息而已。
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult(this, result));
message.sendToTarget();
return result;
}
收到消息的handler回去執行result.mTask.finish(result.mData[0]);
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
isCancelled()其實就是一個停止運行的標記位而已,就跟停止Thread一樣,調用cancel(boolean mayInterruptIfRunning)來停止線程。
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}
mayInterruptIfRunning這個參數是用來判斷是否使用interrupt()方法停止doInBackground。
onCancelled(result)這個函數看名字就很明顯了,生命周期函數,執行時機上面的源碼已經有涉及了,停止線程後,finish中調用。
至此AsyncTask你必須知道的流程實現也就大概清楚了。
作為一個輕量級的異步線程,其優點我就不說了,說說缺點。
首先是AsyncTask不可重用。從寫法來看就知道了execute();每個AsyncTask對象只能調用一次execute(),否者就會拋異常
public final AsyncTask executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
//...
}
public static void execute(Runnable runnable) {
sDefaultExecutor.execute(runnable);
}
execute(Runnable command)該方法是靜態的,不需要實例調用(剛才誰說的只能調用一次的?),然而這個方法我不推薦,因為線程不可控,不能打斷。
sDefaultExecutor這個是什麼?Executor 是個接口,是線程池的基礎接口,裡面的只有一個execute(Runnable command)方法,用來執行任務。線程池是什麼?字面意思就是很多線程在其中的一個容器,至於具體的由下一篇來解釋。
exec.execute(mFuture);AsyncTask任務最終執行的地方就是這個Executor 的實現類中。
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
該線程池是全局的,也就是說不管有多少個AsyncTask都是在這個線程池中執行。
private static class SerialExecutor implements Executor {
final ArrayDeque mTasks = new ArrayDeque();
Runnable mActive;
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
SerialExecutor 的源碼已經暴露了一切,任務可以添加多個,可是永遠只能執行一個,並發不了多個。所以雖然AsyncTask的execute(Runnable runnable)的靜態方法可以調用多次也不用書寫繁重的AsyncTask然而人家並發量始終如一。
當然還有其他方法可以實現並行任務
public final AsyncTask executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
mStatus = Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
那就是傳入一個線程池,讓任務運行在這個線程池裡(但這個函數不是靜態的需要實例調用,而且從源碼上看也是不能二次使用的)…我自己用個線程池那我還用AsyncTask干嘛呢,Executor + Handler不就好了。
其他缺點就是持有外部引用容易造成內存洩漏,生命周期不好管理等問題。
其實開始並不打算寫多少AsyncTask的,之前也沒看過其源碼,前面剖析了源碼後,AsyncTask就停不下來了..>_<..
IntentService
剖析了這麼多源碼我想你們也發現了,只要是更新UI的操作,必i定會Handler的參與,這個就留給你們自行剖析,或者等我哪天開了Service再帶你們飛
首先上效果圖,實現如下效果: @Override protected void onCreate(Bundle savedInstanceState) {
一、首先說明:藍牙通信必須用手機測試,因為avd裡沒有相關的硬件,會報錯! 好了,看看最後的效果圖: 二、概述: 1.判斷是否支持BluetoothBluet
最近項目裡要做一個簡單的曲線圖來標識數據,開始以為很簡單,android已經有那麼多的開源圖表庫了,什麼achartenginee,hellochart,mpandroi
最近下了個攜程App,點開首頁看,注意到其按鈕在點擊的時候並不是我們經常看到的變色效果,而是先收縮,放開時,再回到原來的大小,感覺這個效果雖然小,但是感覺非常新穎,於是決