天天看點

巧用Android多程序,微信,微網誌等主流App都在用

前言

對于程序的概念,來到這裡的都是程式設計修仙之人,就不再啰嗦了,相信大家倒着、跳着、躺着、各種姿勢都能背出來。

為什麼要使用多程序,一個程序不就可以了嗎?

相信很多同學在實際開發中,基本都不會去給app劃分程序,而且,在Android中使用多程序,還可能需要編寫額外的程序通訊代碼,還可能帶來額外的Bug,這無疑加大了開發的工作量,在很多創業公司中工期也不允許,這導緻了整個app都在一個程序中。

整個app都在一個程序有什麼弊端?

在Android中,虛拟機配置設定給各個程序的運作記憶體是有限制值的(這個值可以是32M,48M,64M等,根據機型而定),試想一下,如果在app中,增加了一個很常用的圖檔選擇子產品用于上傳圖檔或者頭像,加載大量Bitmap會使app的記憶體占用迅速增加,如果你還把檢視過的圖檔緩存在了記憶體中,那麼OOM的風險将會大大增加,如果此時還需要使用WebView加載一波網頁,我就問你怕不怕!

微信,微網誌等主流app是如何解決這些問題的?

微信移動開發團隊在 《Android記憶體優化雜談》 一文中就說到:“對于webview,圖庫等,由于存在記憶體系統洩露或者占用記憶體過多的問題,我們可以采用單獨的程序。微信目前也會把它們放在單獨的tools程序中”。

下面我們使用adb檢視一下微信和微網誌的程序資訊(Android 5.0以下版本可直接在“設定 -> 應用程式”相關條目中檢視):

巧用Android多程式,微信,微網誌等主流App都在用

進入adb shell後,使用 “ps | grep 條目名稱” 可以過濾出想要檢視的程序。

可以看到,微信的确有一個tools程序,而新浪微網誌也有image相關的程序,而且它們當中還有好些其它的程序,比如微信的push程序,微網誌的remote程序等,這裡可以看出,他們不單單隻是把上述的WebView、圖庫等放到單獨的程序,還有推送服務等也是運作在獨立的程序中的。一個消息推送服務,為了保證穩定性,可能需要和UI程序分離,分離後即使UI程序退出、Crash或者出現記憶體消耗過高等情況,仍不影響消息推送服務。

可見,合理使用多程序不僅僅是有多大好處的問題,我個人認為而且是很有必要的。

是以說,我們最好還是根據自身情況,考慮一下是否需要拆分程序。這也是本文的初衷:給大家提供一個多程序的參考思路,在遇到上述問題和場景的時候,可以考慮用多程序的方法來解決問題,又或者,在面試的時候,跟面試官聊到這方面的知識時候也不至于尴尬。

為什麼需要“跨程序通訊”?

Android的程序與程序之間通訊,有些不需要我們額外編寫通訊代碼,例如:把選擇圖檔子產品放到獨立的程序,我們仍可以使用startActivityForResult方法,将選中的圖檔放到Bundle中,使用Intent傳遞即可。(看到這裡,你還不打算把你項目的圖檔選擇弄到獨立程序麼?)

但是對于把“消息推送Service”放到獨立的程序,這個業務就稍微複雜點了,這個時候可能會發生Activity跟Service傳遞對象,調用Service方法等一系列複雜操作。

由于各個程序運作在相對獨立的記憶體空間,是以它們是不能直接通訊的,因為程式裡的變量、對象等初始化後都是具有記憶體位址的,舉個簡單的例子,讀取一個變量的值,本質是找到變量的記憶體位址,取出存放的值。不同的程序,運作在互相獨立的記憶體(其實就可以了解為兩個不同的應用程式),顯然不能直接得知對方變量、對象的記憶體位址,這樣的話也自然不能通路對方的變量,對象等。此時兩個程序進行互動,就需要使用跨程序通訊的方式去實作。簡單說,跨程序通訊就是一種讓程序與程序之間可以進行互動的技術。

跨程序通訊的方式有哪些?

  1. 四大元件間傳遞Bundle;
  2. 使用檔案共享方式,多程序讀寫一個相同的檔案,擷取檔案内容進行互動;
  3. 使用Messenger,一種輕量級的跨程序通訊方案,底層使用AIDL實作(實作比較簡單,部落客開始本文前也想了一下是否要說一下這個東西,最後還是覺得沒有這個必要,Google一下就能解決的問題,就不啰嗦了);
  4. 使用AIDL(Android Interface Definition Language),Android接口定義語言,用于定義跨程序通訊的接口;
  5. 使用ContentProvider,常用于多程序共享資料,比如系統的相冊,音樂等,我們也可以通過ContentProvider通路到;
  6. 使用Socket傳輸資料。

接下來本文将重點介紹使用AIDL進行多程序通訊,因為AIDL是Android提供給我們的标準跨程序通訊API,非常靈活且強大(貌似面試也經常會問到,但是真正用到的也不多…)。上面所說的Messenger也是使用AIDL實作的一種跨程序方式,Messenger顧名思義,就像是一種串行的消息機制,它是一種輕量級的IPC方案,可以在不同程序中傳遞Message對象,我們在Message中放入需要傳遞的資料即可輕松實作程序間通訊。但是當我們需要調用服務端方法,或者存在并發請求,那麼Messenger就不合适了。而四大元件傳遞Bundle,這個就不需要解釋了,把需要傳遞的資料,用Intent封裝起來傳遞即可,其它方式不在本文的讨論範圍。

下面開始對AIDL的講解,各位道友準備好渡劫了嗎?

使用AIDL實作一個多程序消息推送

像圖檔選擇這樣的多程序需求,可能并不需要我們額外編寫程序通訊的代碼,使用四大元件傳輸Bundle就行了,但是像推送服務這種需求,程序與程序之間需要高度的互動,此時就繞不過程序通訊這一步了。

下面我們就用即時聊天軟體為例,手動去實作一個多程序的推送例子,具體需求如下:

  1. UI和消息推送的Service分兩個程序;
  2. UI程序用于展示具體的消息資料,把使用者發送的消息,傳遞到消息Service,然後發送到遠端伺服器;
  3. Service負責收發消息,并和遠端伺服器保持長連接配接,UI程序可通過Service發送消息到遠端伺服器,Service收到遠端伺服器消息通知UI程序;
  4. 即使UI程序退出了,Service仍需要保持運作,收取伺服器消息。

實作思路

先來整理一下實作思路:

  1. 建立UI程序(下文統稱為用戶端);
  2. 建立消息Service(下文統稱為服務端);
  3. 把服務端配置到獨立的程序(AndroidManifest.xml中指定process标簽);
  4. 用戶端和服務端進行綁定(bindService);
  5. 讓用戶端和服務端具備互動的能力。(AIDL使用)

例子具體實作

為了閱讀友善,下文中代碼将省略非重點部分,可以把本文完整代碼Clone到本地再看文章:

https://github.com/V1sk/AIDL

Step0. AIDL調用流程概覽

開始之前,我們先來概括一下使用AIDL進行多程序調用的整個流程:

  1. 用戶端使用bindService方法綁定服務端;
  2. 服務端在onBind方法傳回Binder對象;
  3. 用戶端拿到服務端傳回的Binder對象進行跨程序方法調用;
    巧用Android多程式,微信,微網誌等主流App都在用

    AIDL調用過程

    整個AIDL調用過程概括起來就以上3個步驟,下文中我們使用上面描述的例子,來逐漸分解這些步驟,并講述其中的細節。

Step1.用戶端使用bindService方法綁定服務端

1.1 建立用戶端和服務端,把服務端配置到另外的程序
  1. 建立用戶端 -> MainActivity;
  2. 建立服務端 -> MessageService;
  3. 把服務端配置到另外的程序 -> android:process=”:remote”

上面描述的用戶端、服務端、以及把服務端配置到另外程序,展現在AndroidManifest.xml中,如下所示:

1
     
     
      2
     
     
      3
     
     
      4
     
     
      5
     
     
      6
     
     
      7
     
     
      8
     
     
      9
     
     
      10
     
     
      11
     
     
      12
     
     
      13
           
<manifest ...>
     
     
         
      <application ...>
     
             
      <activity android:name=".ui.MainActivity"/>
     
     
             
      <service
     
                 
      android:name=
      ".service.MessageService"
     
                 
      android:enabled=
      "true"
     
                 
      android:exported=
      "true"
     
                 
      android:process=
      ":remote" />
     
         
      </application>
     
     
     
      </manifest>
           

開啟多程序的方法很簡單,隻需要給四大元件指定android:process标簽。

1.2 綁定MessageService到MainActivity

建立MessageService

此時的MessageService就是剛建立的模樣,onBind中傳回了null,下一步中我們将傳回一個可操作的對象給用戶端。

1
     
     
      2
     
     
      3
     
     
      4
     
     
      5
     
     
      6
     
     
      7
     
     
      8
     
     
      9
     
     
      10
     
     
      11
           
public 
      class MessageService extends Service {
     
     
         
      public MessageService() {
     
     
          }
     
     
         
      @Override
     
         
      public IBinder onBind(Intent intent) {
     
             
      return 
      null;
     
     
          }
     
     
     
      }
           

用戶端MainActivity調用bindService方法綁定MessageService

這一步其實是屬于Service元件相關的知識,在這裡就比較簡單地說一下,啟動服務可以通過以下兩種方式:

  1. 使用bindService方法 -> bindService(Intent service, ServiceConnection conn, int flags);
  2. 使用startService方法 -> startService(Intent service);

bindService & startService差別:

使用bindService方式,多個Client可以同時bind一個Service,但是當所有Client unbind後,Service會退出,通常情況下,如果希望和Service互動,一般使用bindService方法,使用onServiceConnected中的IBinder對象可以和Service進行互動,不需要和Service互動的情況下,使用startService方法即可。

正如上面所說,我們是要和Service互動的,是以我們需要使用bindService方法,但是我們希望unbind後Service仍保持運作,這樣的情況下,可以同時調用bindService和startService(比如像本例子中的消息服務,退出UI程序,Service仍需要接收到消息),代碼如下:

1
     
     
      2
     
     
      3
     
     
      4
     
     
      5
     
     
      6
     
     
      7
     
     
      8
     
     
      9
     
     
      10
     
     
      11
     
     
      12
     
     
      13
     
     
      14
     
     
      15
     
     
      16
     
     
      17
     
     
      18
     
     
      19
     
     
      20
     
     
      21
     
     
      22
     
     
      23
     
     
      24
     
     
      25
     
     
      26
     
     
      27
     
     
      28
     
     
      29
     
     
      30
     
     
      31
     
     
      32
     
     
      33
     
     
      34
     
     
      35
     
     
      36
     
     
      37
     
     
      38
     
     
      39
     
     
      40
     
     
      41
     
     
      42
     
     
      43
           
public 
      class MainActivity extends AppCompatActivity {
     
     
         
      private 
      static 
      final String TAG = 
      "MainActivity";
     
     
         
      @Override
     
         
      protected void onCreate(Bundle savedInstanceState) {
     
             
      super.onCreate(savedInstanceState);
     
     
              setContentView(R.layout.activity_main);
     
     
              setupService();
     
     
          }
     
     
         
      /**
     
     
           * unbindService
     
     
           */
     
         
      @Override
     
         
      protected void onDestroy() {
     
     
              unbindService(serviceConnection);
     
             
      super.onDestroy();
     
     
          }
     
     
         
      /**
     
     
           * bindService & startService
     
     
           */
     
         
      private void setupService() {
     
     
              Intent intent = 
      new Intent(
      this, MessageService.class);
     
     
              bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
     
     
              startService(intent);
     
     
          }
     
     
     
          ServiceConnection serviceConnection = 
      new ServiceConnection() {
     
     
             
      @Override
     
             
      public void onServiceConnected(ComponentName name, IBinder service) {
     
     
                  Log.d(TAG, 
      "onServiceConnected");
     
     
              }
     
     
             
      @Override
     
             
      public void onServiceDisconnected(ComponentName name) {
     
     
                  Log.d(TAG, 
      "onServiceDisconnected");
     
     
              }
     
     
          };
     
     
     
      }
           

Stpe2.服務端在onBind方法傳回Binder對象

2.1 首先,什麼是Binder?

要說Binder,首先要說一下IBinder這個接口,IBinder是遠端對象的基礎接口,輕量級的遠端過程調用機制的核心部分,該接口描述了與遠端對象互動的抽象協定,而Binder實作了IBinder接口,簡單說,Binder就是Android SDK中内置的一個多程序通訊實作類,在使用的時候,我們不用也不要去實作IBinder,而是繼承Binder這個類即可實作多程序通訊。

2.2 其次,這個需要在onBind方法傳回的Binder對象從何而來?

在這裡就要引出本文中的主題了——AIDL

多程序中使用的Binder對象,一般通過我們定義好的 .adil 接口檔案自動生成,當然你可以走野路子,直接手動編寫這個跨程序通訊所需的Binder類,其本質無非就是一個繼承了Binder的類,鑒于野路子走起來麻煩,而且都是重複步驟的工作,Google提供了 AIDL 接口來幫我們自動生成Binder這條正路,下文中我們圍繞 AIDL 這條正路繼續展開讨論(可不能把人給帶偏了是吧

繼續閱讀