編輯:關於Android編程
在Android開發中,為了避免出現ANR現象,主要是指來自於接觸事件響應事件過長來說,我們開發者通常會將耗時長的操作,如網絡操作,大圖片加載,IO操作等等會放在子線程中去處理。
而Android中線程除了來自於Java的Thread,Executor生成器之外,還有已經封裝好的AsyncTask,IntentService,HandlerThread。
而本文就開始從AsyncTask開始說起自己的感悟吧。
AsyncTask 是一種輕量級別的異步任務類,它在線程池中執行後台人物,然後把執行進度以及結果傳到主線程中。在AsyncTask中復用了Handler和Thread。
值得注意有三點:
1.AsyncTask作為輕量級的異步操作不適宜做耗時太過長的操作,畢竟AsyncTask核心線程只有5個,緩沖隊列是10。倘若隊列
public abstract class AsyncTask{ private static final String LOG_TAG = "AsyncTask"; private static final int CORE_POOL_SIZE = 5; private static final int MAXIMUM_POOL_SIZE = 128; private static final int KEEP_ALIVE = 1; private static final BlockingQueue sWorkQueue = new LinkedBlockingQueue (10); private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread More ...newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory); private static final int MESSAGE_POST_RESULT = 0x1; private static final int MESSAGE_POST_PROGRESS = 0x2; private static final int MESSAGE_POST_CANCEL = 0x3;
從上面的代碼可以清晰的知道AsyncTask抽象類裡面調用了同步隊列長度為10的LinkBlockingQueue來對任務進行緩沖,同時聲明了一個核心線程數為5,線程池最大容量為128的線程池。(而在android5.0中同步隊列為128)
換句話說,一旦做出超出這個線程池最大容納量,就會造成後續的線程進入到阻塞狀態。
2.AsyncTask要在主線程中聲明。
且看ActivityThread中main函數,這就是我們想要知道的android對應在Java中的入口函數。
public static void main(String[] args) {
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
Security.addProvider(new AndroidKeyStoreProvider());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
從上面的入口函數可以知道,AsyncTask初始化在主線程,同時在主線程Looper了一個MainLooper,而其中AsyncTask復用了Handler,這個Handler也是來源於MainLooper。這也從側面印證了AsyncTask必須聲明在主線程.
3.不要在主線程中直接調用onPreExecute(),onPostExecute,doInBackground,on-ProgressUpdate方法。
這裡就就繼續介紹AsyncTask使用方法。首先可以從第一段代碼看到AsyncTask是一個抽象類,那麼我們必須繼承這個抽象類並且實現其中抽象方法,分別是:
1.onPreExecute
2.doInBackground
3.onProgressUpdate
4.onPostExecute
這四個方法。
這四個方法分別的作用分別是:
1.onPreExecute():執行在主線程中,在異步任務開始前,要做的准備工作。
2.doInBackground(Params…params):這個方法執行在線程池中,用於執行異步任務,Params表示異步任務參數。在這個方法中可以調用publishProgress(Progress…value)來更新任務進度,同時publishProgress將會調用onProgressUpdate。
3.onProgressUpdate(Progress…values)執行在主線程中,當後台任務執行進度發生改變時,該方法會調用。
4.onPostExecute(Result result)執行在主線程中,在異步任務執行之後,這個方法才會調用,result就是doInBackground的返回值。
下面是一個AsyncTask的簡單控制長條型進度條的Demo:
public class MainActivity extends Activity {
private ProgressBar bar;
private Button button;
private TextView text;
private Button cancel;
private DownLoadTask dtask;
private Button sIntent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bar = (ProgressBar)findViewById(R.id.progress);
button = (Button)findViewById(R.id.start);
text = (TextView)findViewById(R.id.text);
cancel = (Button)findViewById(R.id.cancel);
sIntent = (Button)findViewById(R.id.intentService);
cancel.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
if(dtask instanceof DownLoadTask){
dtask.cancel(true);
}
}
});
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
dtask = new DownLoadTask(text, bar);
dtask.execute(1000);
}
});
sIntent.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent service = new Intent(MainActivity.this, MyIntentService.class);
Log.e("service", "start");
service.putExtra("task_action", "Task1");
startService(service);
service.putExtra("task_action", "Task2");
startService(service);
service.putExtra("task_action", "Task3");
startService(service);
}
});
}
class network{
public void operate(){
try{
Thread.sleep(100);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
class DownLoadTask extends AsyncTask{
private TextView text;
private ProgressBar bar;
public DownLoadTask(TextView text,ProgressBar bar){
super();
this.text = text;
this.bar = bar;
}
@Override
protected void onPreExecute(){
bar.setProgress(0);
text.setText("Async start");
}
@Override
protected Integer doInBackground(Integer... params) {
// TODO Auto-generated method stub
network net = new network();
int value = 0;
for(int i = 0;i < 100;i++){
net.operate();
value = i;
publishProgress(i);
}
return value;
}
@Override
protected void onPostExecute(Integer result){
text.setText("AsyncTask End");
}
@Override
protected void onProgressUpdate(Integer... values){
int value = values[0];
Log.e("value", ""+value);
bar.setProgress(value);
text.setText(String.valueOf(value));
}
}
}
在上面Demo中用一個net的class來模擬反應時間較長的任務,同時每隔一定的時間就刷新UI控件。
那麼就是這麼做的:耗時的任務用處理異步任務的doInBackground來完成,而刷新UI控件的方法放在onProgressUpdate進行。這樣就沒有違背UI控件必須在主線程才能才能訪問的原則。
下面繼續來稍微的看看AsyncTask的工作流程,從上面的例子看來AsyncTask是通過execute啟動的。先跳過構造器讓我們看看吧,android5.0下面怎麼做的:
public final AsyncTask execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
從上面可以知道,execute調用了 executeOnExecutor,那麼繼續跟蹤:
public final AsyncTaskexecuteOnExecutor(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狀態以及先處理onPreExecute()這個函數,這也印證了為什麼先處理onPreExecute()這個函數了。獲取完傳入參數之後,在進行異步處理。
在這裡還要提一提AsyncTask這個異步任務和我們熟知的線程池不太一樣,默認的情況下是串行,為什麼這麼說呢?看之前的代碼:
public final AsyncTask execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
直接execute()的話,會調用默認的線程池sDefaultExecutor,而sDefaultExecutor又是什麼呢?
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
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);
}
}
}
這就應該清楚了吧,sDefaultExecutor是一個來自於SerialExecutor的類,傳入mFuture參數,這是一個在構造器完成了聲明的並發類。接著mFuture將會插入隊列中,如果沒有可運行的任務就會調用THREAD_POOL_EXECUTOR.execute(mActive);來執行下一個任務,因此是默認的。那麼又如何使其變成並發工作,而不是串行工作呢?
從上面的源碼可以看出,真正在處理線程是THREAD_POOL_EXECUTOR這個線程池,如下聲明:
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
而默認的串行執行,只是因為任務在隊列中阻塞等待。那麼我們不使用sDefaultExecutor來執行AsyncTask,轉而使用THREAD_POOL_EXECUTOR來執行即可。
如下:
new MyAsyncTask("Task1").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
即可完成並發執行。
之前我們跳過了構造器,那麼我們現在來看看裡面的構造器是怎麼實現的:
public AsyncTask() {
mWorker = new WorkerRunnable() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
return postResult(doInBackground(mParams));
}
};
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);
}
}
};
}
從上面的代碼,我們可以清晰的看見mWorker的聲明中,實現了抽象類WorkerRunnable,將mTaskInvoked設置為真,代表任務已被調用。而調用了doInBackground的方法最後會在FutureTask這個並發中執行,並且返回值給postResult()。
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult(this, result));
message.sendToTarget();
return result;
}
從上面的代碼可以清晰可見這裡面復用了Handler,將返回值通過handler傳回主線程(Hnandler是怎麼回事上一章已經總結了),記下來看看處理器怎麼處理的:
private static class InternalHandler extends Handler {
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
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;
}
}
}
可以知道,handler在處理兩個內容一個是處理發送過來的信息:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
檢查是否被調用終止,是怎停止任務,並且下次從頭開始,不是則處理onPostExecute(),來處理返回值。
另一個則是處理publishProgress方法,且看看他是怎麼發送的:
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult
這樣子就能完全明白了吧,通過publishProgress發送消息到handler之後,轉到onProgressUpdate(Progress…value)方法處理刷新進度操作。
到這裡,AsyncTask的流程就完全結束了。
在這裡要感謝任玉剛的android開發藝術探索。輔助了我去閱讀下面的源碼。
Android自定義控件之滑動解鎖九宮格
概述:滑動解鎖九宮格的分析:1、需要自定義控件;2、需要重寫事件onTouchEvent();3、需要給九個點設置序號和坐標,這裡用Map類就行;4、需要判斷是否到滑到過
【Android藍牙開發】手機藍牙與下位機HC-05藍牙模塊通信系統
本文根據自己的實踐總結而來,參考前人博客之余,也自己總結和開發了一些功能,在這裡給自己備份也分享給大家。不同之處在於:自動打開並搜索藍牙、修改藍牙名字、完整接收藍牙傳輸
android產品研發(十)--)不使用靜態變量保存數據
上一篇文章中我們講解了Android中的幾種常見網絡協議:xml,json,protobuf等,以及各自的優缺點,一般而言主要我們的App涉及到了網絡傳輸都會有這方面的內
微信公眾平台開發入門教程(圖文詳解)
在這篇入門教程中,我們假定你已經有了PHP語言程序、MySQL數據庫、計算機網絡通訊及XML語言基礎。如果你還沒有,那麼請先學習相關知識。我們將使用微信公眾賬號方倍工作室