Activity
1. 生命週期
正常情況下:
- 啟動 Activity:系統先呼叫
onCreate()
,然後呼叫onstart()
,最後呼叫onResume()
,Activity 就此進入執行狀態。 - 退出 Activity:系統先呼叫
onPause()
,然後呼叫onStop()
,最後呼叫onDestroy()
,Activity 就此銷燬。 - 當前裝置鎖屏或者點選 Home 鍵使程式進入後臺,依次執行
onPause()
、onStop()
, 重新回到 Activity,依次執行onRestart()
、onStart()
、onResume()
(Activity 不被回收的情況下)。 - 當前 Activity 被 Dialog 主題的 Activity 覆蓋時,執行
onPause()
,回到前臺執行onResume()
。 - 當 Activity 不在前臺或者不可見時被系統回收後,再次回到此 Activity ,系統會依次執行
onCreate()
、onStart()
、onResume()
。 - 啟動一個新 Activity,舊 Activity 會先執行
onPause()
,然後新 Activity 再啟動。 - 按是否可見分類:
onStart()
、onStop()
。 - 按是否前臺分類:
onResume()
、onPause
。
注意:當 Activity 彈出對話方塊時,並不會回撥onPause
,但是會回撥onStop()
。
異常情況下:
當系統記憶體不足,或者系統配置發生改變(如旋轉方向),Activity 會被殺死。
- 由於是異常情況下終止的,系統會呼叫
onSaveInstanceState()
來儲存當前 Activity 的狀態,這個方法的呼叫時機是在onStop()
之前,當 Activity 重新建立後,系統會把銷燬時儲存的 Bundle 物件作為引數傳遞給onCreate()
和onRestoreInstanceState()
,建議在onRestoreInstanceState()
中做資料恢復,畢竟專門用來恢復例項狀態的。另外,每個 View 本身都有onSaveInstanceState()
和onRestoreInstanceState()
方法,因此係統都會預設恢復 View 的基本狀態。 - 防止重新建立 Activity:在 AndroidManifest.xml 中指定
android:configChanges="orientation"
,似乎有些裝置需要多指定一個引數,即android:configChanges:="orientaion|screenSize"
。
2. 啟動模式
總共 4 種啟動模式:Standard,SingleTop,SingleTask,SingleInstance。
- Standard:預設的啟動模式,在這種模式下,Activity 會進入啟動它的 Activity 所在的任務棧。
- SingleTop:如果新 Activity 位於任務棧的棧頂時,Activity 不會被建立,並且它的
onNewIntent()
方法會被回撥,其餘生命週期方法均不會回撥。 - SingleTask:如果 Activity 在一個任務棧中存在,那麼多次啟動此 Activity 都不會建立新例項,但系統會回撥
onNewIntent()
。此外,位於此 Activity 之上的所有 Activity 均會出棧,此時 Activity 位於棧頂。 - SingleInstance:這種模式下的 Activity 只能單獨存在於一個任務棧中,由於棧內複用特性,此後的請求均不會建立新的例項。
注意:預設情況下,所有 Activity 所需的任務棧的名字為應用的包名,可以在 AndroidManifest.xml 中通過android:taskAffinity=""
來指定任務棧。
Service
被啟動的 Service 預設是在主執行緒下工作的,因此如果需要執行耗時操作,應當另開一個子執行緒來執行,以免阻塞主執行緒導致出現 ANR(Application Not Response)。任何 Activity 都可以控制同一個 Service,並且系統中也只會存在一個 Service 例項。
啟動模式
1. startService()
- 普通模式:在這種模式下啟動 Service 後,即使 Activity 銷燬後,Service 也會在後臺繼續執行任務,直到在 Activity 中手動呼叫
stopService()
或者在在 Service 類中呼叫stopSelf()
,服務才會停止,Activity 無法與 Service 進行通訊。 onCreate
中可以做一些初始化,onStartCommand()
中放置執行任務的程式碼,onDestroy()
中進行資源的釋放。- 在這種方式下啟動 Service,生命週期是
onCreate()
->onStartCommand()
->onDestroy()
,當 Service 已經被啟動後,無論執行多少次startServvice()
,都不會走onCreate()
,而是會呼叫onStartCommand()
。
2. bindService()
- 繫結模式:在這種模式下啟動 Service,當 Activity 銷燬後,Service 也會跟著銷燬,不再使用 Service 時,呼叫
unbindService()
停止。這種模式下可以進行 Activity 和 Service 的通訊。 - 這這種方式下啟動 Service,生命週期是
onCreate()
->onBind()
->onUnbind()
->onDestroy()
,onStartCommand()
不會有呼叫機會。
注意:當startService()
和bindService()
一起被呼叫後,若想停止服務,必須同時呼叫stopService()
和unbindService()
,這樣服務才會停止,順序沒有嚴格要求,但一定要同時呼叫。
示例:
- 建立一個 Service
public class MyService extends Service {
private static final String TAG = "MyService";
private IBinder mBinder;
@Override
public IBinder onBind(Intent intent) {
if (mBinder == null) {
mBinder = new MyBinder();
}
Log.d(TAG, "-----onBind()-----");
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "-----onUnbind()-----");
return super.onUnbind(intent);
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "-----onCreate()-----");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "-----onDestroy()-----");
}
public class MyBinder extends Binder {
// 這裡可以定義想要通訊的方法,在 Activity 中可以通過 Binder 例項呼叫
public void print(String data) {
Log.d(TAG, "print: " + data);
}
}
}
複製程式碼
- 在 AndroidManifest.xml 中註冊
<!--android:enabled 表示是否啟用,android:exported 表示是否向外界公開-->
<service
android:name=".MyService"
android:enabled="true"
android:exported="false" />
複製程式碼
- 在 Activity 中定義 ServiceConnectioin 並進行繫結
public class MainActivity extends AppCompatActivity {
private ServiceConnection mConnection;
private MyService.MyBinder mBinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mConnection = new MyConnection();
initView();
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
private void initView() {
Button bindService = findViewById(R.id.btn3);
Button unbindService = findViewById(R.id.btn4);
bindService.setOnClickListener(view -> {
bindService(new Intent(MainActivity.this, MyService.class), mConnection, BIND_AUTO_CREATE);
});
unbindService.setOnClickListener(view -> {
unbindService(mConnection);
});
}
private class MyConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 轉換為自己定義的 Binder
mBinder = (MyService.MyBinder) service;
// 呼叫自己定義的方法進行通訊
mBinder.print("成功通訊");
}
@Override
public void onServiceDisconnected(ComponentName name) {
// 這個方法只有在出現異常的時候才會被系統呼叫
}
}
}
複製程式碼
前臺服務
服務幾乎都是在後臺執行的,系統優先順序相對比較低,當系統出現記憶體不足時就容易被回收,如果希望服務可以一直保持執行狀態,不會因為記憶體不足而被回收,這時就可以使用前臺服務。與普通服務不同,前臺服務會有一個正在執行的圖示在通知欄裡顯示,類似通知。例如騰訊手機管家等通知,會在通知欄顯示此時手機的記憶體狀態等。
示例:在 Service 中通過startForeground()
建立前臺服務,然後在 Activity 中通過startService()
或bindService()
開啟,通過stopService()
或unbindService()
關閉。
public class MyService extends Service {
private static final String TAG = "MyService";
private IBinder mBinder;
@Override
public void onCreate() {
super.onCreate();
createForegroundService();
}
private void createForegroundService() {
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
Notification notification;
// 使用了建造者模式,將需要定義到的部分提前設定好
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "Channel_1")
.setContentTitle("前臺服務")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentIntent(pi);
// 因為 Android 8.0 新增了 NotificationChannel(通知渠道)
// 因此需要適配,不然在 8.0 上會顯示不了通知
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel("Channel_1", "前臺服務", NotificationManager.IMPORTANCE_DEFAULT);
manager.createNotificationChannel(channel);
notification = builder.build();
} else {
notification = builder.build();
}
startForeground(1, notification);
}
......
}
複製程式碼
IntentService
在前面就知道了,普通服務預設是執行在主執行緒中的,如果在服務裡執行一些耗時操作就容易出現 ANR,當然也可以自己另開執行緒來執行,然後在合適的時機在 Service 內部呼叫stopSelf()
來停止。但是這樣稍顯麻煩,為了可以簡單地建立一個非同步的、會自動停止的 Service,Android 提供了 IntentService 類,很好地解決了這個問題。
- 通過建立一個 Service 繼承自 IntentService,並重寫
onHandleIntent()
方法即可,在這個方法中便可以處理耗時操作,並且當執行完畢後,Service 會自動停止。
示例:
public class MyIntentService extends IntentService {
public MyIntentService() {
// 必須呼叫父類有參構造
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
Log.d("MyIntentService", "currentThread: " + Thread.currentThread().getName());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService", "-----onDestroy()-----");
}
}
複製程式碼
- 當執行完畢後,Service 自動停止,詳情見 Logcat
遠端服務
由於遠端服務我並不熟悉,這裡就先落下了,後面學習了再補充上去。
Broadcast
1. 廣播
標準廣播:
- 是一種完全非同步執行的廣播,所有的廣播接收器之間幾乎沒有任何先後順序。這種廣播效率會比較高,但同時它是無法截斷的。
sendBroadcast(intent)
—— 表示傳送標準廣播
有序廣播:
- 是一種同步執行的廣播,廣播發出後,同一時刻只會有一個廣播接收器接收到這條廣播,這種廣播有先後順序,優先順序高的接收器可以先收到廣播,並且前面的接收器可以截斷正在傳遞的廣播。
sendOrderedBroadcast(intent, null)
—— 表示傳送有序廣播abortBroadcast()
—— 表示截斷接收到的廣播
2. 接收器
注意: BroadcastReceiver 生命週期很短, 如果需要在
onReceiver()
完成一些耗時操作,應該考慮在 Service 中開啟一個新執行緒處理耗時操作,不應該在 BroadcastReceiver 中開啟一個新的執行緒,因為 BroadcastReceiver 生命週期很短,在執行完 onReceiver 以後就結束,如果開啟一個新的執行緒,可能出現 BroadcastRecevier 退出以後執行緒還在,而如果 BroadcastReceiver 所在的程式結束了,該執行緒就會被標記為一個空執行緒,根據 Android 的記憶體管理策略,在系統記憶體緊張的時候,會按照優先順序,結束優先順序低的執行緒,而空執行緒無異是優先順序最低的,這樣就可能導致 BroadcastReceiver 啟動的子執行緒不能執行完成。
動態註冊(監聽網路變化):
- 定義一個 Receiver 類繼承自 BroadcastReceiver,並重寫父類的
onReceive()
方法,呼叫addAction()
新增 action 值,當網路狀態發生變化時,系統發出的正式一條值為android.net.conn.CONNECTIVITY_CHANGE
的廣播,想要監聽什麼廣播便新增相應的 action 值。最後,動態註冊的接受器一定要取消註冊。
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private NetworkChangeReceiver networkChangeReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
networkChangeReceiver = new NetworkChangeReceiver();
registerReceiver(networkChangeReceiver, intentFilter);
}
@Override
protected void onDestroy(){
super.onDestroy();
unregisterReceiver(networkChangeReceiver);
}
class NetworkChangeReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent){
ConnectivityManager manager = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
if(networkInfo != null && networkInfo.isAvailable()){
Toast.makeText(context, "網路沒毛病", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(context, "網路不可用", Toast.LENGTH_SHORT).show();
}
}
}
}
複製程式碼
-
在
onReceive()
方法中,通過getSystemService()
方法得到了 ConnectivityManager 的例項,這是一個系統服務類,專門用於管理網路連線。然後呼叫它的getActiveNetworkInfo()
方法可以得到 NetworkInfo 的例項,接著呼叫 NetworkInfo 的isAvailable()
方法即可判斷當前是否有網路。 -
最後需要在 AndroidManifest.xml 中註冊訪問系統網路狀態許可權。
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
靜態註冊(監聽系統開機):
- 使用 Android Studio 提供的快捷方式新建一個廣播接收器,Exported 屬性表示是否允許這個接收器接收本程式以外的廣播,Enabled 屬性表示是否啟用這個接收器。
- AndroidManifest.xml 中 Android Studio 已經自動註冊好了靜態的廣播接收器,由於需要監聽開機廣播,因此需要宣告接收開機廣播許可權,並且 intent-filter 標籤也需要新增相應的 action 值。
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
複製程式碼
靜態註冊與動態註冊的區別
- 動態註冊廣播不是常駐型廣播,也就是說廣播跟隨 Activity 的生命週期。需要在 Activity 結束前,移除廣播接收器。 靜態註冊是常駐型,也就是說當應用程式關閉後,如果有資訊廣播來,程式也會被系統呼叫自動執行。
- 當廣播為有序廣播時:
- 優先順序高的先接收
- 同優先順序的廣播接收器,動態優先於靜態
- 同優先順序的同類廣播接收器,靜態:先掃描的優先於後掃描的,動態:先註冊的優先於後註冊的。
- 當廣播為普通廣播時:
- 無視優先順序,動態廣播接收器優先於靜態廣播接收器
- 同優先順序的同類廣播接收器,靜態:先掃描的優先於後掃描的,動態:先註冊的優先於後註冊的。
3. 本地廣播
本地廣播用了一個 LocalBroadcastManager 來對廣播進行管理,並提供了傳送廣播和註冊廣播接收器的方法。另外,本地廣播是無法通過靜態註冊的方式來接收的
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private LocalReceiver localReceiver;
private LocalBroadcastManager localBroadcastManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
localBroadcastManager = LocalBroadcastManager.getInstance(this);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent =
new Intent("com.example.broadcasttest.LOCAL_BROADCAST");
localBroadcastManager.sendBroadcast(intent);
}
});
intentFilter = new IntentFilter();
intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
localReceiver = new LocalReceiver();
localBroadcastManager.registerReceiver(localReceiver, intentFilter);
}
@Override
protected void onDestroy(){
super.onDestroy();
localBroadcastManager.unregisterReceiver(localReceiver);
}
class LocalReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent){
// 編寫邏輯操作
}
}
}
複製程式碼
ContentProvider
ContentProvider 是 Android 四大元件之一的內容提供器,它主要的作用就是將程式的內部的資料和外部進行共享,為資料提供外部訪問介面,被訪問的資料主要以資料庫的形式存在,而且還可以選擇共享哪一部分的資料。這樣一來,對於程式當中的隱私資料可以不共享,從而更加安全。ContentProvider 是 Android 中一種跨程式共享資料的重要元件。
1. 使用系統的 ContentProvider
系統的 ContentProvider 有很多,如通話記錄,簡訊,通訊錄等等,都需要和第三方的 App 進行共享資料。既然是使用系統的,那麼 ContentProvider 的具體實現就不需要我們擔心了,使用內容提供者的步驟如下:
- 獲取 ContentResolver 例項
- 確定 Uri 的內容,並解析為具體的 Uri 例項
- 通過 ContentResolver 例項來呼叫相應的方法,傳遞相應的引數,但是第一個引數總是 Uri,它制定了我們要操作的資料的具體地址
可以通過讀取系統通訊錄的聯絡人資訊,顯示在Listview中來實踐這些知識。不要忘記在讀取通訊錄的時候,在清單檔案中要加入相應的讀取許可權。
2. 自定義 ContentProvider
系統的 ContentProvider 在與我們互動的時候,只接受了一個 Uri 的引數,然後根據我們的操作返回給我們結果。系統到底是如何根據一個 Uri 就能夠提供給我們準確的結果呢?只有自己親自實現一個看看了。和之前提到的一樣,想重新自定義自己程式中的四大元件,就必須重新實現一個類,重寫這個類中的抽象方法,在清單檔案中註冊,最後才能夠正常使用。
重新實現 ContentProvider 之後,發現我們重寫了 6 個重要的抽象方法
onCreate()
query()
update()
insert()
delete()
getType()
大部分的方法在資料庫那裡已經見過了,他們內部的邏輯可想而知都是對資料的增刪改查操作,其中這些方法的第一個引數大多都是 Uri 例項。其中有兩個方法比較特殊:
onCreate()
方法應該是內容提供者建立的時候所執行的一個回撥方法,負責資料庫的建立和更新操作。這個方法只有我們在程式中獲取 ContentResolver 例項之後準備訪問共享資料的時候,才會被執行。
getType()
方法是獲取我們通過引數傳遞進去的 Uri 的 MIME 型別,這個型別是什麼,後面會有例項說明。
內容提供者首先要做的一個事情就是將我們傳遞過來的 Uri 解析出來,確定其他程式到底想訪問哪些資料。Uri 的形式一般有兩種:
- 以路徑名為結尾,這種 Uri 請求的是整個表的資料,如:
content://com.demo.androiddemo.provider/table1
標識我們要訪問 table1 表中所有的資料。 - 以 id 列值結尾,這種Uri請求的是該表中和其提供的列值相等的單條資料。
content://com.demo.androiddemo.provider/table1/1
標識我們要訪問 table1 表中 _id 列值為 1 的資料。
如果是內容提供器的設計者,那麼我們肯定知道這個程式的資料庫是什麼樣的,每一張表,或者每一張表中的 _id 都應該有一個唯一的內容 Uri 。我們可以將傳遞進來的 Uri 和我們存好的 Uri 進行匹配,匹配到了之後,就說明資料來源已經找到,便可以進行相應的增刪改查操作。
最後
四大元件的學習可以參考第一行程式碼裡面的知識點,都是比較基礎的東西,自己碼出來練習練習即可,沒必要死記,需要用到時腦袋裡有這麼個印象然後知道哪裡可以查就行了,久而久之自然而然就記住了。