標注:本文為個人學習使用梗肝,僅做自己學習參考使用姐叁,請勿轉載和轉發(fā)
2018-07-10: 初稿,小冰塊加可樂真好延柠。參考博主coder-pig
2018-08-09: 二稿祸挪,在快傳的Demo中遇見的AnsyncTask的方法,重新溫習一下
0. 引言
- 本節(jié)主要講述的為Android提供的一個輕量級的用于處理異步任務的類:AnsyncTask
- 我們一般是繼承AsyncTask贞间,然后在類中實現(xiàn)異步操作贿条,然后將異步執(zhí)行的進度,反饋給UI主線程
參考文獻
1. 相關概念
1.1 多線程的概念
- 應用程序(Application):為了完成特定任務增热,用某種語言編寫的一組指令集合(一組靜態(tài)代碼)
- 進程(Process) :運行中的程序整以,系統(tǒng)調度與資源分配的一個獨立單位,操作系統(tǒng)會為每個進程分配 一段內存空間峻仇,程序的依次動態(tài)執(zhí)行公黑,經理代碼加載 -> 執(zhí)行 -> 執(zhí)行完畢的完整過程!
- 線程(Thread):比進程更小的執(zhí)行單元,每個進程可能有多條線程凡蚜,線程需要放在一個進程中才能執(zhí)行人断! 線程是由程序負責管理的!3恶迈!而進程則是由系統(tǒng)進行調度的!F状肌暇仲!
- 多線程概念(Multithreading):并行地執(zhí)行多條指令,將CPU的時間片按照調度算法副渴,分配給各個線程熔吗,實際上是分時執(zhí)行的,只是這個切換的時間很短佳晶,用戶感覺是同時而已!
- 舉一個簡單的例子:
你掛著QQ讼载,突然想聽歌轿秧,你需要關掉QQ,然后再去啟動音樂播放器么咨堤?答案是否定的菇篡,我們直接打開播放器放歌就好,QQ還在運行著一喘,是吧驱还!這個就是簡單的多線程。 - 實際開發(fā)過程中凸克,想后臺更新议蟆,這個時候一般我們會開辟出一條后臺線程,用于下載萎战,apk咐容,但是這個時候我們會開辟出一條后臺線程,用于下載新版本的apk蚂维,但是這個時候我們還可以使用應用中的其他功能戳粒。
1.2 同步與異步
- 同步:當我們執(zhí)行某個功能時,在沒有得到結果之前虫啥,這個調用就不能返回蔚约!簡單點就是必須等前一件事做完了才能做下一件事。
- 異步:和同步是相對的涂籽,當我們執(zhí)行某個功能后苹祟,我們并不需要立即得到結果,噩夢可以正常的做其他工作,這個功能可以在完成后通知活著回調來告訴我們苔咪,還是上面那個后臺下載的例子锰悼,后臺下載,我們執(zhí)行下載功能之后团赏,我們就無需關心它的下載過程箕般,當下載結束之后通知我們就可以了。
1.3 Android為什么要引入異步任務
- 因為Android程序剛剛啟動時舔清,會同時啟動一個對應的主線程(Main Thread)丝里,這個主線程主要負責處理與UI相關聯(lián)的事件,也稱作為UI線程体谒!
- 而在Android的App時我們必須遵守這個單線程模型的規(guī)則杯聚,Android的UI操作并不是線程安全的,并且這些操作都需要在UI線程中執(zhí)行抒痒!
- 假如我們在非UI線程中幌绍,比如主線程中new Thread()另外開辟一個線程,然后直接在里面修改UI控件的值故响,會拋出異常:android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views
- 如果我們把耗時操作都放在UI線程中的話傀广,如果UI線程超過5秒沒有響應用于請求,那么這個時候會引發(fā)ANR(Application Not Responding)異常彩届,就是應用無響應
- Android4.0之后禁止在UI線程中執(zhí)行網絡操作伪冰,不然會報錯:android.os.NetworkOnMainThreadException
以上種種原因都說明了Adnroid引入異步任務的意義,AsyncTask是實現(xiàn)異步的一種方法
- 前面學習的Handler樟蠕,在Handler中寫好UI的更新贮聂,然后通過sendMessage()等方法通知UI更新,另外別忘了Handler寫在主線程和子線程中的區(qū)別哈
- 利用Activity.runOnUiThread(Runnable)把更新的ui的代碼創(chuàng)建在Runnable中寨辩,更新UI時吓懈,把Runnabel對象傳進來即可
2. AsyncTask解析
2.1 為什么要使用AsyncTask
- 我們可以使用上數(shù)兩種方法來完成我們的異步操作,假如要我們寫的異步操作比較多靡狞,活著較為繁瑣骄瓣,難道我們new Thread()然后用上述方法通知UI更新么?
- 主要是為了偷懶耍攘,既然官方已經給我們提供了AsyncTask這個封裝好的輕量級異步類榕栏,為什么不用呢,這個通過幾十行的代碼就可以完成我們的異步操作蕾各,而且進度可控扒磁。
- 相比Handler,AsyncTask顯得更加簡單快捷式曲,但是只是適合簡單的異步操作妨托,同樣適合網絡操作:圖片加載缸榛、數(shù)據(jù)傳輸?shù)龋珹syncTask暫時可以滿足初學者的需求兰伤。
- 第三方的框架内颗,比如Volley,OkHttp,android-async-http,XUtils等很多,也是異步通信
1.2 AsyncTask的基本結構
- AsyncTask是一個抽象類敦腔,一般我們都會定義一個類繼承AsyncTask然后重寫相關方法~ 官方API:AsyncTask
1.2.1 構建AsyncTask子類的參數(shù)
- AsyncTask和Handler一樣是用于處理異步的均澳,不過相對于前者,AsyncTask的代碼更加輕量級符衔,其實后臺是一個線程池找前,在異步任務數(shù)據(jù)比較龐大的時候,AsyncTask的線程池結構優(yōu)勢就體現(xiàn)出來了
-
AsyncTask<Params, progress, Result>
- Params: 啟動任務執(zhí)行的輸入參數(shù)判族,比如Http請求的URL
- Progress:后臺任務執(zhí)行的百分比
- Result:后臺執(zhí)行任務完成后返回的結果躺盛,如String、Integer等形帮,不需要指令類型的話可以寫成void
1.2.2 相關方法與執(zhí)行流程
定義一個AsyncTask類的時候一般都是繼承該類槽惫,然后將該類中的Task參數(shù)改為自己需要的參數(shù)。
class GetFileInfoListTask extends AsyncTask<String, Integer, List<FileInfo>>
onPreExecute(): 這個方法是在執(zhí)行異步任務之前的時候執(zhí)行辩撑,并且是在UI Thread當中執(zhí)行的界斜,通常我們在這個方法里做一些UI控件的初始化的操作,例如彈出要給ProgressDialog
doInBackground(Params... params): 在onPreExecute()方法執(zhí)行完之后槐臀,會馬上執(zhí)行這個方法,這個方法就是來處理異步任務的方法氓仲,Android操作系統(tǒng)會在后臺的線程池當中開啟一個worker thread來執(zhí)行我們的這個方法水慨,所以這個方法是在worker thread當中執(zhí)行的,這個方法執(zhí)行完之后就可以將我們的執(zhí)行結果發(fā)送給我們的最后一個 onPostExecute 方法敬扛,在這個方法里晰洒,我們可以從網絡當中獲取數(shù)據(jù)等一些耗時的操作
onProgressUpdate(Progess... values): 這個方法也是在UI Thread當中執(zhí)行的,我們在異步任務執(zhí)行的時候啥箭,有時候需要將執(zhí)行的進度返回給我們的UI界面谍珊,例如下載一張網絡圖片,我們需要時刻顯示其下載的進度急侥,就可以使用這個方法來更新我們的進度砌滞。這個方法在調用之前,我們需要在 doInBackground 方法中調用一個 publishProgress(Progress) 的方法來將我們的進度時時刻刻傳遞給 onProgressUpdate 方法來更新
onPostExecute(Result... result): 當我們的異步任務執(zhí)行完之后坏怪,就會將結果返回給這個方法贝润,這個方法也是在UI Thread當中調用的,我們可以將返回的結果顯示在UI控件上
為什么我們的AsyncTask抽象類只有一個 doInBackground 的抽象方法呢铝宵?打掘?原因是华畏,我們如果要做一個異步任務,我們必須要為其開辟一個新的Thread尊蚁,讓其完成一些操作亡笑,而在完成這個異步任務時,我可能并不需要彈出要給ProgressDialog横朋,我并不需要隨時更新我的ProgressDialog的進度條仑乌,我也并不需要將結果更新給我們的UI界面,所以除了 doInBackground 方法之外的三個方法叶撒,都不是必須有的绝骚,因此我們必須要實現(xiàn)的方法是 doInBackground 方法。
1.2.3 注意事項
- Task的示例必須在UI Thread中創(chuàng)建祠够;
- execute方法必須在UI Thread中調用压汪;
- 不要手動調用onPreExecute()、onPostExecute(Result)古瓤、doInBackground(Params...)止剖、onProgressUpdate(Progress...)這幾個方法;
- 該task只能被執(zhí)行一次落君,否則多次調用時會初次安異常穿香;
3. AsyncTask使用示例
3.1 使用示例1
實現(xiàn)效果圖:
布局文件:activity.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MyActivity">
<TextView
android:id="@+id/txttitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!--設置一個進度條,并且設置為水平方向-->
<ProgressBar
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/pgbar"
style="?android:attr/progressBarStyleHorizontal"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btnupdate"
android:text="更新progressBar"/>
</LinearLayout>
定義一個延時操作,用于模擬下載:
public class DelayOperator {
//延時操作,用來模擬下載
public void delay()
{
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();;
}
}
}
自定義AsyncTask:
public class MyAsyncTask extends AsyncTask<Integer,Integer,String>
{
private TextView txt;
private ProgressBar pgbar;
public MyAsyncTask(TextView txt,ProgressBar pgbar)
{
super();
this.txt = txt;
this.pgbar = pgbar;
}
//該方法不運行在UI線程中,主要用于異步操作,通過調用publishProgress()方法
//觸發(fā)onProgressUpdate對UI進行操作
@Override
protected String doInBackground(Integer... params) {
DelayOperator dop = new DelayOperator();
int i = 0;
for (i = 10;i <= 100;i+=10)
{
dop.delay();
publishProgress(i);
}
return i + params[0].intValue() + "";
}
//該方法運行在UI線程中,可對UI控件進行設置
@Override
protected void onPreExecute() {
txt.setText("開始執(zhí)行異步線程~");
}
//在doBackground方法中,每次調用publishProgress方法都會觸發(fā)該方法
//運行在UI線程中,可對UI控件進行操作
@Override
protected void onProgressUpdate(Integer... values) {
int value = values[0];
pgbar.setProgress(value);
}
}
MainActivity.java:
public class MyActivity extends ActionBarActivity {
private TextView txttitle;
private ProgressBar pgbar;
private Button btnupdate;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txttitle = (TextView)findViewById(R.id.txttitle);
pgbar = (ProgressBar)findViewById(R.id.pgbar);
btnupdate = (Button)findViewById(R.id.btnupdate);
btnupdate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MyAsyncTask myTask = new MyAsyncTask(txttitle,pgbar);
myTask.execute(1000);
}
});
}
}
3.2 使用示例2
- 下載一張網絡圖片绎速,帶有進度條的更新
public class MainActivity extends Activity
{
private Button button;
private ImageView imageView;
private ProgressDialog progressDialog;
private final String IMAGE_PATH = "http://developer.android.com/images/home/kk-hero.jpg";
// private final String IMAGE_PATH2 = "http://ww2.sinaimg.cn/mw690/69c7e018jw1e6hd0vm3pej20fa0a674c.jpg";
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button)findViewById(R.id.button);
imageView = (ImageView)findViewById(R.id.imageView);
// 彈出要給ProgressDialog
progressDialog = new ProgressDialog(MainActivity.this);
progressDialog.setTitle("提示信息");
progressDialog.setMessage("正在下載中皮获,請稍后......");
// 設置setCancelable(false); 表示我們不能取消這個彈出框,等下載完成之后再讓彈出框消失
progressDialog.setCancelable(false);
// 設置ProgressDialog樣式為水平的樣式
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
button.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
new MyAsyncTask().execute(IMAGE_PATH);
}
});
}
public class MyAsyncTask extends AsyncTask<String, Integer, byte[]>
{
@Override
protected void onPreExecute()
{
super.onPreExecute();
// 在onPreExecute()中我們讓ProgressDialog顯示出來
progressDialog.show();
}
@Override
protected byte[] doInBackground(String... params)
{
// 通過Apache的HttpClient來訪問請求網絡中的一張圖片
HttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(params[0]);
byte[] image = new byte[]{};
try
{
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
InputStream inputStream = null;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
if(httpEntity != null && httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK)
{
// 得到文件的總長度
long file_length = httpEntity.getContentLength();
// 每次讀取后累加的長度
long total_length = 0;
int length = 0;
// 每次讀取1024個字節(jié)
byte[] data = new byte[1024];
inputStream = httpEntity.getContent();
while(-1 != (length = inputStream.read(data)))
{
// 每讀一次纹冤,就將total_length累加起來
total_length += length;
// 邊讀邊寫到ByteArrayOutputStream當中
byteArrayOutputStream.write(data, 0, length);
// 得到當前圖片下載的進度
int progress = ((int)(total_length/(float)file_length) * 100);
// 時刻將當前進度更新給onProgressUpdate方法
publishProgress(progress);
}
}
image = byteArrayOutputStream.toByteArray();
inputStream.close();
byteArrayOutputStream.close();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
httpClient.getConnectionManager().shutdown();
}
return image;
}
@Override
protected void onProgressUpdate(Integer... values)
{
super.onProgressUpdate(values);
// 更新ProgressDialog的進度條
progressDialog.setProgress(values[0]);
}
@Override
protected void onPostExecute(byte[] result)
{
super.onPostExecute(result);
// 將doInBackground方法返回的byte[]解碼成要給Bitmap
Bitmap bitmap = BitmapFactory.decodeByteArray(result, 0, result.length);
// 更新我們的ImageView控件
imageView.setImageBitmap(bitmap);
// 使ProgressDialog框消失
progressDialog.dismiss();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}