第十二課:Running in the Background(基於AndroidStudio3.2)
現在我們對UI元素和螢幕有了一些瞭解,我們需要讓它們具有響應性。響應力並不僅僅與速度有關 - 你可以在一段時間內完成多少工作。更重要的是應用程式的速度有多快。當人們說應用程式響應時,通常他們的意思是應用程式不會阻止他們做他們正在嘗試的事情去做。它不會妨礙他們。如果你曾經使用過一個只是凍結的應用程式點選某個按鈕,你可以欣賞我們正在談論的內容。它不會阻止。 想象阻止就像打電話給某人一樣。撥號時,您會聽到鈴聲和聲音你等著對方接。除非對方接聽,否則電話不能繼續。我們可以說電話是一種阻止操作,因為事情必須發生按順序。你撥打電話,打電話,另一個人拿起電話,然後你說話。都不是事情可能同時發生。所有步驟都涉及某種形式的“等待” - 或者計算術語,阻塞。
長期執行的任務 使用者可能能夠容忍日常生活中的阻塞,例如排隊續訂許可證或雜貨,或等待某人拿起電話,等等。但在使用您的應用時,他們可能不那麼寬容。即便是Android平臺也不會容忍你的應用程式,如果花費太多時間做它做的事情:WindowManager和ActivityManager是響應性的警察。使用者點選時一個按鈕,或與任何觸發事件的檢視互動,您的應用程式沒有很多時間來完成應該做的事情;事實上,它最多隻有5秒鐘被執行時殺死。到那時,你會看到臭名昭著的ANR錯誤(申請沒有響應)。把它想象成Android的BSOD(藍屏宕機)。 根據Android指南,應用程式可以在100毫秒到200毫秒之間在事件處理程式中完成一項任務 - 這不是很多時間,所以我們確實需要確保我們不會在事件處理程式中做任何太瘋狂的事情。但說起來容易做起來難,而且有幾種情況我們不會完全控制我們在裡面做的事情事件處理程式。我們可以在這裡列出其中幾個。
- 當我們read a file時 - 我們的程式需要儲存資料或讀取他們在某個時間點。 檔案IO操作可能是出了名的有時不可預測; 你只是不知道該檔案有多大。如果它太大,完成任務可能需要200多秒
- 當我們interact with a database時 - 我們與資料庫進行互動為其提供讀取,更新,建立和刪除資料的命令。與檔案一樣,有時候,我們可能會發出一個返回大量檔案的命令資料; 處理這些記錄可能需要一段時間
- 當我們 interact with the network時 - 當我們獲取資料時網路套接字,我們受網路條件的支配。 如果它是沒有擁擠或失望,這對我們有好處。 但它並不總是上升而且它是並不總是很快; 如果你編寫處理網路內部的程式碼事件處理程式,您冒著ANR的風險
- 當我們use other people’s code時 - 我們越來越依賴API構建我們的應用程式,並且有充分的理由:它們節省了我們的時間。 但我們不能總是知道這些API是如何構建的以及它們是什麼型別的他們在引擎蓋下的操作(你真的總是讀它您使用的所有API的原始碼?)
那麼,我們應該怎麼做才能使我們的應用程式不會遇到ANR? 我們當然無法避免前面列出的東西,因為大多數現代(和有用)應用程式將需要做一件或多件(或全部)這些事。 事實證明,答案是執行中的東西背景。 有幾種方法可以做到這一點,但在本節中,我們將看看執行情況我們在AsyncTask中的程式碼。
一、模擬一個長時間執行的任務
當用戶點選時“長時間執行任務”,它將模擬一個長時間執行的任務,但我們所做的只是從1開始計算到15; 計數的每個刻度需要2秒。 我們實際上是將使用者作為人質至少30秒,在此期間他不能在應用程式中做太多其他事情。
1、新建專案Async
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="317dp"
android:gravity="center"
android:text="Long running task"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView"
/>
<TextView
android:id="@+id/textView"
android:layout_width="184dp"
android:layout_height="0dp"
android:layout_marginBottom="55dp"
android:layout_marginTop="34dp"
android:gravity="center"
android:text="TextView"
android:textSize="18sp"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
package com.example.administrator.async;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity
implements View.OnClickListener{
private String TAG;
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button b = (Button) findViewById(R.id.button);
Button b2 = (Button) findViewById(R.id.button2);
tv = (TextView) findViewById(R.id.textView);
TAG = getClass().getSimpleName();
b.setOnClickListener(this);
b2.setOnClickListener(new View.OnClickListener(){
public void onClick(View v) {
Log.i(TAG, "Clicked");
}
});
}
//This whole code block is designed to simulate a time-consuming activity inside an event handler
public void onClick(View v) {
int i = 0;
while (i < 15) {
try {
//This will halt execution for 10 seconds
Thread.sleep(2000);
//Every 10 seconds, we write the value of i to the UI
tv.setText(String.format("Value of i = %d", i));
Log.i(TAG, String.format("value of i = %d", i++ ));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2、測試
這段程式碼不會走得太遠。 如果單擊,它很快就會遇到ANR錯誤“長時間執行任務”按鈕,然後單擊其他按鈕。 你會注意到你不會 能夠點選它,因為UI執行緒正在等待“長時間執行的任務完成” - 使用者介面是不再敏感。
二、AsyncTask
在上面我們遇到的問題是當一個事件處理程式時做一些冗長的事情,整個使用者介面凍結,使用者無法做太多其他事情 - 使用者被封鎖了。 AsyncTask旨在解決這些問題。 它旨在即使在執行需要相當長時間的操作時,也能使UI響應。
AsyncTask,即非同步任務,是Android給我們提供的一個處理非同步任務的類.通過此類,可以實現UI執行緒和後臺執行緒進行通訊,後臺執行緒執行非同步任務,並把結果返回給UI執行緒.
.為什麼需要使用非同步任務?
我們知道,Android中只有UI執行緒,也就是主執行緒才能進行對UI的更新操作,而其他執行緒是不能直接操作UI的.這樣的好處是保證了UI的穩定性和準確性,避免多個執行緒同時對UI進行操作而造成UI的混亂.但Android是一個多執行緒的作業系統,我們總不能把所有的任務都放在主執行緒中進行實現,比如網路操作,檔案讀取等耗時操作,如果全部放到主執行緒去執行,就可能會造成後面任務的阻塞.Android會去檢測這種阻塞,當阻塞時間太長的時候,就會丟擲Application Not Responsed(ANR)錯誤.所以我們需要將這些耗時操作放在非主執行緒中去執行.這樣既避免了Android的單執行緒模型,又避免了ANR.
.AsyncTask為何而生?
提到非同步任務,我們能想到用執行緒,執行緒池去實現.確實,Android給我們提供了主執行緒與其他執行緒通訊的機制.但同時,Android也給我們提供了一個封裝好的元件--AsyncTask.利用AsyncTask,我們可以很方便的實現非同步任務處理.AsyncTask可以在子執行緒中更新UI,也封裝簡化了非同步操作.使用執行緒,執行緒池處理非同步任務涉及到了執行緒的同步,管理等問題.而且當執行緒結束的時候還需要使用Handler去通知主執行緒來更新UI.而AsyncTask封裝了這一切,使得我們可以很方便的在子執行緒中更新UI.
----構建AsyncTask子類的泛型引數
AsyncTask<Params,Progress,Result>是一個抽象類,通常用於被繼承.繼承AsyncTask需要指定如下三個泛型引數:
Params:啟動任務時輸入的引數型別.您想將哪些資訊傳遞給後臺執行緒? 這是通常是您要更新的UI元素。 當你從中呼叫execute時 MainActivity,您需要將此引數傳遞給AsyncTask。 這個引數自動進入doInBackground方法。 在我們的例子中,這個是一個文字檢視物件。 我們希望後臺執行緒可以訪問此UI元素
Progress:後臺任務執行中返回進度值的型別.您希望後臺執行緒返回什麼型別的資訊到onProgressUpdate方法,這樣你就可以指定長時間執行的狀態對使用者的操作? 在我們的例子中,我們想要更新的text屬性文字檢視,所以這是一個String物件
Result:後臺任務執行完成後返回結果的型別.您希望使用哪種資料來指定doInBackground的狀態什麼時候完成任務? 在我們的例子中,我只是想讓它返回真實的一切順利,所以第三個引數是布林值
-----構建AsyncTask子類的回撥方法,AsyncTask主要有如下幾個方法:
doInBackground:必須重寫,非同步執行後臺執行緒要完成的任務,耗時操作將在此方法中完成.
onPreExecute:執行後臺耗時操作前被呼叫,通常用於進行初始化操作.
onPostExecute:當doInBackground方法完成後,系統將自動呼叫此方法,並將doInBackground方法返回的值傳入此方法.通過此方法進行UI的更新.
onProgressUpdate:當在doInBackground方法中呼叫publishProgress方法更新任務執行進度後,將呼叫此方法.通過此方法我們可以知曉任務的完成進度.
下圖描述了AsyncTask在此解決上例中的作用。
- MainActivity建立一個AsyncTask物件(我們基本上建立一個擴充套件AsyncTask的類)
- 呼叫AsyncTask的execute方法;在這個方法中,我們將傳遞給我們想要更新的UI元素的AsyncTask物件
- AsyncTask有各種生命週期方法,但是唯一的方法要覆蓋的強制回撥是doInBackground() - 我們將寫入要實現的操作
- 時,AsyncTask將建立一個後臺執行緒,但是這樣執行緒對我們來說是透明的; 我們不關心它,因為AsyncTask將是管理它的人
在doInBackground()方法中,我們可以定期呼叫publishProgress()。 每一次我們這樣做,執行時將呼叫AsyncTask的onProgressUpdate()方法,它將是以執行緒安全的方式完成。 在這個方法中,我們可以做一些UI更新
注意:執行緒是一系列指令,就像我們在方法中編寫的指令序列一樣。 然而,執行緒以特殊方式執行:它在後臺執行,因此它不會阻止前臺執行的任何內容(UI執行緒)。 這就是為什麼我們需要編寫需要很長時間才能完成執行緒的指令的原因
1、我們來修改AsyncTask專案。 首先,我們需要建立一個從AsyncTask擴充套件的新類。
File ➤ New ➤ Java class,新建類為Worker,擴充套件AsyncTask
package com.example.administrator.async;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.TextView;
//The AsyncTask is parameterized; it’s a generic type, so we need to pass arguments to it.
//These parameters are <Params,Progress, Result>;
public class Worker extends AsyncTask<TextView, String, Boolean> {
private String TAG;
/*
The text view is declared at the top of the class so can we access it from onProgressUpdate;
we can’t define it yet because we will only get object reference to this text view when
doInBackground gets called
*/
private TextView tv;
/*
This is the only method we are obliged to override.
Inside this is where we should put the program logic, which may take some time to complete
*/
@Override
protected Boolean doInBackground(TextView... textViews) {
/*
Now we can define the text view; it was already passed to us when MainActivitycalled the
execute()method. The parameter of this method is an array, but we know that we only passed
one UI object (the text view), so we get only the first element of the array. We can now store that
reference to TextView(tv) variable that we hoisted up in
*/
tv = textViews[0];
TAG = getClass().getSimpleName();
int i = 0;
while (i++ < 15) {
try {
Thread.sleep(2000);
//On each tick, we will call publishProgress, so it can update the UI
publishProgress(String.format("Value of i = %d", i));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return true;
}
//Use this method to communicate progress to the user
/*
This method will catch whatever values we passed to the publishProgressmethod. The
parameter of this method is, again, an array. And since we passed only one string to it, we’ll
only get the first element and set its value as the text attribute of the text view object.
*/
@Override
protected void onProgressUpdate(String... values) {
tv.setText(values[0]);
Log.i(TAG, String.format(values[0]));
}
}
2、我們基本上重新定位了MainActivity中耗時的任務,並將它放在Worker類中。 下一步是更新MainActivity中的程式碼。
package com.example.administrator.async;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity
implements View.OnClickListener{
private String TAG;
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button b = (Button) findViewById(R.id.button);
Button b2 = (Button) findViewById(R.id.button2);
tv = (TextView) findViewById(R.id.textView);
TAG = getClass().getSimpleName();
b.setOnClickListener(this);
b2.setOnClickListener(new View.OnClickListener(){
public void onClick(View v) {
Log.i(TAG, "Clicked");
}
});
}
/*
//This whole code block is designed to simulate a time-consuming activity inside an event handler
public void onClick(View v) {
int i = 0;
while (i < 15) {
try {
//This will halt execution for 10 seconds
Thread.sleep(2000);
//Every 10 seconds, we write the value of i to the UI
tv.setText(String.format("Value of i = %d", i));
Log.i(TAG, String.format("value of i = %d", i++ ));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
*/
public void onClick(View v) {
/*
Create an instance of AsyncTask Worker class. Note that the background execution of the
AsyncTask isn’t started by merely creating an instance of it
*/
Worker worker = new Worker();
/*
The execute method starts the background operation. In this method, we pass whatever we
want to update to the AsyncTask. Note that you can pass more than one UI element to the
execute method, since it will be passed as an array in the doInBackground method of the
AsyncTask
*/
worker.execute(tv);
}
}
注意:AsyncTask並不意味著執行非常冗長的操作,大約幾分鐘。通常,AsyncTaskis僅用於持續幾秒鐘的操作。 任何超過那個和WindowManager / ActivityManager可能仍會殺死該應用程式。 對於長時間執行的操作,你需要使用服務,但這超出了本課的範圍
3、執行ok