Androidバックグラウンドタスク型Appマルチプロセスアーキテクチャの進化

5769 ワード

バックグラウンドタスク型appとは
音楽やレコーダーのように、ユーザーが長時間バックグラウンドで使用する製品
背景:
筆者の以前のプロジェクトはずっとランニングappをしていたが、ユーザーのシーンはこのようなもので、ユーザーがランニングモードを開いた後、Gps信号を傍受してユーザーの運動データを統計する必要がある.距離、配速、時間を含む.実は「簡単」に見えるユーザーシーンですが、最初は筆者もそう思っていましたが、しばらくの反復完備を経て、今からその中の「簡単ではない」を共有します.筆者はランニングapp開発者の角度からこのようなランニングアプリのアーキテクチャの進化を共有します.
最初のアーキテクチャ
筆者はできるだけ早く製品マネージャーのニーズを実現するために、appの最初の版を完成しました.このアーキテクチャはこのようなものです.
Activity+Forground Service+Sqlite+Eventbusのうち、ActivityはUI層、Serviceはランニングモードがオンのときに起動するforground serviceを表し、モーションデータを記録するために使用され、Sqliteはデータの記憶層を表し、eventbusはイベントバスのlibraryであり、モジュール間のデカップリングに使用される.
問題を引き起こす
最初の版が発行された後、一部のユーザーのフィードバックを受けて、運動データのマイルが失われ、記録が正確ではないという問題は、データ統計の運動appにとって致命的であるのに、なぜこのような問題があるのだろうか.appのプロセスが回収されたので簡単に推測できます
解決方法
主にUIプロセスとサービスプロセスの分離といくつかのサービスの保存の策略をして、主に2つの原因に基づいています
  • Androidプロセス管理メカニズムここではAndroidのプロセス管理メカニズムについて言及せざるを得ない.AndroidシステムはLow Memory Killerメカニズム(参考)によってプロセスを管理し、プロセスについていくつかの優先度に分けられる:
  • - native
    - persistent
    - forground
    - visible
    - cache
    

    各プロセスの優先度は、システム計算oom_に依存します.adjの値はoom_に影響しますadjの要因はどれらがありますか?主にプロセスがメモリを消費するサイズ
  • は、ランニングのようなappにとって、ユーザシーンが長い間バックグラウンドで実行されている状態であり、フロントUIはインタラクションのみを担当し、バックグラウンドのサービスは業務の処理を担当し、UIプロセスのメモリはSeviceのメモリ占有量よりはるかに大きいため、appがバックグラウンドに切り替えられたときにすべてのUIリソースを解放することができれば、このappの実行時に大量のメモリを節約することができる.

  • 第2版の修正
    以上の2つの理由に基づいて、第2版の再構築があり、アーキテクチャはこのようになりました.
    UIプロセス+Remoteプロセス(サービスプロセス)
    では、appが単一プロセスからマルチプロセスに変わるとどのような穴があるのでしょうか.筆者は主に3つの問題にぶつかった.
  • 1.プロセス間の通信方法
  • 2.2つのプロセスがデータ保証プロセスセキュリティにどのようにアクセスするか
  • 3.プロセスの安全を保証する方法sharepreference
  • 第1の問題に対して、マルチプロセス通信の方式:1.Broadcast:この方式のすべての通信プロトコルはintentの中で送信して受け入れる必要があります.非同期の通信方式です.つまり、呼び出し後すぐに結果を返すことができません.また、UIセグメントとサービスセグメントにreceiverを登録してこそ、相互通信が可能になります.
    2.Messager Messengerの使用方法は比較的簡単で、Messengerを定義してhandlerを通信のインタフェースとして指定し、onBindの時にMessengerのgetBinderメソッドを返し、UIで戻ったIBinderを利用してMessengerも作成し、彼らの間で通信することができます.この呼び出し方法も非同期呼び出しに属する.
    3.ResultReceiverはコンポーネント間の非同期通信であり、要求-コールバックモードによく用いられる.
    4.Binderというaidlを介した通信を書き換える最後のスキームを選択しました.メインプロセスはbindserviceを介してremoteプロセスを呼び出し、onServiceConnection時にremoteプロセスのcallbackコールバックを登録して、remoteプロセスのメッセージをリスニングし、受信します.
  • まずAndroidManifest.xmlで宣言
  • 
    
  • aidlインタフェース
  • を宣言する
    //aidl service        
    interface IRemoteService {
    void registerCallback(IRemoteCallback cb);
    void unregisterCallback(IRemoteCallback cb);
    }
    
    //    UI       
    interface IRemoteCallback {
    void onDataUpdate(double distance,double duration, double pace, double calorie, double velocity);
    }
    
  • RemoteService Binder
  • を書き換える
    LocalBinder mBinder = new LocalBinder();
    IRemoteCallback mCallback;
    class LocalBinder extends IRemoteService.Stub {    
      @Override    
      public void registerCallback(IRemoteCallback cb) throws  RemoteException {        
          mCallback = cb;    
      }    
      @Override    
      public void unregisterCallback(IRemoteCallback cb) throws   RemoteException {        
            mCallback = null;  
      }   
      public IBinder asBinder() {        
            return null;   
       }
    }
    
  • UIプロセスを書き換えるBinder
  • public class RemoteCallback extends IRemoteCallback.Stub {    
    @Override    
    public void onActivityUpdate(final double distance, final double duration, final double pace, final double calorie, final double velocity) throws RemoteException {        
      //do something
        }    
    }
    
  • onServiceConnectionの場合、UIプロセスのbinderがremoteプロセス
  • に登録されます.
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {   
     try {       
        mService = IRemoteService.Stub.asInterface(service);        
        mService.registerCallback(mCallback);    
    }  catch (RemoteException e) {      
      e.printStackTrace();   
     }
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {    
    try {     
         if (mService != null) { 
             mService.unregisterCallback(mCallback);     
         } 
     } catch (RemoteException e) {    
        e.printStackTrace();    
    }    
        mService = null;
    }
    

    2つ目の問題は、2つのプロセスがデータにアクセスして一貫性を保証する方法です.ContentProviderはSqliteの上位階層にContentProviderをカプセル化し、既存のアーキテクチャが次のようになりました.
    UI process: Activity + eventbus Remote process : Service + ContentProvider + Sqlite + Eventbus
    もう3つ目の問題は、ユーザーのニーズです.複数のプロセスは、ランニング中、ランニング一時停止かランニング終了かなど、ランニングの状態情報を取得する必要があります.プロセスの場合はSharePreferenceを使用して永続化された状態を格納し、プロセスを分割した後、MODE_の使用を開始します.MULTI_PROCESS、その後、ドキュメントの注釈が廃棄されていることに気づきました.multi_プロセスモードではsharepreferenceの動作は信頼できず、同期データは一致しません.以下に説明します.
    SharedPreference loading flag: when set, the file on disk will be checked for modification even if the shared preferences instance is already loaded in this process. This behavior is sometimes desired in cases where the application has multiple processes, all writing to the same SharedPreferences file. Generally there are better forms of communication between processes, though.
    では、どのように解決すればいいのでしょうか.2つのシナリオ
  • 1.ContentProvider+ Sqlite Tray(https://github.com/grandcentrix/tray/)
  • 2.ContentProvider + SharePreference(MODE_PRIVATE) DPreference(https://github.com/DozenWang/DPreference)

  • DPreference setString called 1000 times cost:375 ms getString called 1000 times cost:186 ms Tray setString called 1000 times cost:13699 ms getString called 1000 times cost:3496 ms
    シナリオ1には、古いSharePreferenceデータをsqlite方式ですべてコピーする必要がある場合、シナリオ2は天然にこのような問題を回避し、読み書き性能がより優れているため、シナリオ2を採用し、アーキテクチャがこのようになったという欠点があります.
    UI process: Activity + eventbus Remote process : Service + (ContentProvider + Sqlite)+ (ContentProvider + SharePreference) + Eventbus
    以上は筆者がマルチプロセス開発で出会ったいくつかの問題と解決策であり、皆さんの役に立つことを望んでいます.