第2章Android四大元件

weixin_34162695發表於2018-02-27

2.1 Activity

2.1.1 Activity的生命週期全面分析

典型情況下的生命週期:在使用者參與的情況下,Activity所經過的生命週期改變。
異常情況下的生命週期:Activity被系統回收或者裝置的Configuration發生改變從而導致Activity被銷燬重建。

1. 典型情況下的生命週期分析

9821298-79ca9ca1bc139aa0.png
Activity生命週期
  • ==onPause必須執行完,新的Activity的onResume才會執行==。比如A開啟B,生命週期順序是 A onPause->B onCreate ->B onStart ->B onResume ->A onStop。因此onPause不能做耗時操作,才能使新的Activity儘快顯示出來
  • onStart表示Activity已經可見但還在後臺我們看不見,沒有出現在前臺無法與使用者進行互動。
  • onResume表示Activity已經出現在前臺且可以與使用者進行互動。
  • Activity切換到後臺( 使用者開啟新的Activity或者切換到桌面) ,onPause->onStop(如果新Activity採用了透明主題,則當前Activity不會回撥onStop)。

2. 異常情況下的生命週期分析

(1) 情況1:系統配置發生改變導致Activity被殺死並重新建立

比如橫豎屏切換,預設情況下,Activity就會銷燬重建。生命週期如下:


9821298-4ad7e345a76e0058.png
異常情況下Activity的重建過程

當系統配置發生改變導致Activity被銷燬,onPause->onStop->onDestroy,還會==呼叫onSaveIntanceState儲存當前Activity的狀態,時機是在onStop之前==(僅僅出現在Activity異常終止的情況)。當Activity被重建時,==會呼叫onRestoreInstanceState,時機是在onStart之後==,並把onSaveIntanceState中儲存的Bundle物件同時傳遞給onRestoreInstanceState和onCreate。注意,onRestoreInstanceState被呼叫,其入參Bundle一定有值,但是onCreate的Bundle入參可能會null,建議採用onRestoreInstanceState去恢復資料。

==每個View都有onSaveIntanceState和onRestoreInstanceState方法,系統會自動幫我們做一些恢復工作==,具體恢復哪些資料,要看每個View的實現。大致的工作流程如下:Activity意外終止,呼叫onSaveIntanceState去儲存資料,然後Activity委託Window去儲存資料,Window再委託上面的頂層容器(ViewGroup,一般為DecorView)去儲存資料,頂層容器再去一一通知它的子元素來儲存資料。(這是一種典型的委託思想,事件分發和View的繪製過程也是採用類似的思想)

系統只有在Activity異常終止的情況下,才會呼叫onSaveIntanceState和onRestoreInstanceState方法,其他情況不會呼叫。

(2) 情況2:記憶體資源不足,導致低優先順序Activity被殺死

Activity按照優先順序分類:
1)前臺Activity
2)可見非前臺Activity
3)後臺Activity

系統記憶體不足時,低優先順序的Activity所在程式會被殺死,並通過onSaveIntanceState和onRestoreInstanceState來儲存和恢復資料。

3. 系統配置發生改變,Activity如何不重新建立?

在Activity的配置中,配置屬性android:configChanges="orientation|screenSize",可以在螢幕旋轉的時候,不重新建立Activity,取而代之的是==呼叫Activity的onConfigurationChanged方法==。

其他可以配置專案如下表所示

專案 含義
locale 一般指切換了系統語言
keboardHidden 鍵盤的可訪問性發生了變化
orientation 螢幕方向發生了變化
screenSize 螢幕尺寸資訊發生了變化,旋轉螢幕尺寸資訊就會發生變化。若編譯版本小於13,不會導致Activity的重啟;若大於13,則會導致Activity的重啟

2.1.2 Activity的啟動模式

1. Activity的LaunchMode

四種啟動模式:standard、singleTop、singleTask和singleInstance。

(1) standard

預設模式,誰啟動了Activity,那麼這個Activity就執行在啟動它的那個Activity所在的棧中。非Activity型別的Context(如:ApplicationContext)並沒有所謂的任務棧,所以就會有問題。

(2) singleTop

棧頂複用模式。新的Activity位於任務棧的頂部,那麼此Activity不會重新建立(onCreate、onStart不會呼叫),此時==onNewIntent(Intent intent)會被呼叫==,入參就是我們請求的資訊。

(3) singleTask

棧內複用模式。比如啟動ActivityA,系統會先找A想要的任務棧,若不存在,則建立任務棧、A例項、入棧;若存在,棧中已經存在A例項,移除A之上的元素,使A在棧頂,並呼叫onNewIntent,否則建立A例項,入棧。

(4) singleInstance

加強的singTask模式,加強一點:此類Activity只能單獨的位於一個任務棧中,==後續的請求均不會建立新的Activity,除非這個任務棧被銷燬。==

(5) Activity所需的任務棧

==TaskAffinity==:任務相關性,標識了一個Activity所需要的任務棧的名字,預設情況下是包名。==必須和singleTask啟動模式或者allowTaskReparenting屬性配對使用==,否則沒有意義。

任務棧分為前臺任務棧和後臺任務棧,後臺任務棧中的Activity處於暫停狀態,使用者可以通過切換將後臺任務棧調到前臺。

TaskAffinity和allowTaskReparenting結合使用:現在有2個應用A和B,A啟動了B的Activity C,然後按Home鍵回到桌面,然後單擊桌面圖示啟動B,此時並不是啟動B的主Activity,而是重新顯示了Activity C或者說,==C從A的任務棧轉移到了B的任務棧==。可以這麼理解,由於A啟動了C,這個C只能執行在A所在的任務棧中,但是C是屬於B應用的,正常情況下,它的TaskAffinity應該是B的包名。所以,B被啟動之後,B會建立自己的任務棧,此時系統發現C原本想要的任務棧已經有了,就會把C從A的任務棧中轉移過。

(6) 指定啟動模式的兩種方式

第一種:通過AndroidMenifest指定android:launchMode="singTask"
第二種:通過Intent的addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)

2. Activity的Flags

Flags有很多,有些可以設定Activity的啟動模式,有些可以影響Activity的執行狀態。

FLAG_ACTIVITY_NEW_TASK

為Activity指定“singleTask”啟動模式

FLAG_ACTIVITY_SINGLE_TOP

為Activity指定“singleTop”啟動模式

FLAG_ACTIVITY_CLEAR_TOP

具有此標記的Activity在啟動時,同一個任務棧中位於它上面的都要出棧。與singleTask一起使用,若例項已經存在,會呼叫newIntent方法;若被啟動的Activity是standard,則它自己也會出棧,然後重新建立一個新的Activity例項入棧。

FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

等價於android:excludeFromRecents="true",表明==此Activity不會出現在歷史Activity列表中==。

三、IntentFilter的匹配規則

Activity的啟動分為顯式呼叫和隱式呼叫。隱式呼叫需要Intent能夠匹配目標元件的IntentFilter中設定的過濾資訊,不匹配將無法啟動目標Activity。IntentFilter的過濾資訊有action、category、data。

<intent-filter>  
    <action android:name="android.intent.action.MEDIA_MOUNTED"/>  
    <category android:name="com.hilton.controlpanel.category.SELF_MAINTAIN" /> 
    <data android:scheme="file" />  
</intent-filter>

1. action的匹配規則

是一個區分大小寫的字串,一個過濾規則中可以有多個action。

匹配要求:Intent中的action存在,且能夠和過濾規則中的==任何一個action相同==即可匹配成功。

2. category的匹配規則

是一個字串,一個過濾規則中可以有多個category。

匹配要求:Intent可以沒有category,但是如果有,==每一個都要和過濾規則中任何一個的匹配==。在startActivity或者startActivityForResult的時候,會預設加上“android.inent.category,DEFULT”,所以必須在intent-filter中指定這個category。

3. data的匹配規則

由兩部分組成:mimeType和URI。一個過濾規則中可以有多個。

匹配要求:Intent中必須有,且==和過濾規則中的一個相匹配==。

<data android:scheme="string"
    android:host="string"
    android:port="string"
    android:path="string"
    android:pathPattern="string"
    android:pathPrefix="string"
    android:mimeType="string"/>
(1) mimeType

指媒體型別,如 image/jpeg、vide/*等,可以表示圖片、文字、視訊等不同的媒體格式、

<data android:mimeType="image/*"/>

如上匹配規則==指定了媒體型別為所有型別的圖片,雖然沒有指定URI,但是卻預設為content和file==。Intent中的URI部分的scheme必須為content或者file才能匹配。如下:

intent.setDataAndType(Uri.parse("file://abc"),"image/png")

setDataAndType(Uri data, String type)指定Data和Type屬性,因為setAction(String action)addCategory(String category)2個方法會相互覆蓋,所以當要指定Data和Type時,使用這個方法

(2) URI
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]

scheme:URI的模式,如http、file、content。沒有指定,則URI無效。
host:URI的主機名。沒有指定,則URI無效。
port:URI中的埠號,可以沒有。
path:表示完整的路徑資訊。
pathPattern:表示完整的路徑資訊,建議包含萬用字元“*”(表示0個或多個任意字元)。
pathPrefix:表示路徑的字首資訊。

<data android:scheme="file" android:host="www.baidu.com"/>
等價於
<data android:scheme="file"/>
<data android:host="www.baidu.com"/>

2.2 Service

2.2.1 基礎知識

1. 定義

服務,屬於Android中的計算型元件

2. 作用

提供需要在後臺長期執行的服務(如複雜計算、下載等等)

3. 特點

  • 長生命週期的、沒有使用者介面、在後臺執行、使用者不手動關閉不會停止
  • 從Context派生出來的,也有getResources(),getContentResolver()等方法

2.2.2 相關方法

1. Context相關方法

  • startService(Intent intent) ComponentName 啟動一個Service,訪問者和Service之間沒有關聯,==即使訪問者退出了,Service仍然執行==
  • stopService (Intent intent) boolean 之後會自動呼叫內部方法:onDestory()
  • bindService(Intent service, ServiceConnection conn, int flags) boolean 訪問者與Service綁在了一起,==訪問者一旦全部退出,Service也將終止==。conn:該引數是一個ServiceConnection(I)物件,用於==監聽訪問者和Service之間的連線情況==,若連線成功,將回撥ServiceConnection#onServiceConnected(ComponentName name, IBinder service)方法,當異常終止連線時,回撥ServiceConnection#onServiceDisconnected(ComponentName name)方法(若訪問者主動呼叫unbindService(ServiceConnection conn) 斷開連線,不會回撥此方法),flags:指定繫結時若Service未建立是否自動建立,值:0或BIND_AUTO_CREATE
  • unbindService(ServiceConnection conn)

2. 生命週期方法

  • onBind(Intent intent) IBinder 應用程式可以通過IBinder與Service元件進行通訊,繫結該Service時回撥該方法。

在繫結本地Service的情況下,onBind(Intent service)方法返回的IBinder物件會傳給ServiceConnection#onServiceConnected(ComponentName name, IBinder service)的service引數,IBinder相當於一個代理人的角色,實現互相通訊,所以onBind方法應該返回一個繼承Binder類的物件,可以操作Service中的內容,在onServiceConnected方法就可以使用該代理人。

  • onUnbind(Intent intent) boolean 當繫結在該Service上的所有客戶端都斷開連線時回撥該方法
  • onStartCommand(Intent intent, int flags, int startId) int 呼叫startService(Intent)方法啟動Service時回撥該方法,==會被呼叫多次==
  • onCreate() 第一次被建立時回撥該方法,僅被呼叫一次
  • onDestroy()
  • stopSelf()

3. onStartCommand(Intent intent, int flags, int startId)

(1) 返回值

有三種可選值。

  • START_STICKY
    當Service因記憶體不足而被系統kill後,一段時間後記憶體再次空閒時,系統將會嘗試重新建立此Service,一旦建立成功後將回撥onStartCommand方法,但其中的Intent將是null,除非有掛起的Intent,如pendingintent。這個狀態下比較適用於不執行命令、但無限期執行並等待作業的媒體播放器或類似服務。
  • START_NOT_STICKY
    當Service因記憶體不足而被系統kill後,即使系統記憶體再次空閒時,系統也不會嘗試重新建立此Service。除非程式中再次呼叫startService啟動此Service,這是最安全的選項,可以避免在不必要時以及應用能夠輕鬆重啟所有未完成的作業時執行服務。
  • START_REDELIVER_INTENT
    當Service因記憶體不足而被系統kill後,一段時間後記憶體再次空閒時,系統將會嘗試重新建立此Service,並通過傳遞給服務的最後一個 Intent 呼叫 onStartCommand(),任何掛起 Intent均依次傳遞。與START_STICKY不同的是,其中的傳遞的Intent將是非空,是最後一次呼叫startService中的intent。這個值適用於主動執行應該立即恢復的作業(例如下載檔案)的服務。
(2) 方法入參
  • intent :啟動Service元件傳遞過來的Intent。
  • flags:表示啟動請求時是否有額外資料。可選值有0,START_FLAG_REDELIVERY,START_FLAG_RETRY。0代表沒有,它們具體含義如下:
    a)START_FLAG_REDELIVERY
    這個值代表返回值為START_REDELIVER_INTENT,而且在上一次服務被殺死前會去呼叫stopSelf()方法停止服務。
    b)START_FLAG_RETRY
    該flag代表當onStartCommand呼叫後一直沒有返回值時,會嘗試重新去呼叫onStartCommand()。
  • startId : 指明當前服務的唯一ID,與stopSelfResult(int startId)配合使用,stopSelfResult 可以更安全地根據ID停止服務。

4. Service生命週期

(1) 單獨呼叫
  • startService()->onCreate()->onStartCommand()->onStop()->onDestory()
  • bindService()->onCreate()->onBind()->onUnbind()->onDestory()
(2) 混合呼叫
  • onCreate()->onStartCommand()->onBind()->onUnbind()->onRebind()

說明:混合呼叫時,要兩兩對應,不要相互巢狀(類似於html標籤)
服務只能解綁一次,多次會報錯
建議在Activity的onDestroy方法中解綁掉服務
startService用於保證服務的後臺執行,bindService用於呼叫服務的方法

2.2.3 Service分類

9821298-d632d29ed7e6c04c.png

1. 本地Service

這是最普通、最常用的後臺服務Service。

(1) 使用步驟

步驟1:新建子類繼承Service類
需重寫父類的onCreate()、onStartCommand()、onDestroy()和onBind()方法
步驟2:構建用於啟動Service的Intent物件
步驟3:呼叫startService()啟動Service、呼叫stopService()停止服務
步驟4:在AndroidManifest.xml裡註冊Service

屬性說明

  • android:name Service的類名
  • android:label Service的名字,若不設定,預設為Service類名
  • android:icon Service的圖示
  • android:permission 宣告此Service的許可權,提供了該許可權的應用才能控制或連線此服務
  • android:process 表示該服務是否在另一個程式中執行(遠端服務) 不設定預設為本地服務;remote則設定成遠端服務
  • android:enabled 是否可用即是否可以被系統例項化
  • android:exported 是否能被其他應用隱式呼叫。 預設值是由service中有無intent-filter決定的,如果有intent-filter,預設值為true,否則為false。為false的情況下,即使有intent-filter匹配,也無法開啟,即無法被其他應用隱式呼叫

2. 可通訊Service

例項Demo

步驟1:在新建子類繼承Service類,並新建一個子類繼承自Binder類、寫入與Activity關聯需要的方法、建立例項

public class MyService extends Service {

    private MyBinder mBinder = new MyBinder();

    @Override
    public void onCreate() {
        super.onCreate();
        System.out.println("執行了onCreat()");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        System.out.println("執行了onStartCommand()");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        System.out.println("執行了onDestory()");
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        System.out.println("執行了onBind()");
        //返回例項
        return mBinder;
    }


    @Override
    public boolean onUnbind(Intent intent) {
        System.out.println("執行了onUnbind()");
        return super.onUnbind(intent);
    }

    //新建一個子類繼承自Binder類
    class MyBinder extends Binder {
        public void service_connect_Activity() {
            System.out.println("Service關聯了Activity,並在Activity執行了Service的方法");
        }
    }
}

步驟2:在Activity通過呼叫MyBinder類中的public方法來實現Activity與Service的聯絡,即實現了Activity指揮Service幹什麼Service就去幹什麼的功能

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button startService;
    private Button stopService;
    private Button bindService;
    private Button unbindService;

    private MyService.MyBinder myBinder;


    //建立ServiceConnection的匿名類
    private ServiceConnection connection = new ServiceConnection() {

        //重寫onServiceConnected()方法和onServiceDisconnected()方法
        //在Activity與Service建立關聯和解除關聯的時候呼叫
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }

        //在Activity與Service解除關聯的時候呼叫
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //例項化Service的內部類myBinder
            //通過向下轉型得到了MyBinder的例項
            myBinder = (MyService.MyBinder) service;
            //在Activity呼叫Service類的方法
            myBinder.service_connect_Activity();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startService = (Button) findViewById(R.id.startService);
        stopService = (Button) findViewById(R.id.stopService);
        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);
        bindService = (Button) findViewById(R.id.bindService);
        unbindService = (Button) findViewById(R.id.unbindService);
        bindService.setOnClickListener(this);
        unbindService.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            //點選啟動Service
            case R.id.startService:
                //構建啟動服務的Intent物件
                Intent startIntent = new Intent(this, MyService.class);
                //呼叫startService()方法-傳入Intent物件,以此啟動服務
                startService(startIntent);
                break;
            //點選停止Service
            case R.id.stopService:
                //構建停止服務的Intent物件
                Intent stopIntent = new Intent(this, MyService.class);
                //呼叫stopService()方法-傳入Intent物件,以此停止服務
                stopService(stopIntent);
                break;
            //點選繫結Service
            case R.id.bindService:
                //構建繫結服務的Intent物件
                Intent bindIntent = new Intent(this, MyService.class);
                //呼叫bindService()方法,以此停止服務
                bindService(bindIntent,connection,BIND_AUTO_CREATE);
                //引數說明
                //第一個引數:Intent物件
                //第二個引數:上面建立的Serviceconnection例項
                //第三個引數:標誌位
                //這裡傳入BIND_AUTO_CREATE表示在Activity和Service建立關聯後自動建立Service
                //這會使得MyService中的onCreate()方法得到執行,但onStartCommand()方法不會執行
                break;
            //點選解綁Service
            case R.id.unbindService:
                //呼叫unbindService()解綁服務
                //引數是上面建立的Serviceconnection例項
                unbindService(connection);
                break;
                default:
                    break;
        }
    }
}

3. 前臺Service

前臺Service和後臺Service(普通)最大的區別就在於:

  • 前臺Service在下拉通知欄有顯示通知(如下圖),但後臺Service沒有;

    9821298-9ff0daa2e38d37f3.png

  • 前臺Service優先順序較高,不會由於系統記憶體不足而被回收;後臺Service優先順序較低,當系統出現記憶體不足情況時,很有可能會被回收

//用法很簡單,只需要在原有的Service類對onCreate()方法進行稍微修改即可
@Override
public void onCreate() {
    super.onCreate();
    System.out.println("執行了onCreat()");

    //新增下列程式碼將後臺Service變成前臺Service
    //構建"點選通知後開啟MainActivity"的Intent物件
    Intent notificationIntent = new Intent(this, MainActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

    //新建Builer物件
    Notification.Builder builer = new Notification.Builder(this);
    builer.setContentTitle("前臺服務通知的標題");//設定通知的標題
    builer.setContentText("前臺服務通知的內容");//設定通知的內容
    builer.setSmallIcon(R.mipmap.ic_launcher);//設定通知的圖示
    builer.setContentIntent(pendingIntent);//設定點選通知後的操作

    Notification notification = builer.getNotification();//將Builder物件轉變成普通的notification
    startForeground(1, notification);//讓Service變成前臺Service,並在系統的狀態列顯示出來
}

4. 使用場景

9821298-79c1a678a78f4d73.png

2.2.4 其他

1. Service和Thread的區別

Service和Thread之間沒有任何關係,之所以有不少人會把它們聯絡起來,主要因為Service的後臺概念

後臺的定義:後臺任務執行完全不依賴UI,即使Activity被銷燬,或者程式被關閉,只要程式還在,後臺任務就可以繼續執行

9821298-67af86a7d50b4b55.png

一般來說,會將Service和Thread聯合著用,即在Service中再建立一個子執行緒(工作執行緒)去處理耗時操作邏輯,如下:

@Override  
public int onStartCommand(Intent intent, int flags, int startId) {  
    //新建工作執行緒
    new Thread(new Runnable() {  
        @Override  
        public void run() {  
            // 開始執行後臺任務  
        }  
    }).start();  
    return super.onStartCommand(intent, flags, startId);  
}  

class MyBinder extends Binder {  
    public void service_connect_Activity() {  
        //新建工作執行緒
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                // 執行具體的下載任務  
            }  
        }).start();  
    }  
}  

2. IntentService

(1) 定義

Android裡的一個封裝類,繼承四大元件之一的Service

(2) 作用

處理非同步請求 & 實現多執行緒

(3) 使用場景

執行緒任務需按順序在後臺執行

  • 最常見的場景:離線下載
  • 不符合多個資料同時請求的場景:所有的任務都在同一個Thread looper裡執行
(4) 使用步驟

步驟1:定義 IntentService的子類

需傳入執行緒名稱、複寫onHandleIntent()方法

public class myIntentService extends IntentService {

    /** 
    * 在建構函式中傳入執行緒名字
    **/  
    public myIntentService() {
        // 呼叫父類的建構函式
        // 引數 = 工作執行緒的名字
        super("myIntentService");
    }

    /** 
     * 複寫onHandleIntent()方法
     * 根據 Intent實現 耗時任務 操作
     **/  
    @Override
    protected void onHandleIntent(Intent intent) {

        // 根據 Intent的不同,進行不同的事務處理
        String taskName = intent.getExtras().getString("taskName");
        switch (taskName) {
            case "task1":
                Log.i("myIntentService", "do task1");
                break;
            case "task2":
                Log.i("myIntentService", "do task2");
                break;
            default:
                break;
        }
    }

    @Override
    public void onCreate() {
        Log.i("myIntentService", "onCreate");
        super.onCreate();
    }
    /** 
     * 複寫onStartCommand()方法
     * 預設實現 = 將請求的Intent新增到工作佇列裡
     **/  
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("myIntentService", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.i("myIntentService", "onDestroy");
        super.onDestroy();
    }
}

步驟2:在Manifest.xml中註冊服務

<service android:name=".myIntentService">
    <intent-filter >
        <action android:name="cn.scu.finch"/>
    </intent-filter>
</service>

步驟3:在Activity中開啟Service服務

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
            // 同一服務只會開啟1個工作執行緒
            // 在onHandleIntent()函式裡,依次處理傳入的Intent請求
            // 將請求通過Bundle物件傳入到Intent,再傳入到服務裡

            // 請求1
            Intent i = new Intent("cn.scu.finch");
            Bundle bundle = new Bundle();
            bundle.putString("taskName", "task1");
            i.putExtras(bundle);
            startService(i);

            // 請求2
            Intent i2 = new Intent("cn.scu.finch");
            Bundle bundle2 = new Bundle();
            bundle2.putString("taskName", "task2");
            i2.putExtras(bundle2);
            startService(i2);
            startService(i);  //多次啟動
        }
    }

測試結果

9821298-836169999b8b7359.png

3. 程式優先順序

  • 前臺程式:某個Activity可見,獲得焦點
  • 可見程式:某個Activity可見,但是沒有焦點
  • 服務程式:有一個服務在後臺執行
  • 後臺程式:沒有任何服務,開啟一個Activity然後最小化(容易被回收)
  • 空程式:沒有任何活動元件存在的程式(容易被回收)

4. AIDL

(1) 簡介

為了實現跨程式通訊(IPC),實現程式之間的資料交換

(2) 步驟

①:服務端建立.aidl檔案
②:服務端建立Service,並在onBind中返回一個IBinder(介面物件(代理人))

IBinder binder = new IMyAidlInterface.Stub() {
    @Override
    public int add(int num1, int num2) throws RemoteException {
         return num1 + num2;
    }
};

③:客戶端建立(複製)和服務端一樣的.aidl檔案(包名也必須一致)
④:客戶端建立ServiceConnection的子類,並實現其onServiceConnected(ComponentName name, IBinder service),在方法中(service就是中間人)iMyAdil = IMyAidlInterface.Stub.asInterface(service),客戶端可以使用服務端的方法了
⑤:客戶端bindService

(3) AIDL支援的資料型別(支援其實就是定義aidl的時候,引數可以使用的型別)

基本資料型別(除short),String,CharSequence,List(僅支援ArrayList),Map(僅支援HashMap),Parcelable

注意:
①:非基本資料型別,需要用in,out,inout指定資料的走向
②:複雜型別(如Book)必須實現Parcelable,且需要Book.aidl(內容parcelable Book;)—— 包名必須和Book.java相同,無論是否相同的包,都需要匯入包
③:AIDL介面中只支援方法,不支援宣告靜態變數

2.3 IPC與多程式

2.3.1 IPC簡介

執行緒:① CPU最小的排程單元 ② 一種有限的系統資源
程式:一個執行單元。一般指一個程式或一個應用。一個程式可以包含多個執行緒。
IPC:程式間通訊。

多執行緒的情況
1)因為某些原因自身需要採用多程式模式來實現。比如:某些模組需要執行在單獨程式;為了加大一個應用可以使用的記憶體。
2)需要從其他應用獲取資料。

2.3.2 Android中的多程式模式

1. 開啟多程式模式

在Android中一個應用開啟多程式唯一辦法:給四大元件在AndroidMenifest.xml中指定android:process屬性。

<service
    android:name=".MyService"
    android:process=":remote" />
<service
    android:name=".SecondService"
    android:process="com.example.xiang.myapplication.remote" />

預設程式名是包名。“:remote”是一種省略寫法,完整名為“com.example.xiang.myapplication:remote”程式名,以“:”開頭,屬於當前應用的私有程式,其他應用的元件不可以和它跑在同一個程式中。

2. 多程式模式的執行機制

Android系統為==每一個程式分配了一個獨立的虛擬機器==,不同的虛擬機器在記憶體分配上有不同的地址空間。將導致存在如下問題:
1)靜態成員和單例完全失效
2)執行緒同步機制完全失效
3)SharedPreferences的可靠性下降
4)Application會多次建立
不同的程式擁有獨立的虛擬機器、Application和記憶體空間,導致通過記憶體來共享資料,都會共享失敗。

Android的IPC方式
1)Intent
2)檔案共享方式
3)Binder(AIDL和Messenger)
4)ContentProvider
5)Socket

2.3.3 IPC的基礎概念

為什麼要序列化?
1)永久性儲存物件的位元組序列到本地檔案中
2)通過序列化在網路中傳遞物件
3)通過序列化在程式間傳遞物件

1. Serializable介面

Serializable是Java提供的一個序列化介面,是一個空介面,為物件提供標準的序列化和反序列化操作。

private static final long serialVersionUID = 8154678445665565611L;

serialVersionUID是用來輔助序列化和序列化的過程,原則上==序列化後的資料中的serialVersionUID只有和當前類的serialVersionUID相同才能夠正常地被反序列化==。一般我們應該手動指定serialVersionUID的值,比如1L(或者根據類結構生成hash值)。若不指定,反序列化時當前類有所改變(比如增加或者刪除了成員變數),那麼系統會==根據類結構重新計算當前類的hash值==並賦給serialVersionUID,導致serialVersionUID不一致,於是反序列化失敗,程式就會crash。若手動指定了,可以在很大程度上避免序列化失敗。比如當版本升級後,可能刪除/增加了某個成員變數,程式仍然能夠最大限度的恢復資料。(若類結構發生了非常規改變,比如改了類名,修改了成員變數的型別,還是會反序列化失敗)

靜態成員變數屬於類不屬於物件,不會參加序列化
用transient標記的成員變數不會參與序列化

2. Parcelable介面

主要基於Parcel實現。

Serializable介面是JAVA中的序列化介面,==使用簡單開銷大==(序列化和反序列化過程中包含大量的IO操作)。Parcelable介面是Android的序列化方式,==使用麻煩效率高==。
1)在使用記憶體的時候,Parcelable比Serializable效能高(Serializable在序列化的時候會產生大量的臨時變數,從而引起頻繁的GC),所以推薦使用Parcelable
2)Parcelable不能使用在要將資料儲存在磁碟上的情況,因為在外界有變化的情況下,Parcelable不能很好的保證資料的持續性。==將物件序列化到儲存裝置==或==將物件序列化後通過網路傳輸==,建議使用Serializable介面。

3. Binder

Binder是什麼?
a) 實現了IBinder介面的一個類。
b) 跨程式通訊的==媒介==。
c) ServiceManager連線==各種Manager和相應的ManagerService的橋樑==。

(1) AIDL介面的建立

在Android開發中,Binder主要用在Service中,包括Messenger(底層其實是AIDL)和AIDL。

//Book.java
package com.example.xiang.myapplication;

import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable {
    private int bookId;
    private String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    /**
     * 幾乎所有情況都返回0
     * @return
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * 實現序列化
     * @param parcel
     * @param i
     */
    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeInt(bookId);
        parcel.writeString(bookName);
    }

    /**
     * 實現反序列化
     */
    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    public Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }
}

//Book.aidl
package com.example.xiang.myapplication;

parcelable Book;

// IBookManager.aidl
package com.example.xiang.myapplication;

import com.example.xiang.myapplication.Book;//必須匯入,否則報錯

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

Book.aidl是Book類在AIDL中的宣告。IBookManager.aidl是定義的一個介面,雖然Book類和IBookManager在同一個包中,但是還是要顯示匯入Book類。目錄結構如下:

9821298-5a5d7a0b5c354773.png
AIDL目錄結構

(新建Book.aidl時候,直接填Book為名字時候會報錯,只有先建立完之後再RENAME才不會報錯)

(2) Binder類分析

系統為IBookManager.aidl自動生成的Binder類

package com.example.xiang.myapplication;

public interface IBookManager extends android.os.IInterface {
   
    public static abstract class Stub extends Binder implements IBookManager {
        //Binder的唯一標識
        private static final String DESCRIPTOR = "com.example.xiang.myapplication.IBookManager";
       
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        public static IBookManager asInterface(IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof IBookManager))) {
                return ((IBookManager) iin);
            }
            return new IBookManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, Parcel data,
        Parcel reply, int flags) throws RemoteException {
             switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    List<Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements IBookManager {
            private IBinder mRemote;

            Proxy(IBinder remote) {
                mRemote = remote;
            }

            @Override
            public IBinder asBinder() {
                return mRemote;
            }

            public String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public List<Book> getBookList() throws RemoteException {
                Parcel _data = Parcel.obtain();
                Parcel _reply = Parcel.obtain();
                List<Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    //_data寫入引數
                    //發起遠端呼叫,當前執行緒掛起
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(Book book) throws RemoteException {
                Parcel _data = Parcel.obtain();
                Parcel _reply = Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_getBookList = (IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public List<Book> getBookList() throws RemoteException;

    public void addBook(Book book) throws RemoteException;
}

Binder類中比較重要的方法有2個:==transact方法和onTransact方法==

  • asInterface //將服務端的Binder物件轉換為客戶端所需的AIDL介面型別的物件,如果C/S位於同一程式,此方法返回就是服務端的==Stub物件==本身,否則返回的就是系統封裝後的==Stub.Proxy物件==
  • onTransact//執行在服務端
  • Proxy#getBookList//執行在客戶端,內部實現過程如下:首先建立該方法所需要的輸入型物件Parcel物件_data,輸出型Parcel物件_reply和返回值物件List。然後把該方法的引數資訊寫入_data(如果有引數);接著呼叫transact方法發起RPC( 遠端過程呼叫),同時當前執行緒掛起(因此不能在UI執行緒中發起遠端請求);然後服務端的onTransact方法會被呼叫(服務端的Binder方法==執行線上程池==,所以需要採用同步方式實現),直到RPC過程返回後,當前執行緒繼續執行,並從_reply中取出RPC過程的返回結果,最後返回_reply中的資料。
1944615-3c92d9d160957e78.png
Binder工作機制

AIDL的本質:==系統提供的一個快速實現Binder的工具而已==。

(3) 遠端服務端Service的實現
public class BookManagerService extends Service {
    private static final String TAG = "BookManagerService";
    //CopyOnWriteArrayList支援併發讀/寫
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "Android開發藝術探索"));
        mBookList.add(new Book(2, "Android進階之光"));
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

//註冊Service
<service
    android:name="com.example.service.MessengerService"
    android:process=":remote" />

AIDL方法(getBookList和addBook)是執行在Binder執行緒池中的,所以需要處理執行緒同步,這裡採用CopyOnWriteArrayList來進行自動的執行緒同步。

(4) 客戶端的實現
public class BookManagerActivity extends Activity {
    private static final String TAG = "BookManagerActivity";
    
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            try {
                List<Book> list = bookManager.getBookList();
                Log.d(TAG, "查詢圖書列表:" + list.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        unbindService(connection);
        super.onDestroy();
    }
}

服務端的方法可能需要很久才能執行完畢,上面這樣寫的目的是為了更好的瞭解AIDL的實現步驟。
物件是不能跨程式直接傳輸的,物件跨程式傳輸的本質是反序列化過程,這也是為什麼AIDL中定義的物件必須實現Parcelable介面。

4. Binder的兩個重要方法

linkToDeath(DeathRecipient recipient, int flags) //設定Binder的死亡代理。當Binder死亡時,系統會回撥DeathRecipient的binderDied()方法,所以需要在此方法中移除之前繫結的binder代理(呼叫unlinkToDeath)並重新繫結遠端服務
unlinkToDeath(DeathRecipient recipient, int flags) //移除死亡代理
isBinderAlive() //判斷Binder是否死亡

DeathRecipient是IBinder的一個內部介面,裡面只有一個方法binderDied()

5. 補充說明

  • 自定義的Parcelable物件(如上例中的Book類),必須新建一個和它同名的AIDL檔案(Book.aidl),並新增相應的內容。
  • 自定義Parcelable物件和AIDL物件必須要顯式import進來。
  • 除了基本資料型別的其他型別引數,都需要標上方向:in、out或者inout。
  • AIDL介面中只支援方法,不支援宣告靜態常量。
  • 建議把所有和AIDL相關的類和檔案全部放在同一個包中,好處是,若客戶端在另一應用(模組),複製整個包即可。
  • AIDL的包結構在服務端和客戶端必須保持一致,否則執行出錯。

AIDL檔案支援的資料型別
1)基本資料型別(int、long、char、boolean等)
2)String和CharSequence
3)List:只支援ArrayList,裡面的每個元素必須被AIDL所支援
4)Map:只支援HashMap,裡面的每個元素必須被AIDL所支援,包括key和value
5)Parcelable:所有實現了Parcelable介面的物件
6)AIDL:所有AIDL介面本身也可以在AIDL檔案中使用

2.4 BroadcastReceiver

2.4.1 簡介

1. 定義

系統級的監聽器,預設執行在本程式的主執行緒

2. 作用

用於監聽應用發出的廣播訊息,並做出響應

最常見的應用場景
a. 不同元件之間通訊(包括應用內 / 不同應用之間)
b. Android系統在特定情況下與App之間的訊息通訊(如當電話呼入時、網路可用時)
c. 多執行緒通訊

3. 實現原理

1)自定義廣播接收者BroadcastReceiver,並複寫onRecvice()方法;
2)通過Binder機制向AMS(Activity Manager Service)進行註冊;
3)廣播傳送者通過Binder機制向AMS傳送廣播;
4)AMS查詢符合相應條件(IntentFilter/Permission等)的BroadcastReceiver,將廣播傳送到BroadcastReceiver(一般情況下是Activity)相應的訊息迴圈佇列中;
5)訊息迴圈執行拿到此廣播,回撥BroadcastReceiver中的onReceive()方法。

2.4.2 使用流程

1. 自定義BroadcastReceiver

新建一個BroadcastReceiver子類,並重寫onReceive(Context context, Intent intent)方法

注意
a)廣播接收器執行在UI執行緒,因此,onReceive方法不能執行耗時操作,否則將導致ANR
b)執行耗時操作,考慮通過Intent啟動一個Service(不應考慮啟動新的程式,原因:BroadcastReceiver生命週期短,子執行緒沒有結束,BroadcastReceiver程式結束了,由於新程式沒有任何活動的元件,當記憶體緊張系統優先結束該程式,導致不能執行完成)

c)監聽到就會建立例項,觸發onReceive()方法,執行完該方法,銷燬例項
d)系統通過Intent激發BrocastReceiver元件時,找不到也不會報錯(區別Activity)

2. 配置

註冊的方式分為兩種:靜態註冊、動態註冊

(1) 靜態註冊

在AndroidManifest.xml裡通過標籤宣告,當此App首次啟動時,系統會自動例項化mBroadcastReceiver類,並註冊到系統中

<receiver 
    android:enabled=["true" | "false"]
    //此broadcastReceiver能否接收其他App的發出的廣播
    //預設值是由receiver中有無intent-filter決定的:如果有intent-filter,預設值為true,否則為false
    android:exported=["true" | "false"]
    android:icon="drawable resource"
    android:label="string resource"
    //繼承BroadcastReceiver子類的類名
    android:name=".mBroadcastReceiver"
    //具有相應許可權的廣播傳送者傳送的廣播才能被此BroadcastReceiver所接收
    android:permission="string"
    //BroadcastReceiver執行所處的程式
    //預設為app的程式,可以指定獨立的程式
    //注:Android四大基本元件都可以通過此屬性指定自己的獨立程式
    android:process="string" >
    //用於指定此廣播接收器將接收的廣播型別
    //本示例中給出的是用於接收網路狀態改變時發出的廣播
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
</receiver>
(2) 動態註冊

呼叫Context#registerReceiver(BroadcastReceiver receiver, IntentFilter filter) Intent註冊到系統中。所有匹配該Intent的BrocastReceiver都有可能被啟動。

注意
動態廣播最好在Activity的onResume()註冊、onPause()登出,不銷燬會導致記憶體洩漏。(重複註冊、重複登出也不允許)。不在onCreate() & onDestory() 或 onStart() & onStop()註冊、登出是因為:
當系統因為記憶體不足(優先順序更高的應用需要記憶體)要回收Activity佔用的資源時,Activity在執行完onPause()方法後就會被銷燬,有些生命週期方法onStop(),onDestory()就不會執行。當再回到此Activity時,是從onCreate方法開始執行。
假設我們將廣播的登出放在onStop(),onDestory()方法裡的話,有可能在Activity被銷燬後還未執行onStop(),onDestory()方法,即廣播仍還未登出,從而導致記憶體洩露。但是,onPause()一定會被執行,從而保證了廣播在App死亡前一定會被登出,從而防止記憶體洩露。(詳見Activity生命週期圖)

(3) 兩種註冊方式的區別
9821298-19c3c07f2e38870c.png

3. 廣播傳送者向AMS傳送廣播

呼叫Context#sendBroadcast(Intent intent)sendOrderedBroadcast(Intent intent, String receiverPermission)方法來傳送廣播

2.4.3 廣播的分類

廣播的型別主要分為5類:

  • 普通廣播(Normal Broadcast)
  • 有序廣播(Ordered Broadcast)
  • App應用內廣播(Local Broadcast)
  • 系統廣播(System Broadcast)
  • 粘性廣播(Sticky Broadcast) 已廢棄

1. 普通廣播

sendBroadcast(Intent intent)傳送的廣播。
同一時刻被所有的接收者接收到,訊息傳遞的效率高。

2. 有序廣播

sendOrderedBroadcast(Intent intent, String receiverPermission) 傳送的廣播。
按預先宣告的優先順序依次接收廣播,接收者可以將資料傳遞給下一個接收者,也可以終止廣播的傳播(abortBroadcast()方法)

設定優先順序:
①:<inter-filter.../>元素的priority屬性,範圍在-1000~1000之間
②:呼叫InterFilter的setPriority(int priority)

3. 應用內廣播

Android中的廣播可以跨App直接通訊(exported對於有intent-filter情況下預設值為true)

可能出現的問題

  • 其他App針對性發出與當前App intent-filter相匹配的廣播,由此導致當前App不斷接收廣播並處理;
  • 其他App註冊與當前App一致的intent-filter用於接收廣播,獲取廣播具體資訊。

即會出現安全性 & 效率性的問題。

解決方案

使用App應用內廣播(Local Broadcast)

  • App應用內廣播可理解為一種區域性廣播,廣播的傳送者和接收者都同屬於一個App。
  • 相比於全域性廣播(普通廣播),App應用內廣播優勢體現在:安全性高 & 效率高

具體使用1 - 將全域性廣播設定成區域性廣播

  • 註冊廣播時將exported屬性設定為false,使得非本App內部發出的此廣播不被接收;
  • 在廣播傳送和接收時,增設相應許可權permission,用於許可權驗證;
  • 傳送廣播時指定該廣播接收器所在的包名,此廣播將只會傳送到此包中的App內與之相匹配的有效廣播接收器中。
    通過intent#setPackage(packageName)指定包名

具體使用2 - 使用封裝好的LocalBroadcastManager類

Android Support包提供了一個工具,是用來在同一個應用內的不同元件間傳送Broadcast的。
使用方式上與全域性廣播幾乎相同,只是註冊/取消註冊廣播接收器和傳送廣播時將引數的context變成了LocalBroadcastManager的單一例項

注:對於LocalBroadcastManager方式傳送的應用內廣播,只能通過LocalBroadcastManager動態註冊,不能靜態註冊

//步驟1:例項化BroadcastReceiver子類 & IntentFilter mBroadcastReceiver 
mBroadcastReceiver = new mBroadcastReceiver(); 
IntentFilter intentFilter = new IntentFilter(); 
//步驟2:例項化LocalBroadcastManager的例項
localBroadcastManager = LocalBroadcastManager.getInstance(this);
//步驟3:設定接收廣播的型別 
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
//步驟4:呼叫LocalBroadcastManager單一例項的registerReceiver()方法進行動態註冊 
localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);
//取消註冊應用內廣播接收器
localBroadcastManager.unregisterReceiver(mBroadcastReceiver);
//傳送應用內廣播
Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
localBroadcastManager.sendBroadcast(intent);

2.4.4 補充說明

1. context返回值

對於不同註冊方式的廣播接收器回撥OnReceive(Context context,Intent intent)中的context返回值是不一樣的:

  • 對於靜態註冊(全域性+應用內廣播),回撥onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext;
  • 對於全域性廣播的動態註冊,回撥onReceive(context, intent)中的context返回值是:Activity Context;
  • 對於應用內廣播的動態註冊(LocalBroadcastManager方式),回撥onReceive(context, intent)中的context返回值是:Application Context。
  • 對於應用內廣播的動態註冊(非LocalBroadcastManager方式),回撥onReceive(context, intent)中的context返回值是:Activity Context;

2. 方法

  • onReceive(Context context, Intent intent) 收到廣播時回撥的方法
  • abortBroadcast()
  • setResultExtras(Bundle extras) 將處理結果放入廣播中
  • getResultExtras(boolean makeMap) Bundle 獲取上一接收者存入的資料,boolean:true,Bundle為null,建立一個空的
  • getResultData() String
  • setResultData(String data)

3. 常見系統廣播

常用的系統廣播的Action常量(對應類似android.intent.action.BOOT_COMPLETED字串)

  • ACTION_BOOT_COMPLETED :系統啟動完成
  • ACTION_SHUTDOWN :系統關閉
  • ACTION_POWER_CONNECTED :連線電源
  • ACTION_POWER_DISCONNECTED :與電源斷開
  • ACTION_BATTERY_CHANGED :電量低
  • android.provider.Telephony.SMS_REVEIVED :收到簡訊

2.5 ContentProvider

2.5.1 ContentProvider

ContentProvider為儲存和獲取資料提供統一的介面,可以在不同的應用程式之間共享資料。如果你不需要在多個應用之間共享資料,你可以直接通過SQLite來運算元據庫。

使用ContentProvider,主要有以下幾個理由:

  • ContentProvider提供了對底層資料儲存方式的抽象。比如下圖中,底層使用了SQLite資料庫,在用了ContentProvider封裝後,即使你把資料庫換成MongoDB,也不會對上層資料使用層程式碼產生影響。


    1362430-1857f913ab2e7a3d.png
  • Android框架中的一些類需要ContentProvider型別資料。如果你想讓你的資料可以使用在如SyncAdapter, Loader, CursorAdapter等類上,那麼你就需要為你的資料做一層ContentProvider封裝
  • 最主要的原因,是ContentProvider為應用間的資料互動提供了一個安全的環境。它准許你把自己的應用資料根據需求開放給其他應用進行增、刪、改、查,而不用擔心直接開放資料庫許可權而帶來的安全問題。
onCreate() boolean //當第一次訪問ContentProvider,建立完物件之後呼叫,唯一的生命週期方法
insert(Uri uri, ContentValues values) Uri  //根據Uri插入values對應的資料
delete(Uri uri, String selection, String[] selectionArgs) int    //根據Uri刪除select條件所匹配的全部記錄
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)    int //根據Uri修改select條件所匹配的全部記錄
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) Cursor    //查詢,projection:選擇的指定的列
getType(Uri uri) String    //返回當前Uri所代表的MIME型別。

2.5.2 ContentResolver

Android為我們提供了ContentResolver來統一管理與不同ContentProvider間的操作,通過URI來區別不同的ContentProvider
ContentResolver 類也提供了與ContentProvider類相對應的四個方法:

insert(Uri uri, ContentValues values) Uri    
delete(Uri uri, String selection, String[] selectionArgs) int   
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)  int   
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) Cursor 
9821298-14a35ed05e1f02eb.png
ContentResolver角色

ContentProvider中的URI有固定格式,如下圖:

1362430-b39bc91ec8e272af.png

Scheme:URI的名稱空間標識
Authority:授權資訊,用以區別不同的ContentProvider
Path:表名,用以區分ContentProvider中不同的資料表
Id:Id號,用以區別表中的不同資料

2.5.3 URI

1. URI

URI:通用資源標誌符 —— Uniform Resource Identifier
URI類:java.net.URI,是Java提供的一個類,代表了URI的一個例項
Uri類:android.net.Uri,擴充套件了JAVA中URI的一些功能來適用於Android開發

2. URI的結構

(1) 基本結構
[scheme:]scheme-specific-part[#fragment]  
[scheme:][//authority][path][?query][#fragment]  
[scheme:][//host:port][path][?query][#fragment]
http://www.java2s.com:8080/yourpath/fileName.htm?stove=10&path=32&id=4#harvic

在android中,除了scheme、authority是必須要有的,其他都可以不要,Android中可用的每種資源( 影象、視訊片段等)都可以用URI來表示

(2) 程式碼中提取
Uri.parse(String uriString)    //返回一個Uri物件
getScheme()     //獲取Uri中的scheme字串部分,在這裡即,http
getSchemeSpecificPart()   //獲取Uri中的scheme-specific-part:部分,這裡是://www.java2s.com:8080/yourpath/fileName.htm?
getFragment()   //獲取Uri中的Fragment部分,即harvic
getAuthority()   //獲取Uri中Authority部分,即www.java2s.com:8080
getPath()   //獲取Uri中path部分,即/yourpath/fileName.htm
getQuery()   //獲取Uri中的query部分,即stove=10&path=32&id=4
getHost()   //獲取Authority中的Host字串,即www.java2s.com
getPost()   //獲取Authority中的Port字串,即8080
getPathSegments() List<String>    //依次提取出Path的各個部分的字串,以字串陣列的形式輸出
getQueryParameter(String key)   //根據傳進去path中某個Key的字串,返回對應的值
(3) 絕對URI和相對URI

絕對URI:以scheme元件起始的完整格式,如http://fsjohnhuang.cnblogs.com。表示以對標識出現的環境無依賴的方式引用資源
相對URI:不以scheme元件起始的非完整格式,如fsjohnhuang.cnblogs.com。表示以對標識出現的環境有依賴的方式引用資源

(4) 不透明URI和分層URI

不透明URI:scheme-specific-part元件不是以正斜槓(/)起始的,如mailto:fsjohnhuang@xxx.com
分層URI:scheme-specific-part元件是以正斜槓(/)起始的,如http://fsjohnhuang.com

(5) UriMatcher工具類

因為Uri代表了要操作的資料,所以我們很經常需要解析Uri,並從Uri中獲取資料。Android系統提供了兩個用於操作Uri的工具類,分別為UriMatcher和ContentUris。掌握它們的使用,會便於我們的開發工作。

UriMatcher

UriMatcher本質上是一個文字過濾器,用在contentProvider中幫助我們過濾,分辨出查詢者想要查詢哪個資料表

UriMatcher(int code)   //code:匹配未成功時返回的標誌碼,一般置為:UriMatcher.NO_MATCH(值為-1)
//向UriMatcher中註冊Uri,其中authority和path組合成一個Uri,code標識該Uri對應的標識碼,path中*表示可匹配任意文字,#表示只能匹配數字
addURI(String authority, String path, int code) void    
match(Uri uri) int   //該Uri是否匹配,若匹配返回註冊時候的標誌碼,否則返回-1

matcher.addURI("com.xx","person/id/#", 2) 匹配content://com.xx/person/id/1any://com.xx/person/id/12

ContentUris工具類

其實就是在末尾加上一個id

ContentUris.withAppendedId(Uri contentUri, long id) //為路徑加上ID部分
ContentUris.parseId(Uri contentUri) //解析Uri中的ID值

3. URL

URL = URI(scheme元件為部分已知的網路協議) + 與scheme元件標識的網路協議匹配的協議處理器(URL Protocol Handler),是URI子集

  • URI的scheme元件在URL中稱為protocol元件,一般http、https、ftp、file、data、jar等。
  • URL Protocol Handler則是一種資源定位器和根據協議建立的約束規則與資源通訊的讀寫機制,用於定位、讀寫資源。

2.5.4 ContentObserver

1. 基本認知

內容觀察者,觀察指定的Uri引起資料庫變化後通知主執行緒,然後根據需求做處理。首先在需要監測ContentProvider的應用中進行註冊(ContentResolver呼叫方法的地方),在ContentProvider中要做的就是當資料變化時進行通知。

ContentResolver相關方法:

//傳遞一個ContentObserver的子類物件進去,會回撥其onChange(boolean selfChange)
registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)
//取消對註冊的那個Uri的觀察,這裡傳進去的就是在registerContentObserver中傳遞進去的ContentObserver物件。
unregisterContentObserver(ContentObserver observer) 
notifyChange(Uri uri, ContentObserver observer)//在Provider中呼叫,通知觀察uri的觀察者,observer可以傳null

registerContentObserver方法是註冊一個觀察者例項,當指定的Uri發生改變時,這個例項會回撥例項物件做相應處理。uri:需要觀察的Uri,notifyForDescendents:如果為true表示以這個Uri為開頭的所有Uri都會被匹配到,如果為false表示精確匹配,即只會匹配這個給定的Uri。
舉個例子,假如有這麼幾個Uri:
content://com.example.studentProvider/student
content://com.example.studentProvider/student/#
content://com.example.studentProvider/student/10
content://com.example.studentProvider/student/teacher
假如觀察的Uri為content://com.example.studentProvider/student,當notifyForDescendents為true時則以這個Uri開頭的Uri的資料變化時都會被捕捉到,在這裡也就是①②③④的Uri的資料的變化都能被捕捉到,當notifyForDescendents為false時則只有①中Uri變化時才能被捕捉到。

2. 實現一個ContentObserver

直接建立一個類繼承ContentObserver,實現其onChange(boolean selfChange)方法,當指定的Uri的資料發生變化時會回撥這個方法。在此方法中,呼叫建構函式ContentObserver(Handlerhandler)中傳入的 Handler物件傳送訊息到Handler中,做相應的處理。

2.5.5 資料共享

如何讓其他應用也可以訪問此應用中的資料?

1. android:sharedUserId

向此應用設定一個android:sharedUserId,然後需要訪問此資料的應用也設定同一個sharedUserId,具有同樣的sharedUserId的應用間可以共享資料。

不足:
1)不夠安全
2)無法做到對不同資料設定不同讀寫許可權的管理

2. android:exported

  • android:exported 設定此provider是否可以被其他應用使用。
  • android:readPermission 該provider的讀許可權的標識
  • android:writePermission 該provider的寫許可權標識
  • android:permission 該provider的讀寫許可權標識
  • android:grantUriPermissions 臨時許可權標識,true時,意味著該provider下所有資料均可被臨時使用;false時,則反之。但可以通過設定<grantUriPermission>標籤來指定哪些路徑可以被臨時使用。舉個例子,比如你開發了一個郵箱應用,其中含有附件需要第三方應用開啟,但第三方應用又沒有向你申請該附件的讀許可權,但如果你設定了此標籤,則可以在start第三方應用時,傳入FLAG_GRANT_READ_URI_PERMISSION或FLAG_GRANT_WRITE_URI_PERMISSION來讓第三方應用臨時具有讀寫該資料的許可權。

例項:
1)宣告一個許可權
<permission android:name="me.pengtao.READ" android:protectionLevel="normal"/>
2)配置

<provider
android:authorities="me.pengtao.contentprovidertest"
android:name=".provider.TestProvider"
android:readPermission="me.pengtao.READ"
android:exported="true">
</provider>

3)其他應用中可以使用以下許可權來對TestProvider進行訪問<uses-permission android:name="me.pengtao.READ"/>

對不同的資料表有不同的許可權操作,要如何做呢?Android為這種場景提供了provider的子標籤<path-permission>,path-permission包括了以下幾個標籤。

<path-permission android:path="string"
android:pathPrefix="string"
android:pathPattern="string"
android:permission="string"
android:readPermission="string"
android:writePermission="string" />

2.5.6 開發ContentProvider步驟

1. 步驟一:暴露資料

  1. 開發一個ContentProvider的子類,預設需要實現上面的6個方法。
    資料訪問的方法(如:insert和update)可能被多個執行緒同時呼叫,此時必須是執行緒安全的。其他方法(如: onCreate())只能被應用的主執行緒呼叫,它應當避免冗長的操作。ContentResolver(內容解析者)請求被自動轉發到合適的內容提供者例項,所以子類不需要擔心跨程式呼叫的細節。

例項程式碼
實現的ContentProvider子類中:

private final static int TEST = 100;

static UriMatcher buildUriMatcher() {
    final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
    final String authority = TestContract.CONTENT_AUTHORITY;//必須和清單檔案中配置的一致
    matcher.addURI(authority, TestContract.PATH_TEST, TEST);
    return matcher;
}

@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    Cursor cursor = null;
    switch (buildUriMatcher().match(uri)) {
        case TEST://匹配不同的表
            cursor = db.query(TestContract.TestEntry.TABLE_NAME, projection, selection, selectionArgs, sortOrder, null, null);
            break;
    }
    return cursor;
}

@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
    final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    Uri returnUri;
    long _id;
    switch ( buildUriMatcher().match(uri)) {
        case TEST:
            _id = db.insert(TestContract.TestEntry.TABLE_NAME, null, values);
            if ( _id > 0 )
                returnUri = TestContract.TestEntry.buildUri(_id);
            else
                throw new android.database.SQLException("Failed to insert row into " + uri);
            break;
        default:
            throw new android.database.SQLException("Unknown uri: " + uri);
    }
    return returnUri;
}

  1. 在清單檔案中配置
<provider    
    android:authorities="me.pengtao.contentprovidertest" //一般為包名.含義 
    android:name=".provider.TestProvider"
    android:exprorted="true"/>//是否允許其他應用呼叫

2. 步驟二:獲取ContentResolver物件,並使用

  1. 獲取ContentResolver物件。Context的方法:getContentResolver(),因此Activity,Service都能獲得該物件
  2. 呼叫ContentResolver的insert,delete,update,query方法
ContentValues contentValues = new ContentValues();
contentValues.put(TestContract.TestEntry.COLUMN_NAME, "peng");
contentValues.put(TestContract.TestEntry._ID, System.currentTimeMillis());
getContentResolver().insert(TestContract.TestEntry.CONTENT_URI, contentValues);
Cursor cursor = getContentResolver().query(TestContract.TestEntry.CONTENT_URI, null, null, null, null);
try {
    Log.e("ContentProviderTest", "total data number = " + cursor.getCount());
    cursor.moveToFirst();
    Log.e("ContentProviderTest", "total data number = " + cursor.getString(1));
} finally {
    cursor.close();
}

2.6 四大元件的工作過程

2.6.1 四大元件的執行狀態

Android的四大元件除了BroadcastReceiver以外,都需要在AndroidManifest檔案註冊,BroadcastReceiver可以通過程式碼註冊。呼叫方式上,除了ContentProvider以外的三種元件都需要藉助intent。

Activity

是一種展示型元件,用於向使用者直接地展示一個介面,並且可以接收使用者的輸入資訊從而進行互動,扮演的是一個前臺介面的角色。Activity的啟動由intent觸發,有隱式和顯式兩種方式。一個Activity可以有特定的啟動模式,finish方法結束Activity執行。

Service

是一種計算型元件,在後臺執行一系列計算任務。它==本身還是執行在主執行緒中的==,所以耗時的邏輯仍需要單獨的執行緒去完成。Activity只有一種狀態:啟動狀態。而service有兩種:啟動狀態和繫結狀態。當service處於繫結狀態時,外界可以很方便的和service進行通訊,而在啟動狀態中是不可與外界通訊的。Service可以停止,需要靈活採用stopServiceunBindService

BroadcastReceiver

是一種訊息型元件,用於在不同的元件乃至不同的應用之間傳遞消
息。

  • 靜態註冊
    在清單檔案中進行註冊廣播, 這種廣播在應用安裝時會被系統解析, 此種形式的廣播不需要應用啟動就可以接收到相應的廣播.
  • 動態註冊
    需要通過Context.registerReceiver()來實現, 並在不需要的時候通過Context#unRegisterReceiver()來解除廣播. 此種形態的廣播要應用啟動才能註冊和接收廣播. 在實際開發中通過Context的一系列的send方法來傳送廣播, 被髮送的廣播會被系統傳送給感興趣的廣播接收者,傳送和接收的過程的匹配是通過廣播接收者的<intent-filter>來描述的.可以實現低耦合的觀察者模式, 觀察者和被觀察者之間可以沒有任何耦合. 但廣播不適合來做耗時操作.

ContentProvider

是一種資料共享型元件,用於向其他元件乃至其他應用共享資料。在它內部維持著一份資料集合, 這個資料集合既可以通過資料庫來實現, 也可以採用其他任何型別來實現, 例如list或者map. ContentProvider對資料集合的具體實現並沒有任何要求.要注意處理好內部的insert, delete, update, query方法的執行緒同步, 因為這幾個方法是==在Binder執行緒池被呼叫==.

2.6.2 Activity的工作過程

(1) Activity的所有 startActivity 過載方法最終都會呼叫 startActivityForResult。
(2) 呼叫 mInstrumentation.execStartActivity.execStartActivity()方法。
(3) 程式碼中啟動Activity的真正實現是由ActivityManagerNative.getDefault().startActivity()方法完成的. ActivityManagerService簡稱AMS. AMS繼承自ActivityManagerNative(), 而ActivityManagerNative()繼承自Binder並實現了IActivityManager這個Binder介面, 因此AMS也是一個Binder, 它是IActivityManager的具體實現.ActivityManagerNative.getDefault()本質是一個IActivityManager型別的Binder物件, 因此==具體實現是AMS==.
(4) 在ActivityManagerNative中, AMS這個Binder物件採用單例模式對外提供, Singleton是一個單例封裝類. 第一次呼叫它的get()方法時會通過create方法來初始化AMS這個Binder物件, 在後續呼叫中會返回這個物件.
(5) AMS的startActivity()過程

  • checkStartActivityResult () 方法檢查啟動Activity的結果( 包括檢查有無在
    manifest註冊)
  • Activity啟動過程經過兩次轉移, 最後又轉移到了mStackSupervisor.startActivityMayWait()這個方法, 所屬類為ActivityStackSupervisor. 在startActivityMayWait()內部又呼叫了startActivityLocked()這裡會返回結果碼就是之前checkStartActivityResult()用到的。
  • 方法最後會呼叫startActivityUncheckedLocked(), 然後又呼叫了ActivityStack#resumeTopActivityLocked(). 這個時候啟動過程已經從ActivityStackSupervisor轉移到了ActivityStack類中.

    (6) 在最後的 ActivityStackSupervisor. realStartActivityLocked() 中,呼叫了 app.thread.scheduleLaunchActivity() 方法。 這個app.thread是ApplicationThread 型別,繼承於 IApplicationThread 是一個Binder類,內部是各種啟動/停止 Service/Activity的介面。
    (7) 在ApplicationThread中, scheduleLaunchActivity() 用來啟動Activity,裡面的實現就是傳送一個Activity的訊息( 封裝成 從ActivityClientRecord 物件) 交給Handler處理。這個Handler有一個簡潔的名字 H 。
    (8) 在H的 handleMessage() 方法裡,通過 handleLaunchActivity() 方法完成Activity物件的建立和啟動,並且ActivityThread通過handleResumeActivity()方法來呼叫被啟動的onResume()這一生命週期方法。PerformLaunchActivity()主要完成了如下幾件事:
  • 從ActivityClientRecord物件中獲取待啟動的Activity元件資訊
  • 通過 Instrumentation 的 newActivity 方法使用類載入器建立Activity物件
  • 通過 LoadedApk 的makeApplication方法嘗試建立Application物件,通過類載入器實現( 如果Application已經建立過了就不會再建立)
  • 建立 ContextImpl 物件並通過Activity的 attach 方法完成一些重要資料的初始化(ContextImpl是一個很重要的資料結構, 它是Context的具體實現, Context中的大部分邏輯都是由ContentImpl來完成的. ContextImpl是通過Activity的attach()方法來和Activity建立關聯的,除此之外, 在attach()中Activity還會完成Window的建立並建立自己和Window的關聯, 這樣當Window接收到外部輸入事件收就可以將事件傳遞給Activity.)
  • 通過 mInstrumentation.callActivityOnCreate(activity, r.state) 方法呼叫Activity的 onCreate 方法

2.6.3 Service的工作過程

  • 啟動狀態:執行後臺計算
  • 繫結狀態:用於其他元件與Service互動

兩種狀態是可以共存的

1. Service的啟動過程

image

(1) Service的啟動從 ContextWrapper 的 startService 開始
(2) 在ContextWrapper中,大部分操作通過一個 ContextImpl 物件mBase實現
(3) 在ContextImpl中, mBase.startService() 會呼叫 startServiceCommon 方法,而
startServiceCommon方法又會通過 ActivityManagerNative.getDefault() ( 實際上就是AMS) 這個物件來啟動一個服務。
(4) AMS會通過一個 ActiveService 物件( 輔助AMS進行Service管理的類,包括Service的啟動,繫結和停止等) mServices來完成啟動Service: mServices.startServiceLocked() 。
(5) 在mServices.startServiceLocked()最後會呼叫 startServiceInnerLocked() 方法:將Service的資訊包裝成一個 ServiceRecord 物件,ServiceRecord一直貫穿著整個Service的啟動過程。通過 bringUpServiceLocked()方法來處理,bringUpServiceLocked()又呼叫了 realStartServiceLocked() 方法,這才真正地去啟動一個Service了。
(6) realStartServiceLocked()方法的工作如下:

  • app.thread.scheduleCreateService() 來建立Service並呼叫其onCreate()生命週期方法
  • sendServiceArgsLocked() 呼叫其他生命週期方法,如onStartCommand()
  • app.thread物件是 IApplicationThread 型別,實際上就是一個Binder,具體實現是ApplicationThread繼承ApplictionThreadNative

(7) 具體看Application對Service的啟動過程app.thread.scheduleCreateService():通過 sendMessage(H.CREATE_SERVICE , s) ,這個過程和Activity啟動過程類似,同時通過傳送訊息給Handler H來完成的。
(8) H會接受這個CREATE_SERVICE訊息並通過ActivityThread的 handleCreateService() 來完成Service的最終啟動。
(9) handleCreateService()完成了以下工作:

  • 通過ClassLoader建立Service物件
  • 建立Service內部的Context物件
  • 建立Application,並呼叫其onCreate()( 只會有一次)
  • 通過 service.attach() 方法建立Service與context的聯絡( 與Activity類似)
  • 呼叫service的 onCreate() 生命週期方法,至此,Service已經啟動了
  • 將Service物件儲存到ActivityThread的一個ArrayMap中

2. Service的繫結過程

image

和service的啟動過程類似的:
(1) Service的繫結是從 ContextWrapper 的 bindService 開始
(2) 在ContextWrapper中,交給 ContextImpl 物件 mBase.bindService()
(3) 最終會呼叫ContextImpl的 bindServiceCommon 方法,這個方法完成兩件事:

  • 將客戶端的ServiceConnection轉化成 ServiceDispatcher.InnerConnection 物件。ServiceDispatcher連線ServiceConnection和InnerConnection。這個過程通過 LoadedApk 的 getServiceDispatcher 方法來實現,將客戶端的ServiceConnection和ServiceDispatcher的對映關係存在一個ArrayMap中。
  • 通過AMS來完成Service的具體繫結過程 ActivityManagerNative.getDefault().bindService()

(4) AMS中,bindService()方法再呼叫 bindServiceLocked() ,bindServiceLocked()再呼叫 bringUpServiceLocked() ,bringUpServiceLocked()又會呼叫 realStartServiceLocked() 。
(5) AMS的realStartServiceLocked()會呼叫 ActiveServices 的requrestServiceBindingLocked() 方法,最終是呼叫了ServiceRecord物件r的 app.thread.scheduleBindService() 方法。
(6) ApplicationThread的一系列以schedule開頭的方法,內部都通過Handler H來中轉:scheduleBindService()內部也是通過 sendMessage(H.BIND_SERVICE , s)
(7) 在H內部接收到BIND_SERVICE這類訊息時就交給 ActivityThread 的handleBindService() 方法處理:

  • 根據Servcie的token取出Service物件
  • 呼叫Service的 onBind() 方法,至此,Service就處於繫結狀態了。
  • 這時客戶端還不知道已經成功連線Service,需要呼叫客戶端的binder物件來呼叫客戶端的ServiceConnection中的 onServiceConnected() 方法,這個通過 ActivityManagerNative.getDefault().publishService() 進行。ActivityManagerNative.getDefault()就是AMS。

(8) AMS的publishService()交給ActivityService物件 mServices 的 publishServiceLocked() 來處理,核心程式碼就一句話 c.conn.connected(r.name,service) 。物件c的型別是 ConnectionRecord ,c.conn就是ServiceDispatcher.InnerConnection物件,service就是Service的onBind方法返回的Binder物件。
(9) c.conn.connected(r.name,service)內部實現是交給了mActivityThread.post(new RunnConnection(name ,service,0)); 實現。ServiceDispatcher的mActivityThread是一個Handler,其實就是ActivityThread中的H。這樣一來RunConnection就經由H的post方法從而執行在主執行緒中,因此客戶端ServiceConnection中的方法是在主執行緒中被回撥的。
(10) RunConnection的定義如下:

  • 繼承Runnable介面, run() 方法的實現也是簡單呼叫了ServiceDispatcher的 doConnected 方法。
  • 由於ServiceDispatcher內部儲存了客戶端的ServiceConntion物件,可以很方便地呼叫ServiceConntion物件的 onServiceConnected 方法。
  • 客戶端的onServiceConnected方法執行後,Service的繫結過程也就完成了。
  • 根據步驟8、9、10service繫結後通過ServiceDispatcher通知客戶端的過程可以說明ServiceDispatcher起著連線ServiceConnection和InnerConnection的作用。 至於Service的停止和解除繫結的過程,系統流程都是類似的。

2.6.4 BroadcastReceiver的工作過程

簡單回顧一下廣播的使用方法, 首先定義廣播接收者, 只需要繼承BroadcastReceiver並重寫onReceive()方法即可. 定義好了廣播接收者, 還需要註冊廣播接收者, 分為兩種靜態註冊或者動態註冊. 註冊完成之後就可以傳送廣播了.

1. 廣播的註冊過程

image

(1) 動態註冊的過程是從ContextWrapper#registerReceiver()開始的. 和Activity或者Service一樣. ContextWrapper並沒有做實際的工作, 而是將註冊的過程直接交給了ContextImpl來完成.
(2) ContextImpl#registerReceiver()方法呼叫了本類的registerReceiverInternal()方法.
(3) 系統首先從mPackageInfo獲取到IIntentReceiver物件, 然後再採用跨程式的方式向AMS傳送廣播註冊的請求. 之所以採用IIntentReceiver而不是直接採用BroadcastReceiver, 這是因為上述註冊過程中是一個程式間通訊的過程. 而BroadcastReceiver作為Android中的一個元件是不能直接跨程式傳遞的. 所有需要通過IIntentReceiver來中轉一下.
(4) IIntentReceiver作為一個Binder介面, 它的具體實現是LoadedApk.ReceiverDispatcher.InnerReceiver, ReceiverDispatcher的內部同時儲存了BroadcastReceiver和InnerReceiver, 這樣當接收到廣播的時候, ReceiverDispatcher可以很方便的呼叫BroadcastReceiver#onReceive()方法. 這裡和Service很像有同樣的類, 並且內部類中同樣也是一個Binder介面.
(5) 由於註冊廣播真正實現過程是在AMS中, 因此跟進AMS中, 首先看registerReceiver()方法, 這裡只關心裡面的核心部分. 這段程式碼最終會把遠端的InnerReceiver物件以及IntentFilter物件儲存起來, 這樣整個廣播的註冊就完成了.

2. 廣播的傳送和接收過程

廣播的傳送有幾種:普通廣播、有序廣播和粘性廣播,他們的傳送/接收流程是類似的,因此只分析普通廣播的實現。

(1) 廣播的傳送和接收, 本質就是一個過程的兩個階段. 廣播的傳送仍然開始於ContextImpl#sendBroadcase()方法, 之所以不是Context, 那是因為Context#sendBroad()是一個抽象方法. 和廣播的註冊過程一樣, ContextWrapper#sendBroadcast()仍然什麼都不做, 只是把事情交給了ContextImpl去處理.
(2) ContextImpl裡面也幾乎什麼都沒有做, 內部直接向AMS發起了一個非同步請求用於傳送廣播.
(3) 呼叫AMS#broadcastIntent()方法,繼續呼叫broadcastIntentLocked()方法。
(4) 在broadcastIntentLocked()內部, 會根據intent-filter查詢出匹配的廣播接收者並經過一系列的條件過濾. 最終會將滿足條件的廣播接收者新增到BroadcastQueue中, 接著BroadcastQueue就會將廣播傳送給相應廣播接收者.
(5) BroadcastQueue#scheduleBroadcastsLocked()方法內並沒有立即傳送廣播, 而是傳送了一個BROADCAST_INTENT_MSG型別的訊息, BroadcastQueue收到訊息後會呼叫processNextBroadcast()方法。
(6) 無序廣播儲存在mParallelBroadcasts中, 系統會遍歷這個集合並將其中的廣播傳送給他們所有的接收者, 具體的傳送過程是通過deliverToRegisteredReceiverLocked()方法實現. deliverToRegisteredReceiverLocked()負責將一個廣播傳送給一個特定的接收者, 它的內部呼叫了performReceiverLocked方法來完成具體傳送過程.
(7) performReceiverLocked()方法呼叫的ApplicationThread#scheduleRegisteredReceiver()實現比較簡單, 它通過InnerReceiver來實現廣播的接收
(8) scheduleRegisteredReceiver()方法中,receiver.performReceive()中的receiver對應著IIntentReceiver型別的介面. 而具體的實現就是ReceiverDispatcher$InnerReceiver. 這兩個巢狀的內部類是所屬在LoadedApk中的。
(9) 又呼叫了LoadedApk$ReceiverDispatcher#performReceive()的方法.在performReceiver()這個方法中, 會建立一個Args物件並通過mActivityThread的post方法執行args中的邏輯. 而這些類的本質關係就是:

  • Args: 實現類Runnable
  • mActivityThread: 是一個Handler, 就是ActivityThread中的mH. mH就是ActivityThread$H. 這個內部類H以前說過.

(10) 實現Runnable介面的Args中BroadcastReceiver#onReceive()方法被執行了, 也就是說應用已經接收到了廣播, 同時onReceive()方法是在廣播接收者的主執行緒中被呼叫的.

android 3.1開始就增添了兩個標記為. 分別是FLAG_INCLUDE_STOPPED_PACKAGES, FLAG_EXCLUDE_STOPPED_PACKAGES. 用來控制廣播是否要對處於停止的應用起作用.

  • FLAG_INCLUDE_STOPPED_PACKAGES: 包含停止應用, 廣播會傳送給已停止的應用.
  • FLAG_EXCLUDE_STOPPED_PACKAGES: 不包含已停止應用, 廣播不會傳送給已停止的應用

在android 3.1開始, 系統就為所有廣播預設新增了FLAG_EXCLUDE_STOPPED_PACKAGES標識。 當這兩個標記共存的時候以FLAG_INCLUDE_STOPPED_PACKAGES(非預設項為主).

應用處於停止分為兩種

  • 應用安裝後未執行
  • 被手動或者其他應用強停
    開機廣播同樣受到了這個標誌位的影響. 從Android 3.1開始處於停止狀態的應用同樣無法接受到開機廣播, 而在android 3.1之前處於停止的狀態也是可以接收到開機廣播的.

更多參考1參考2參考3

2.6.5 ContentProvider的工作機制

ContentProvider是一種內容共享型元件, 它通過Binder向其他元件乃至其他應用提供資料. 當ContentProvider所在的程式啟動時, ContentProvider會同時啟動併發布到AMS中. 要注意:這個時候ContentProvider的onCreate()方法是先於Application的onCreate()執行的,這一點在四大元件是少有的現象.


image

(1) 當一個應用啟動時,入口方法是ActivityThread的main方法,其中建立ActivityThread的例項並建立主執行緒的訊息佇列;
(2) ActivityThread的attach方法中會遠端呼叫ActivityManagerService的attachApplication,並將ApplicationThread提供給AMS,ApplicationThread主要用於ActivityThread和AMS之間的通訊;
(3) ActivityManagerService的attachApplication會呼叫ApplicationThread的bindApplication方法,這個方法會通過H切換到ActivityThread中去執行,即呼叫handleBindApplication方法;
(4) handleBindApplication方法會建立Application物件並載入ContentProvider,注意是先載入ContentProvider,然後呼叫Application的onCreate方法。
(5) ContentProvider啟動後, 外界就可以通過它所提供的增刪改查這四個介面來操作ContentProvider中的資料來源, 這四個方法都是通過Binder來呼叫的, 外界無法直接訪問ContentProvider, 它只能通過AMS根據URI來獲取到對應的ContentProvider的Binder介面IContentProvider, 然後再通過IContentProvider來訪問ContentProvider中的資料來源.

ContentProvider的android:multiprocess屬性決定它是否是單例項,預設值是false,也就是預設是單例項。當設定為true時,每個呼叫者的程式中都存在一個ContentProvider物件。

當呼叫ContentProvider的insert、delete、update、query方法中的任何一個時,如果ContentProvider所在的程式沒有啟動的話,那麼就會觸發ContentProvider的建立,並伴隨著ContentProvider所在程式的啟動。

以query呼叫為例

(1) 首先會獲取IContentProvider物件, 不管是通過acquireUnstableProvider()方法還是直接通過acquireProvider()方法, 他們的本質都是一樣的, 最終都是通過acquireProvider方法來獲取ContentProvider.
(2) ApplicationContentResolver#acquireProvider()方法並沒有處理任何邏輯, 它直接呼叫了ActivityThread#acquireProvider()
(3) 從ActivityThread中查詢是否已經存在了ContentProvider了, 如果存在那麼就直接返回. ActivityThread中通過mProviderMap來儲存已經啟動的ContentProvider物件, 這個集合的儲存型別ArrayMap mProviderMap. 如果目前ContentProvider沒有啟動, 那麼就傳送一個程式間請求給AMS讓其啟動專案目標ContentProvider, 最後再通過installProvider()方法來修改引用計數.
(4) AMS是如何啟動ContentProvider的呢?首先會啟動ContentProvider所在的程式, 然後再啟動ContentProvider. 啟動程式是由AMS#startProcessLocked()方法來完成, 其內部主要是通過Process#start()方法來完成一個新程式的啟動, 新程式啟動後其入口方法為ActivityThread#main()方法。
(5) ActivityThread#main()是一個靜態方法, 在它的內部首先會建立ActivityThread例項並呼叫attach()方法來進行一系列初始化, 接著就開始進行訊息迴圈. ActivityThread#attach()方法會將Application物件通過AMS#attachApplication方法跨程式傳遞給AMS, 最終AMS會完成ContentProvider的建立過程.
(6) AMS#attachApplication()方法呼叫了attachApplication(), 然後又呼叫了ApplicationThread#bindApplication(), 這個過程也屬於程式通訊.bindApplication()方法會傳送一個BIND_APPLICATION型別的訊息給mH, 這是一個Handler, 它收到訊息後會呼叫ActivityThread#handleBindApplication()方法.
(7) ActivityThread#handlerBindApplication()則完成了Application的建立以及ContentProvider 可以分為如下四個步驟:

  • 建立ContentProvider和Instrumentation
  • 建立Application物件
  • 啟動當前程式的ContentProvider並呼叫onCreate()方法. 主要內部實現是installContentProvider()完成了ContentProvider的啟動工作, 首先會遍歷當前程式的ProviderInfo的列表並一一呼叫installProvider()方法來啟動他們, 接著將已經啟動的ContentProvider釋出到AMS中, AMS會把他們儲存在ProviderMap中, 這樣一來外部呼叫者就可以直接從AMS中獲取到ContentProvider. installProvider()內部通過類載入器建立的ContentProvider例項並在方法中呼叫了attachInfo(), 在這內部呼叫了ContentProvider#onCreate()
  • 呼叫Application#onCreate()

經過了上述的四個步驟, ContentProvider已經啟動成功, 並且其所在的程式的Application也已經成功, 這意味著ContentProvider所在的程式已經完成了整個的啟動過程, 然後其他應用就可以通過AMS來訪問這個ContentProvider了.

當拿到了ContentProvider以後, 就可以通過它所提供的介面方法來訪問它. 這裡要注意: 這裡的ContentProvider並不是原始的ContentProvider. 而是ContentProvider的Binder型別物件IContentProvider, 而IContentProvider的具體實現是ContentProviderNative和ContentProvider.Transport. 後者繼承了前者.

如果還用query方法來解釋流程: 那麼最開始其他應用通過AMS獲取到ContentProvider的Binder物件就是IContentProvider. 而IContentProvider的實際實現者是ContentProvider.Transport. 因此實際上外部應用呼叫的時候本質上會以程式間通訊的方式呼叫ContentProvider.Transport的query()方法。

相關文章