作用
android是單線程模型。如果在UI主線程中執行耗時操作。可能導致ANR(應用無響應)。系統就會彈出一個ANR對話框。用戶選擇等待或者離開應用
注意:ANR出現場景:
- 主線程被IO操作(4.0以後主線程中不允許進行網絡IO操作)阻塞。
- 主線程中進行耗時的操作。
- 主線程中進行錯的操作,如Thread.wait Thread.sleep
Android系統會監視應用響應情況:如果應用在5秒內沒有響應用戶輸入事件(如按鍵或者觸摸)或者Broadcase Receiver在10秒內未完成相關的處理都會彈出ANR。
如何避免:
- 基本的思路就是將IO操作在工作線程來處理,減少其他耗時操作和錯誤操作
- 使用AsyncTask處理耗時IO操作。
- 使用Thread或者HandlerThread時,調用Process.setThreadPriority
(Process.THREAD_PRIORITY_BACKGROUND)設置優先級,否則仍然會降低程序響應,因爲默認Thread的優先級和主線程相同。- 使用Handler處理工作線程結果,而不是使用Thread.wait()或者Thread.sleep()來阻塞主線程。
- Activity的onCreate和onResume回調中儘量避免耗時的代碼
- BroadcastReceiver中onReceive代碼也要儘量減少耗時,建議使用IntentService處理。
AsyncTask
android中可以使用Handler和AsyncTask來實現異步機制。
Handler模式需要爲每一個任務創建一個新的線程,任務完成後通過Handler實例向UI線程發送消息,完成界面的更新
AsyncTask是抽象類。開發者需要繼承後使用。
繼承AsyncTask 時需要給AsyncTask指定三個類型。
- params 任務啓動時候傳遞的參數。即調用execute()時,傳遞的參數。
- progress 該參數是在doInbackground()函數中調用publishProgress()將進程執行的值傳遞給doProgressUpdate()函數做參數。
- result 是doInbackground()返回的值得類型。該函數返回的值會傳遞給doPostExecute()函數。進行任務完成後的處理。
繼承AsyncTask必須要重寫doInbackground()。在該函數中具體執行異步任務。
- doPreExecute()(在函數doInbackground()執行之前調用做一些準備工作)
- doPostExecute()(在函數doInbackground()執行之後進行相關的處理。並且doInbackground()的返回值會傳遞給該函數做參數)
- doProgressUpdate()()(在doInbackground()函數中調用publishProgress()後該函數被調用,將進程執行的值通過publishProgress()傳遞給doProgressUpdate()函數做參數)
public class MainActivity extends Activity {
private ImageView imageView;
private ProgressBar progressBar;
public static String url="https://img3.doubanio.com/lpic/s28385426.jpg";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView=(ImageView)findViewById(R.id.iamgeview);
progressBar=(ProgressBar)findViewById(R.id.progress);
new MyAsyncTask().execute(url);
}
//注意三個參數,分別是execute(url)函數傳入參數的類型,progress的返回值,protected Bitmap doInBackground的返回值
class MyAsyncTask extends AsyncTask<String,Void,Bitmap>{
@Override
protected Bitmap doInBackground(String... params) {
//取出參數
String url=params[0];
Bitmap bitmap=null;
InputStream is=null;
try {
URLConnection urlc=new URL(url).openConnection();
is=urlc.getInputStream();
BufferedInputStream bis=new BufferedInputStream(is);
bitmap= BitmapFactory.decodeStream(bis)
is.close();
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}
@Override
protected void onPreExecute() {
super.onPreExecute();
//任務執行前進度條設爲visible。
progressBar.setVisibility(View.VISIBLE);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
//任務執行完返回下載的圖片,進度條隱藏,在子線程中更新界面
progressBar.setVisibility(View.GONE);
imageView.setImageBitmap(bitmap);
}
}
}
AsyncTask取消
public class MainActivity extends Activity {
private ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar=(ProgressBar)findViewById(R.id.progress);
new MyAsyncTask().execute();
}
class MyAsyncTask extends AsyncTask<Void,Integer,Void>{
@Override
protected Void doInBackground(Void... params) {
for(int i=0;i<100;i++)
{
publishProgress(i);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
//更新progressbar
progressBar.setMax(100);
progressBar.setProgress(values[0]);
}
}
}
該代碼段運行會出現這種問題:第一次打開應用正常執行。progressbar未更新完然後按返回鍵。再次打開應用時,應用會等上一個線程執行完然後纔會執行下一個任務。所以看到,等一會progressbar纔開始更新(等上一次執行時打開的線程執行完成)
所以避免這種情況可以讓任務執行的生命週期和activity相關聯
public class MainActivity extends Activity {
private ProgressBar progressBar;
private MyAsyncTask myAsyncTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar=(ProgressBar)findViewById(R.id.progress);
myAsyncTask=new MyAsyncTask();
myAsyncTask.execute();
}
@Override
protected void onStop() {
super.onStop();
if(myAsyncTask!=null&&myAsyncTask.getStatus()==AsyncTask.Status.RUNNING)
//cancel()方法只是設置AsyncTask爲cancel狀態,並沒有停止線程的執行。所在doInbackground()中判斷如果AsyncTask爲cancel狀態就停止循環
{
myAsyncTask.cancel(true);
}
}
class MyAsyncTask extends AsyncTask<Void,Integer,Void>{
@Override
protected Void doInBackground(Void... params) {
for(int i=0;i<100;i++)
{
if(isCancelled())
{
break;
}
publishProgress(i);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
progressBar.setMax(100);
progressBar.setProgress(values[0]);
}
}
}
總結
- AsyncTask只能在UI線程中創建和執行execute()
- 重寫的AsycTask只能由系統自動調用,不可手動調用
- 一個AsynTask只能被執行一次,多次執行可能會出現問題。如上面的例子。
- AsyncTask的本質是一個靜態的線程池,AsyncTask派生出的子類可以實現不同的異步任務,這些任務都是提交到靜態的線程池中執行。
- 當任務狀態改變之後,工作線程會向UI線程發送消息,AsyncTask內部的InternalHandler響應這些消息,並調用相關的回調函數