Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 開發

Android 開發

編輯:關於Android編程

第一章 Activity 的生命周期和啟動模式

1.1 Activity 的生命周期全面分析

1.1.1 典型情況下的生命周期分析

1. 當用戶打開新的Activity或者切換到桌面的時候,回調如下:onPause->onStop。這裡有一種特殊情況,如果新Activity采用了透明主題,那麼當前Activity不會回調onStop。

2. onStart和onStop是從Activity是否可見這個角度來回調的,而onResume和onPause是從Activity是否位於前台這個角度來回調的,除了這種區別,在實際使用中沒有明顯區別。

1.1.2 異常情況下的生命周期分析q

1. 資源相關的系統配置發生改變導致Activity被殺死並被重新創建。

系統會調用onSaveInstanceState來保存當前Activity的狀態。這個方法的調用時機是在onStop之前,它和onPause沒有既定的時序關系,它既可能在onPause之前調用,也可能在onPause之後調用。onRestoreInstanceState的調用時機在onStart之後。

保存和恢復View的層次結構,系統的工作流程:首先Activity被意外終止時,Activity會調用onSaveInstanceState去保存數據,然後Activity會委托Window去保存數據,接著Window再去委托它上面的頂級容器去保存數據。頂級容器一般來說很有可能是DecorView。最後頂級容器再去一一通知它的子元素去保存數據,這樣整個數據保存就完成了。

注意:系統只在Activity異常終止的情況下才會調用onSaveInstanceState和onRestoreInstanceState來保存和恢復數據,其它情況不會觸發這個過程。但是在按home鍵和啟動新的Activity時仍然會觸發onSaveInstanceState,不過不會調用onRestoreInstanceState,因為當前的Activity並沒有確定被系統銷毀。

2. 資源內存不足導致低優先級的Activity被殺死。

注意:如果一個進程中沒有四大組件在運行,那麼很容易被系統殺死,因此,一些後台工作不適合脫離四大組件而獨自工作在後台中,這樣進程很容易被殺死。比較好的方法是將後台工作放入Service中,使進程有一定的優先級,從而不容易被系統殺死。

1.2 Activity 的啟動模式

1.2.1 Activity 的 LaunchMode

1. standard。

2. singTop。

3. singleTask。

4. singleInstance。

TaskAffinity屬性主要和singleTask啟動模式或者allowTaskReparenting屬性配對使用。taskAffinity用於指定當前Activity(activity1)所關聯的Task,allowTaskReparenting用於配置是否允許該activity可以更換從屬task,通常情況二者連在一起使用,用於實現把一個應用程序的Activity移到另一個應用程序的Task中。

allowTaskReparenting用來標記Activity能否從啟動的Task移動到taskAffinity指定的Task,默認繼承至application中的allowTaskReparenting=false,如果為true,則表示可以更換;false表示不可以。

由於A啟動了C,這個時候C只能運行在A的任務棧中,但是C屬於B應用,正常情況下,它的TaskAffinity值肯定不可能和A的任務棧相同(因為包名不同)。所以,當B被啟動後,B會創建自己的任務棧,這個時候系統發現C原本想要的任務棧已經被創建了,所以就把C從A的任務棧中轉移過來了。

給Activity指定啟動模式有2種方式,第一種是通過AndroidManifest給Activity指定啟動模式,第二種是通過在Intent中設置標志位來為Activity指定啟動模式。

第二種的優先級要高於第一種。第一種方式無法直接為Activity設置FLAG_ACTIVITY_Clear_TOP標識,第二種方式無法為Activity指定singleInstance模式。

1.2.2 Activity 的 Flags

FLAG_ACTIVITY_NEW_TASK

FLAG_ACTIVITY_SINGLE_TOP

FLAG_ACTIVITY_CLEAR_TOP

FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

1.3 IntentFilter 的匹配規則

一個Activity中可以有多個intent-filter,一個Intent只要能匹配任何一組Intent-filter即可成功啟動對應的Activity。

1. action 的匹配規則。

2. category 的匹配規則。

3. data 的匹配規則。

注意:在過濾規則沒有指定URI的情況下,URI是有默認值的,默認值為content和file。也就是說,雖然沒有指定URI,但是Intent中的URI部分的schema必須為content或者file才能匹配。

當我們通過隱式方式啟動一個Activity的時候,可以做下判斷,看是否Activity能夠匹配我們的隱式Intent。判斷的方法有兩種:采用PackageManager的ResolveActivity方法或者Intent的ResolveActivity方法,如果它們找不到匹配的Activity就會返回null,我們通過判斷返回值就可以規避錯誤。

第二章 IPC 機制

2.1 Android IPC 簡介

2.2 Android 中的多進程模式

2.2.1 開啟多進程模式

在Android中使用多進程只有一種方法,那就是給四大組件在AndroidManifest中指定android:process屬性。其實還有另一種非常規的多進程方法,那就是通過JNI在native層去fork一個新的進程,但是這種方法屬於特殊情況,也不是常用的創建多進程的方式。

“:“的含義是指要在當前的進程名前面附加上當前的包名。

進程名以“:”開頭的進程屬於當前應用的私有進程,其它組件應用不可以和它跑在同一個進程中,而進程名不以”:“開頭的進程屬於全局進程,其他應用通過ShareUID方式可以和它跑在同一個進程中。兩個應用通過ShareUID跑在同一個進程中是有要求的,需要這兩個應用有相同的ShareUID並且簽名相同才可以。

2.2.2 多進程模式的運行機制

同一個應用間的多進程:它就相當於兩個不同的應用采用了ShareUID模式。

2.3 IPC 基礎概念介紹

2.3.1 Serializable 接口

如何進行對象的序列化和反序列化也非常簡單,只需要采用ObjectInputStream和ObjectOutputStream即可輕松實現。

注意:靜態成員屬於類不屬於對象,所以不會參與序列化過程;其次用transient(穩態)關鍵字標記的成員變量不參與序列化過程。

2.3.2 Parcelable 接口

內容描述功能有describeContents方法來完成,幾乎所有情況下這個方法都應該返回0,僅當當前對象中存在文件描述符時,此方法返回1。

Android推薦的序列化方式,首選Parcelable。Parcelable主要用在內存序列化上,通過Parcelable將對象序列化到存儲設備中或者將對象序列化後通過網絡傳輸也都是可以的,當時這個過程會稍顯復雜,因此在這兩種情況下建議大家使用Serializable。

2.3.3 Binder

 

\

 

手動實現一個Binder的步驟如下:

1. 聲明一個AIDL性質的接口,只需要繼承IInterface接口即可,IInterface接口中只有一個asBinder方法。

2. 實現Stub類和Stub類中的Proxy代理類。

如果服務端進程由於某種原因異常終止,這個時候我們服務端的Binder連接斷裂(稱之為Binder死亡),如何解決?

Binder中提供了兩個配對的方法linkToDeath和unlinkToDeath,通過linkToDeath我們可以給Binder設置一個死亡代理,當Binder死亡時,我們就會收到通知,這個時候就可以重新發起連接從而恢復連接。另外,通過Binder的方法isBinderAlive也可以判斷Binder是否死亡。

2.4 Android 中的 IPC 方式

2.4.1 使用 Bundle

2.4.2 使用文件共享

文件共享方式適合在堆數據同步要求不高的進程之間進行通信,並且要求妥善處理並發讀寫問題。

在多進程模式下,系統對它的讀/寫就變得不可靠,當面對高並發的讀/寫訪問SharedPreferences有很大幾率丟失數據,因此,不建議在進程間通信中使用SharedPreferences。

2.4.3 使用 Messenger

同一個應用的不同組件,如果它們運行在不同的進程中,那麼和它們分別屬於兩個應用沒有本質區別。

2.4.4 使用 AIDL

使用AIDL進行進程間通信的流程,分為服務端和客戶端兩個方面。

1. 服務端。

服務端首先要創建一個Service用來監聽客戶端的連接請求,然後創建一個AIDL文件,將暴露給客戶端的接口在這個AIDL文件中聲明,最後在Service中實現這個AIDL接口即可。

2. 客戶端。

客戶端要做的事情比較簡單,首先需要綁定服務端的Service,綁定成功以後,將服務端返回的Binder對象轉成AIDL接口所屬的類型,接著就可以調用AIDL中的方法了。

3. AIDL接口的創建。

AIDL接口支持6種數據類型:

1. 基本數據類型;

2. String和CharSequence;

3. List:只支持ArrayList,裡面每個元素都必須能被AIDL支持;

4. Map:只支持HashMap,裡面每個元素都必須能被AIDL支持,包括key和value。

5. Parcelable:所有實現了Parcelable的對象。

6. AIDL:所有AIDL接口本身也可以在AIDL中使用。

其中自定義的Parcelable對象和AIDL對象必須要顯示import進來,不管它們是否和當前的AIDL文件是否位於同一個包中。

注意:如果AIDL文件中用到了自定義的Parcelable對象,必須建立一個和它同名的AIDL文件,並在其中聲明它為Parcelable類型。

package com.ryg.chapter_2.aidl;

parcelable Book;

我們需要注意,AIDL中每個實現了Parcelable接口的類都需要按照上面那種方式去創建相應的AIDL文件並聲明那個類為Parcelable。

AIDL中除了基本數據類型,其他類型的參數必須標上方向:in、out或者inout。

AIDL接口中只支持方法,不支持聲明靜態常量,這一點區別於傳統的接口。

AIDL的包結構在服務端和客戶端要保持一致,否則運行會出錯,這是由於客戶端需要序列化服務端中和AIDL接口相關的所有類,如果類的完整路徑不一樣的話,就無法成功反序列化。

4. 遠程服務端Service的實現

CopyOnWriteArrayList支持並發讀/寫,並能進行自動的線程同步。它在Binder中會按照List的規范去訪問數據並最終形成一個新的ArrayList傳遞給客戶端。與此類型的還有ConcurrentHashMap。

5. 客戶端的實現。

對象是不能跨進程直接進行傳輸的,對象的跨進程傳輸的本質都是序列化的過程,這就是AIDL中的自定義對象需要實現Parcelable接口的原因。

如何實現解注冊功能?

使用RemoteCallbackList。

客戶端和服務端進行跨進程傳輸的同一個對象在這兩端雖然是不同對象,但是它們底層的Binder對象是同一個。

當客戶端解注冊的時候,我們只要遍歷服務端所有的listener,找出那個和解注冊listener具有相同Binder對象的服務端listener並把它刪掉,這就是RemoteCallbackList為我們做的事情。此外,當客戶端進程終止後,它能夠自動移除客戶端所注冊的listener,並且,RemoteCallbackList內部自動實現了線程同步的功能。

遍歷RemoteCallbackList,必須要按照下面的方式進行,其中beginBroadCast和finishBroadcast必須要配對使用。

final int N = mListenerList.beginBroadcast();

for ( int i = 0; i < N; i++) {

IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);

if (l ! = null) {

//TODO handle l

}

}

mListenerList.finishBroadcast();

客戶端的onServiceConnected和onServiceDisconnected方法都運行在UI線程中。

如果要訪問UI,請使用Handler切換到UI線程。

服務端意外停止了,需要重新連接服務:

1. 給Binder設置DeathRecipient監聽。

2. 在onServiceDisconnected中重連遠程服務。

區別:onServiceDisconnected在客戶端的UI線程中被回調,而binderDied在客戶端的Binder線程池中被回調。

可通過自定義權限在onBind方法和onTransact方法中進行權限驗證。

2.4.5 使用 ContentProvider

通過ContentResolver的query、update、insert和delete方法即可進行跨進程訪問信息。

getType用來返回一個Uri請求所對應的MIME類型(媒體型),比如圖片、視頻等。

android:authoritie是ContentProvider的唯一標識。

ContentProvider通過Uri來區分外界要訪問的數據集合。

根據Uri先取出Uri_Code,根據Uri_Code再得到數據表名稱,知道了外界要訪問的表,接下來就可以響應外界的增刪改查請求了。

要觀察一個ContentProvider中的數據改變情況,可以通過ContentResolver的registerContentObserver和unregisterContentObserver方法來注冊和解注冊觀察者。

注意:query、update、insert、delete四大方法是存在多線程並發訪問的,因此要做好線程同步。

SQliteDatabase內部對數據庫的操作是有同步處理的,但是如果通過多個SQLiteDatabase對象來操作數據庫就無法保證線程同步,因為SQLiteDatabase對象來操作對象就無法保證線程同步,因此SQLiteDatabase對象之間無法進行線程同步。

ContentProvider除了支持對數據源的增刪改查這四個操作,還支持自定義調用,這個過程是通過ContentResolver的Call方法和ContentProvider的Call方法來完成的。

2.4.6 使用 Socket

為了降低重試機制的開銷,加入休眠機制,即每次重試的時間間隔為1000毫秒。

多進程不推薦使用這種方式,過於繁瑣。

2.5 Binder 連接池

工作機制:每個業務模塊創建自己的AIDL接口並實現此接口,這個時候不同業務模塊之間是不能有耦合的,所有實現細節要單獨開來,然後向服務端提供自己的唯一標識和其對應的Binder對象;對於服務端來說,只需要一個Service就可以了,服務端提供一個queryBinder接口,這個接口能夠根據業務模塊的特征來返回相應的Binder對象給它們,不同的業務模塊拿到所需的Binder對象之後就可以進行遠程方法調用了。

Binder連接池的作用主要是將每個業務模塊的Binder請求統一轉發到遠程Service中去執行,從而避免了重復創建Service的過程。

Binder連接池的實現中,我們通過CountDownLatch將異步操作轉化成了同步操作,這就意味著它有可能是耗時的,然後就是Binder方法的調用過程也可能是耗時的,因此不建議放在主線程中執行。

BinderPool有斷線重連機制,當遠程服務意外終止時,BinderPool會重新建立連接,這個時候如果業務模塊中的Binder調用出現了異常,也需要手動去獲取最新的Binder對象,這個是需要注意的。

2.6 選用合適的 IPC 方式

名稱優點缺點 注意點設用場景

Bundle簡單易用只能傳輸Bundle支持的數據類型四大組件

間的進程通信

文件共享簡單易用不適合高並發的情況,

並且無法做到進程間的即時通訊無並發訪問情況下,

交換簡單的數據實時性不高的情況

AIDL功能強大,支持一對多並發通信

,支持實時通訊需要處理好線程同步一對多通信且有RPC需求

Messager支持一對多串行通信,支持實時通訊不能很好處理高並發情況,不支持RPC,數據通過Message進行傳輸,因此只能傳輸Bundle支持的數據類型低並發的一對多即時通信,無

RPC需求,或者無需返回結果的RPC需求

ContentProvider在數據源訪問方面功能強大,支持一對多並發數據共享,可通過call方法擴展其他操作可以理解為受約束的AIDL,主要提供數據源的CRUD一對多的進程間數據共享

Socket功能強大,可以通過網絡傳輸字節流,支持一對多並發實時通訊實現細節有點繁瑣,不支持直接的RPC網絡數據交換

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved