Android 開發學習筆記
基本概念
Android 應用程式由一些零散的有聯絡的元件組成,透過一個工程 manifest 繫結在一起。在 manifest 中,描述了每一個元件以及元件的作用,其中有 6 個元件,它們是 Android 應用程式的基石。Android 有四大元件(也有說六大元件的,外加 Intent 和 Notification),分別是 Activity,Service,Content Provider 和 BroadcastReceiver。這四大元件一起組成了完整的 Android 程式。我們將分別簡要介紹。
介面顯示與邏輯處理:
利用 XML 標記描繪應用介面,使用 Java 程式碼書寫程式邏輯。
Activity
- Activity 指一個完整的佔了一個螢幕的頁面(上下滑動的內容也算這個介面內的內容,所以它的概念可以理解成類似網站的一個網頁一樣)。
- Activity 允許顯示一些控制元件、檢視,並可以監聽處理使用者的事件,做出響應等。Activity 之間透過 Intent 通訊(呼叫、跳轉等動作)。
- 一個 Activity 實際上是一個 XML 檔案,它可以被 Android 系統以視覺化的介面展現。每一個 Activity 都與一個 Java 後臺程式相聯絡,這個 Java 程式可以控制這個頁面的啟動、展示以及資料等資訊。頁面上展示的內容可以透過 Activity 本身的 xml 檔案配置,也可以由相聯絡的 Java 檔案來控制。
Service
- Service 是服務的意思。是 Android 程式中 “不可見” 的部分,但是它負責更新資料來源、觸發通知等。是一種沒有介面的長生命週期的適合監控或者後臺執行的程式。
- 最佳的例子是多媒體播放器,多媒體播放器程式可能含有一個或多個 Activity,使用者透過這些 Activity 選擇並播放音樂。然而,音樂回放並不需要一個 Activity 來處理,因為使用者可能會希望音樂一直播放下去,即使退出了播放器去執行其它程式。為了讓音樂一直播放,多媒體播放器 Activity 可能會啟動一個 Service 在後臺播放音樂。Android 系統會使音樂回放 Service 一直執行,即使在啟動這個 Service 的 Activity 退出之後。
- Android 服務有兩種:一是本地服務,另一種是遠端服務。前者只能由託管服務的應用程式訪問,後者是指由裝置上其他應用程式進行遠端訪問的服務。
Content Provider
- Content Provider 是指內容提供器。App 執行的時候需要很多外部資料作為支撐,這些資料一般由內容提供器儲存、共享。
- 比如,我們可以配置自己的 Content Provider 來存取其他應用程式,或者是透過其他應用程式給出的 Content Provider 來獲取他們的資料。
- 系統本身也提供了一些 Content Provider,如聯絡人資訊等。這些資料可以儲存在檔案系統、SQLite 資料庫或者其他一些媒介裡。
BroadcastReceiver
- 你的應用可以使用它對外部事件進行過濾,只對感興趣的外部事件(如當電話呼入時,或者資料網路可用時)進行接受並作出響應。
- 廣播接收器沒有使用者介面,然而,它們可以啟動一個 activity 或 serice 來響應它們收到的資訊,或者用 NotificationManager 來通知使用者。
- 通知可以用很多種方式來吸引使用者的注意力──閃動背燈、震動、播放聲音等。一般來說是在狀態列上放一個持久的圖示,使用者可以開啟它並獲取訊息。
Intent
- Intent(意圖)是各種元件之間通訊的橋樑,可以用來連線 Android 的應用元件,它提供了一種在不同應用之間進行任務執行繫結的工具,它最重要的應用是啟動活動 Activity。
- Intent 是非同步訊息,可以幫助一個應用元件從另一個元件中請求功能。
- Intent 是一個物件,即
android.content.Intent
。
Notification
- Notification 是通知元件,主要是和推送使用者資訊有關。
Android App 專案中的檔案簡介
-
AndroidManifest.xml 檔案:在 mainfests 資料夾下面,叫做清單檔案,它描述了整個專案的資訊,包括專案名稱、SDK 版本等等。我們在應用程式中的每個活動必須在
AndroidManifest.xml
檔案中宣告,系統需要根據裡面的內容執行 APP 程式碼,顯示介面,注意這個檔名一個字都不能錯。<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".SmsNewActivity"> <!-- <intent-filter>--> <!-- <action android:name="android.intent.action.MAIN" />--> <!-- <category android:name="android.intent.category.LAUNCHER" />--> <!-- </intent-filter>--> </activity> <activity android:name=".NerEvalActivity"> <!-- 下面兩行程式碼設定程式入口 --> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
-
Java 資料夾:存放 java 源程式的地方。注意我們這裡有一些以 Activity 結尾的程式檔案,每一個檔案其實對應了一個 Activity 的頁面,也就是和下面資原始檔夾(res)中的 layout 裡面的內容繫結的。
-
Res 資料夾:存放 java 資源的地方,包括圖片、佈局檔案、選單等等。
-
drawable 資源:存放點陣圖的資料夾
-
layout 資源:存放的是佈局資源,也就是指 Android 裡面的活動和檢視。在 Android 中,佔用一個螢幕的 UI 介面稱之為 Activity(活動),頁面中的按鈕、標籤、文字欄位等稱之為 View(檢視)。一個活動通常包含一個或者多個檢視(也就是一個頁面裡面有按鈕,文字之類的東西)。這裡的佈局檔案都是 XML 檔案,因為 Android 中檢視都是從 XML 檔案載入的,裡面描述了位置、大小等檢視資訊。佈局資源下每個檔案都將根據其檔名(不包含副檔名)生成一個唯一的常量 ID,可以透過某些手段與 java 原始檔繫結,或者被其他頁面呼叫。
-
values 資源:是 Android 中存放陣列、顏色、尺寸、字串和樣式的資料夾。其實就是統一存放所有變數的地方,比如主題顏色、app 名稱、Logo 的樣式等,在 values 資源下統一定義可以使得我們在各個地方都呼叫同樣的資源,在修改的時候也只要更改一處即可。
/res/values/strings.xml /res/values/colors.xml /res/values/dimens.xml /res/values/attrs.xml /res/values/styles.xml # 定義資源:尖括號定義資源型別,name 表示資源名稱,裡面表示內容 <string name="app_name">樂購</string> <string name="edit_message">請輸入您想查詢的地點</string>
- minmap:存放程式啟動圖示的資料夾。一般只存放啟動圖示(就是桌面圖示)。
-
-
Gradle Scripts:工程的編譯配置檔案
build.gradle
:分為專案級與模組級,用於描述 App 工程的編譯規則proguard-rules.pro
:用於描述 java 程式碼的混淆規則,對編譯好的 class 檔案進行混淆處理,目的是防止 java 程式碼被反編譯gradle.properties
:用於配置編譯工程的命令列引數,一般無須改動settings.gradle
:配置了需要編譯哪些模組。初始內容為 include ':app' ,表示只編譯 app 模組local.properties
:專案的本地配置檔案,在工程編譯時自動生成,用於描述開發者電腦的環境配置,包括 SDK 的本地路徑,NDK 的本地路徑等- 注意每個版本的 Android Studio 都有對應的 Gradle 版本,只有二者的版本正確對應,App 工程才能成功編譯,比如 Android studio 4.1 對應的 Graddle 版本為 6.5。
Android 中的資源訪問(R 類 / R.java)
在 Android 開發中,所有的外部資源都透過其資源的 ID 來訪問,而所有的資源 ID 都在專案中 R 類中定義,而 R.java 這個類是由 aapt 工具自動生成的,使用者本身不用修改新增。只要在資源中申明瞭 ID,那麼 R 類會自動將該資源新增到其中。
編譯應用時,aapt 會生成 R 類,其中包含您的 res/
目錄中所有資源的資源 ID。 每個資源型別都有對應的 R 子類(例如,R.drawable 對應於所有可繪製物件資源),而該型別的每個資源都有對應的靜態整型數(例如,R.drawable.icon)。這個整型數就是可用來檢索資源的資源 ID。
定義資源 ID 主要包括兩個部分,一個是資源型別如 string、drawable 和 layout 等。另一個是資源名稱,不包括其副檔名(當然也可以是 xml 中 android:name 屬性中的值)。
訪問這些資源有兩種方式,一種是在 Java 程式中,一種是在 XML 檔案中。
// Java 程式中訪問資源,如下面的程式設定內容顯示為某個 activity
// 可以使用 R.layout.activity 名稱的方式。其中 layout 是資源型別,後面的是資源名稱
setContentView(R.layout.activity_display_message);
// 這種方式是在 XML 檔案中訪問資源,使用 @ 開頭表示什麼型別,然後斜槓後面寫上資源名稱
@string/hello
NDK 相關
- NDK 是 Native Development Kit 的縮寫,是 Android 的工具開發包。
- 作用是快速開發 C/C++ 的動態庫,並自動將動態庫與應用一起打包到 apk 。
參考:https://blog.csdn.net/afei__/article/details/80897404
開發步驟
建立新的 App 頁面
完整的頁面建立過程包括三個步驟:
- 在 layout 目錄下建立 XML 檔案
- 建立與 XML 檔案對應的 Java 程式碼
- 在 AndroidManifest.xml 中註冊頁面配置
右鍵之後可以一鍵建立 Activity,系統同時幫我們做好上述三件事情。
實現介面的跳轉
點選事件和長按事件:
- 監聽器:意思是專門監聽控制元件的動作行為,只有控制元件發生了指定的動作,監聽器才會觸發開關去執行對應的程式碼邏輯。
- 常用的兩種監聽器:
- 點選監聽器:透過 setOnClickListener 方法設定
- 長按監聽器:透過 setOnLongClickListener 方法設定
public class LKdemoActivity extends AppCompatActivity {
// 注意 onCreate 方法有兩個,我們需要選擇引數是 @Nullable Bundle savedInstanceState 的這一個
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lk);
// 跳轉到 MobileRecActivity
Button button = findViewById(R.id.button2);
// 點選監聽器
button.setOnClickListener((new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent();
// 設定跳轉的 Activity
intent.setClass(LKdemoActivity.this, MobileRecActivity.class);
startActivity(intent);
}
}));
TextView tv_hello = findViewById(R.id.tv_hello);
tv_hello.setText(R.string.welcome);
tv_hello.setTextSize(30);
}
}
還有一種設定監聽的方式:button.setOnClickListener(this)
// 如果監聽很多,不要建立太多類,可以公用這個類
public class LKdemoActivity extends AppCompatActivity implements View.OnClickListener {
// 注意 onCreate 方法有兩個,我們需要選擇引數是 @Nullable Bundle savedInstanceState 的這一個
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lk);
// 跳轉到 MobileRecActivity
Button button = findViewById(R.id.button2);
// 點選監聽器
button.setOnClickListener(this);
}
@Override
public void onClick(View view) {
// 有很多監聽事件,我們需要判斷是哪個按鈕被點選了
if (view.getId() == R.id.button2) {
Intent intent = new Intent();
// 設定跳轉的 Activity
intent.setClass(LKdemoActivity.this, MobileRecActivity.class);
startActivity(intent);
}
}
Button 點選之後顯示時間
直接在 layout 中的 XML 檔案中設定:需要知道呼叫的方法名稱(高耦合)
<Button
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_marginBottom="20dp"
android:layout_weight="1"
// 在此處進行設定,點選之後執行 doClick 函式
android:onClick="doClick" />
public class LKdemoActivity extends AppCompatActivity {
// bt_time 在兩個函式中都要用,所以要設定為類變數
private TextView bt_time;
// 注意 onCreate 方法有兩個,我們需要選擇引數是 @Nullable Bundle savedInstanceState 的這一個
@SuppressLint("MissingInflatedId")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lk);
bt_time = findViewById(R.id.tv_result6);
}
public void doClick(View view) {
String desc = String.format("%s 您好,歡迎使用按鈕: %s", Common.getNowTime(), ((Button) view).getText());
bt_time.setText(desc);
}
}
// 在 utils 資料夾 Common 類增加獲取當前時間的函式
public static String getNowTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(new Date());
}
在 activity 檔案對應的 xml 檔案中增加一個 button 和一個點選之後顯示內容的文字框:
<Button
android:id="@+id/button6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="顯示時間"
android:onClick="doClick"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.489"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.408" />
<TextView
android:id="@+id/tv_result6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="這裡檢視按鈕的點選結果"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.335" />
Activity
啟停活動頁面
Activity 的啟動和結束
- 從當前頁面跳到新頁面:
// Intent 的建構函式需要傳入兩個引數:上下文和目標元件的 Class 物件。
startActivity(new Intent(源頁面.this, 目標頁面.class))
- 從當前頁面回到上一個頁面,相當於關閉當前頁面
finish() // 結束當前的活動頁面
Activity 的生命週期
private static final String TAG ="ning";
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate: LKdemoActivity");
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart: LKdemoActivity");
}
// 在 Logcat 控制檯輸入 tag:ning 進行過濾篩選
onCreate()
:建立活動,把頁面佈局載入進記憶體,進入初始狀態onStart()
:開始活動,把活動頁面顯示在螢幕上,進入就緒狀態onResume()
:修復活動,活動頁面進入活躍狀態,能夠與使用者正常互動,例如允許響應使用者的點選動作、允許輸入文字等onPause()
:暫停活動,無法與使用者正常互動,Activity 處於部分可見狀態。這發生在 Activity 失去焦點但仍部分可見的時候,例如有一個對話方塊或透明的 Activity 遮蓋在其上。如果有動畫,在這裡暫停onStop()
:停止活動,Activity 處於不可見狀態。Activity 將停止顯示在螢幕上,並且它失去了使用者焦點,但仍然保留在 Activity 堆疊中onDestroy()
:銷燬活動,Activity 將被銷燬。這發生在使用者按下 "Back" 鍵或呼叫finish()
方法關閉 Activity 時。一旦 Activity 被銷燬,它就會從 Activity 堆疊中移除onNewIntent
:重用已有的活動例項
Activity 的啟動模式
我們可以在配置檔案中指定啟動模式:
<activity android:name=".LKdemoActivity" android:launchMode="standard" />
在 Android 中,Activity 的啟動模式(Launch Mode)定義了 Activity 如何在任務棧中啟動和管理。不同的啟動模式可以影響 Activity 的例項化和任務棧的行為。以下是常見的 Activity 啟動模式:
- Standard(標準模式):這是預設的啟動模式。每次啟動一個 Activity,都會建立一個新的例項,並將其放入任務棧的頂部。無論是否已經存在相同的例項,都會建立新的例項。
- SingleTop(單頂模式):在 SingleTop 模式下,如果要啟動的 Activity 已經位於任務棧的頂部(即棧頂有一個相同的例項),則不會建立新的例項,而是呼叫現有例項的
onNewIntent()
方法來傳遞新的 Intent 資料。如果 Activity 不在棧頂,仍然會建立新的例項。 - SingleTask(單任務模式):在 SingleTask 模式下,系統會確保一個特定的 Activity 只存在於一個任務棧中的一個例項。如果要啟動的 Activity 已經在其他任務棧中存在,系統會將該任務棧移到前臺,並呼叫現有例項的
onNewIntent()
方法傳遞新的 Intent 資料。如果要啟動的 Activity 在當前任務棧中已經存在,則不會建立新的例項,而是將任務棧中其他 Activity 移除,使該 Activity 變為棧頂。
單任務模型的應用場景:
- 程式主介面:我們肯定不希望主介面被建立多次,而且在主介面退出的時候退出整個 app 是最好的效果
- 耗費系統資源的 activity:設定為單任務模式可以減少資源耗費
-
SingleInstance(單例項模式):SingleInstance 是最特殊的啟動模式。在 SingleInstance 模式下,系統會為該 Activity 建立一個新的任務棧,並且該任務棧中只會存在一個例項。其他應用程式的 Activity 無法與其共享任務棧。這種模式適用於需要獨立存在且與其他應用程式隔離的 Activity。
-
在 JAVA 程式碼中動態設定啟動模式:
- 在兩個頁面之間跳來跳去:設定
setFlags
以避免重複跳轉
public void onClick(View view) { // 有很多監聽事件,我們需要判斷是哪個按鈕被點選了 if (view.getId() == R.id.button2) { Intent intent = new Intent(); // 設定跳轉的 Activity intent.setClass(LKdemoActivity.this, MobileRecActivity.class); // 棧中存在待跳轉的活動例項時,則重新建立該活動的例項,並清除原例項上方的所有例項 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); }
- 登入成功後不再返回登入介面:設定啟動標誌 FLAG_ACTIVITY_CLEAR_TASK,該標誌會清空當前活動棧裡的所有例項,不過全部清空之後,意味著當前棧沒法用了,必須另外找個活動棧才行,也就是同時設定啟動標誌 FLAG_ACTIVITY_NEW_TASK,該標誌用於開闢新任務的活動棧。這種操作可以實現從登入介面登入成功之後,無法再次返回到登入介面。
Intent intent = new Intent(); // 設定跳轉的 Activity intent.setClass(LKdemoActivity.this, MobileRecActivity.class); // 設定啟動標誌:跳轉到新頁面時,棧中的原有例項都被清空,同時開闢新任務的活動棧 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); // 跳轉到意圖指定的活動頁面
- 在兩個頁面之間跳來跳去:設定
在活動之間傳遞訊息
元素名稱 | 設定方法 | 說明與用途 |
---|---|---|
componentName | setComponent | 指定意圖的來源與目標 |
action(動作) | setAction | 指定意圖的動作行為 |
category(類別) | addCategory | 指定意圖的操作類別 |
data(資料) | setData | 指定動作要操縱的資料路徑 |
type(資料型別) | setType | 指定訊息的資料型別 |
extras(擴充套件資訊) | putExtras | 指定裝載的包裹資訊 |
Flags(標誌位) | setFlags | 指定活動的啟動標誌 |
顯式 Intent 和隱式 Intent
-
Intent 是各元件之間資訊溝通的橋樑,用於 Android 各元件之間的通訊,主要完成下列工作:
- 標明本次通訊請求從哪裡來、到哪裡去、要怎麼走;
- 發起方攜帶本次通訊需要的資料內容,接受方從收到的意圖中解析資料;
- 發起方若想判斷接受方的處理結果,意圖就要負責讓接受方傳回應答的資料內容。
-
顯式 Intent:直接指定來源活動與目標活動,屬於精確匹配,有三種構建方式:
- 在 Intent 的建構函式中指定
// 建立一個目標明確的意圖 Intent intent = new Intent(CurrentActivity.this, NextActivity.class)
- 呼叫意圖物件的
setClass
方法指定
// 建立一個新意圖 Intent intent = new Intent(); // 設定跳轉的 Activity intent.setClass(CurrentActivity.this, NextActivity.class);
- 呼叫意圖物件的 setComponent 方法指定
// 建立一個新意圖 Intent intent = new Intent(); // 建立包含目標活動在內的元件名稱物件 ComponentName component = new ComponentName(CurrentActivity.this, NextActivity.class); // 設定意圖攜帶的元件資訊 intent.setComponent(component);
-
隱式 Intent:沒有明確指定要跳轉的目標活動,只給出一個動作字串讓系統自動匹配,屬於模糊匹配
- App 不希望向外部暴露活動名稱,只給出一個事先定義好的標記串,約定俗成就好
Intent intent = new Intent(); // 設定意圖動作為準備撥號 intent.setAction(Intent.ACTION_DIAL);
向下一個 Activity 傳送資料
- Intent 使用 Bundle 物件存放待傳遞的資料資訊
- 在程式碼中傳送訊息包裹,呼叫意圖物件的
putExtras
方法,即可存入訊息包裹 - 在程式碼中接收訊息包裹,呼叫意圖物件的
getExtras
方法,即可取出訊息包裹
Intent intent = new Intent(CurrentActivity.this, NextActivity.class);
// 1. 可以直接傳入一個字串
intent.putExtra("key", value);
// 2. 也可以建立一個包裹
Bundle bundle = new Bundle();
bundle.putString("request_time", DateUtil.getNowTime());
bundle.putString("request_content", tv_send.getText().toString());
intent.putExtras(bundle);
startActivity(intent);
// 1. 在目標 Activity 中接收資料:
Intent intent = getIntent();
String name = intent.getStringExtra("key");
// 2. 在目標 Activity 中接收包裹
Bundle bundle = getIntent().getExtras();
String request_time = bundle.getString("request_time");
String request_content = bundle.getString("request_content");
向上一個 Activity 傳送資料
在 Android 中,Activity 之間的資料傳遞是單向的,即從一個 Activity 向另一個 Activity 傳送資料。如果你想要從目標 Activity 返回資料給上一個 Activity,可以透過以下步驟實現:
- 在目標 Activity 中,建立一個新的 Intent 並使用
putExtra()
方法來新增要返回的資料到 Intent 中。
Intent intent = new Intent();
intent.putExtra("result", "這是返回的資料");
- 在目標 Activity 中呼叫
setResult()
方法來設定結果碼和返回的 Intent。
setResult(Activity.RESULT_OK, intent);
- 在目標 Activity 中呼叫
finish()
方法來關閉當前 Activity,並返回到上一個 Activity。
finish();
- 在上一個 Activity 中,可以在
onActivityResult()
方法中接收返回的資料。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
if (data != null && data.hasExtra("result")) {
String result = data.getStringExtra("result");
// 在這裡處理返回的資料
}
}
}
上述步驟中的 REQUEST_CODE
是一個自定義的請求碼,用於標識請求的來源。在呼叫 startActivityForResult()
方法啟動目標 Activity 時,可以傳遞這個請求碼。
示例程式碼如下:
- 在當前 Activity 中啟動目標 Activity:
private static final int REQUEST_CODE = 1;
// 啟動目標
ActivityIntent intent = new Intent(CurrentActivity.this, TargetActivity.class);
startActivityForResult(intent, REQUEST_CODE);
- 在目標 Activity 中返回資料:
// 在目標 Activity 中設定返回資料
Intent intent = new Intent();
intent.putExtra("result", "這是返回的資料");
setResult(Activity.RESULT_OK, intent);
finish();
- 在上一個 Activity 中接收返回的資料:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
if (data != null && data.hasExtra("result")) {
String result = data.getStringExtra("result");
// 在這裡處理返回的資料
}
}
}
透過這種方式,你可以在目標 Activity 中返回資料給上一個 Activity,並在上一個 Activity 中獲取並處理這些返回的資料。
Android AsyncTask 非同步任務
AsyncTask
是 Android 中用於在後臺執行緒執行非同步任務並在主執行緒更新 UI 的類。它提供了一種簡單的方法來執行後臺計算、網路請求或其他耗時操作,然後將結果返回到主執行緒,以便更新使用者介面。AsyncTask
可以幫助開發者避免在主執行緒執行耗時操作而導致的介面卡頓和 ANR(Application Not Responding)問題。
AsyncTask
包含了四個關鍵的回撥方法,它們分別是:
onPreExecute()
: 在後臺任務開始執行之前,在主執行緒中呼叫。通常用於初始化 UI 或顯示進度對話方塊。doInBackground(Params...)
: 在後臺執行緒中執行耗時操作的方法。在該方法內部進行計算、網路請求或其他耗時任務,但不要更新 UI。doInBackground
結束後會返回一個 String 型別的資料到onPostExecute
中,接下來onPostExecute
就可以使用這個返回的資料來做 UI 更新的邏輯了。onProgressUpdate(Progress...)
: 當呼叫publishProgress(Progress...)
方法時,在主執行緒中呼叫。通常用於更新 UI 進度。onPostExecute(Result)
: 在後臺任務執行完成後,在主執行緒中呼叫。通常用於處理後臺任務的結果,並更新 UI。
使用 AsyncTask
的步驟如下:
- 建立一個繼承自
AsyncTask
的子類,並指定泛型引數:Params
(傳遞給doInBackground
的引數型別)、Progress
(傳遞給onProgressUpdate
的引數型別)、Result
(傳遞給onPostExecute
的引數型別)。 - 實現
doInBackground
方法,執行後臺任務,並在需要時呼叫publishProgress
方法來更新進度。 - 實現
onPostExecute
方法,處理後臺任務的結果,並更新 UI。 - 在主執行緒中例項化並執行 AsyncTask,呼叫
execute()
方法。
下面是一個簡單的示例,演示了使用 AsyncTask
進行後臺計算,並在完成後更新 UI。
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private ProgressBar progressBar;
private TextView resultTextView;
private Button calculateButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = findViewById(R.id.progressBar);
resultTextView = findViewById(R.id.resultTextView);
calculateButton = findViewById(R.id.calculateButton);
calculateButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 執行 AsyncTask
MyAsyncTask myAsyncTask = new MyAsyncTask();
myAsyncTask.execute(10); // 將計算的引數傳遞給 AsyncTask
}
});
}
private class MyAsyncTask extends AsyncTask<Integer, Integer, Integer> {
@Override
protected void onPreExecute() {
super.onPreExecute();
// 初始化 UI 或顯示進度對話方塊
progressBar.setVisibility(View.VISIBLE);
calculateButton.setEnabled(false);
resultTextView.setText("");
}
@Override
protected Integer doInBackground(Integer... params) {
// 後臺計算任務
int num = params[0];
int result = 0;
for (int i = 1; i <= num; i++) {
// 假設這是一個耗時的計算任務
result += i;
// 更新進度
// publishProgress 方法被呼叫後,會觸發 onProgressUpdate 方法的執行
// 這裡的引數會自動傳遞給 onProgressUpdate
// 注意:不能寫成 onProgressUpdate(i * 10),因為這樣會導致在錯誤的執行緒中執行
publishProgress(i * 10);
}
return result;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
// 更新 UI 進度
progressBar.setProgress(values[0]);
}
@Override
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
// 處理計算結果,並更新 UI
progressBar.setVisibility(View.GONE);
calculateButton.setEnabled(true);
resultTextView.setText("計算結果:" + result);
}
}
}
在這個示例中,當使用者點選按鈕時,會觸發 AsyncTask 的執行。doInBackground
方法會在後臺執行緒中執行計算任務,並使用 publishProgress
方法來更新進度。在 onProgressUpdate
中更新 UI 進度,最終在 onPostExecute
中更新計算結果。這樣,整個計算過程在後臺進行,不會阻塞主執行緒,並且在計算完成後更新了 UI。
參考資料
- Android 開發入門基礎