1. ホーム
  2. android

Androidプロセス生存のためのソリューション

2022-02-17 21:03:08

前置き

Androidは、システムを円滑に稼働させるため、メモリが不足すると一部のプロセスを強制終了し、メモリを解放します。しかし、メッセージをタイムリーに受け取りたい重要なアプリ(QQ、WeChatなど)については、プロセスをアクティブにしておく必要があり、そのためにはプロセスを生かすための対策、つまりAndroidプロセス生かしを実施する必要があります。

Androidのプロセス保存は、一般的に2つの方法で行うことができます。

  1. インランの保全。プロセスの優先度を上げ、システムによって強制終了される確率を下げます。

  2. Kill後にPull aliveする。システムによって強制終了されたプロセスを再び起動(再起動)します。

I. プロセス

デフォルトでは、同じAppのすべてのコンポーネントは同じプロセスで実行されますが、必要に応じてAndroidManifest.xmlマニフェストファイルで設定することで、特定のコンポーネントが属するプロセスを制御することもできます。Androidアプリは起動後にそれぞれ少なくとも1つのプロセスに対応し、アプリによっては複数のプロセスになっているものもあります。マルチプロセスになっているアプリもあり、基本的にはマルチプロセスになっているアプリが主流です。

1.1 プロセスの分類

しかし、新しいプロセスを作成したり、より重要なプロセスを実行するために、最終的には古いプロセスを削除し、メモリを確保する必要があります。どのプロセスを残すか、あるいは終了させるかを決定するために、システムは各プロセスを、そのプロセスで実行されているコンポーネントとそれらのコンポーネントの状態に基づいて、重要度階層(quot; importance hierarchy")に配置します。必要であれば、最も重要度の低いプロセスを最初に、次に重要度のやや低いプロセスを削除し、システム資源を再利用します。

重要度の階層は5段階に分かれています。以下のリストは、プロセスを重要度順に並べたものです ( 重要度の高いものから低いものへ ).

1. フォアグラウンドプロセス

以下の条件のいずれかを満たす場合、そのプロセスはフォアグラウンドプロセスとみなされます。

(1) ユーザーが操作しているアクティビティをホストしている(アクティビティのonResumeメソッドが呼び出されている)。

(2) ユーザーが操作しているActivityにバインドされているServiceをホストしている。

(3) フォアグラウンドで動作しているサービスをホストする (サービスはstartForegroundメソッドを呼び出した)

(4) ライフサイクル(onCreate, onStart, or onDestroy)をホストしているサービス。

(5) onReceiverメソッドの実行をホストしているBroadcastReceiver

通常、フォアグラウンド・プロセスは常時多数存在するわけではなく、一般にシステムはフォアグラウンド・プロセスを強制終了せず、実行し続けるのに十分なメモリがない場合にのみ強制終了します。

2. 可視化プロセス

フォアグラウンドコンポーネントを持たないが、ユーザーが画面上で見るものに影響を与えるプロセス。以下の条件のいずれかを満たす場合、プロセスは可視とみなされます。

(1) フォアグラウンドではないが、ユーザーから見える(onPauseメソッドが呼び出されている)Activityをホストしている。

(2) 可視(またはフォアグラウンド)アクティビティにバインドされているアクティビティをホストするサービス。

ユーザーが使用している、見ることはできるが触ることはできない、画面全体を覆っていない、画面の一部だけが見えるプロセスにはフォアグラウンドコンポーネントが含まれていない、リソースが不足した状況で1つ以上のフォアグラウンドプロセスを生かしておく必要がない限り一般にシステムは可視プロセスを停止しない、など。

3. サービスプロセス

以下の条件のいずれかを満たすプロセスをサービスプロセスと見なす。

(1) startServiceメソッドを使用して開始されたサービスを実行しており、上記の2つの上位カテゴリのプロセスのいずれでもないこと。

フォアグラウンドプロセスと可視プロセスを同時に実行するのに十分なメモリがない場合、サービスプロセスは強制終了されます。

4. バックグランドプロセス

以下の条件のいずれかを満たす場合、そのプロセスはバックグラウンドプロセスとみなされます。

(1) 現在ユーザーから見えないActivityを含むプロセス(ActivityのonStop()メソッドが呼び出されたことがある)。

システムは、メモリを再利用するためにいつでもそれらを終了させることができる

5. 空のプロセス

以下の条件のいずれかを満たす場合、プロセスは空であるとみなされます。

(1)アクティブなアプリケーションコンポーネントを一切含まないプロセス。

このようなプロセスは、その中のコンポーネントを次に実行するのに必要な起動時間を短縮するためのキャッシュとしてのみ保持されます。これらのプロセスは、プロセスキャッシュと基礎となるカーネルキャッシュの間でシステム全体のリソースのバランスを保つために、しばしば終了させられます。

1.2 メモリの閾値

経験とパフォーマンス上の理由から、システムはアプリがバックグラウンドに退いたときにプロセスを実際に殺すことはせず、キャッシュします。開いているアプリが多ければ多いほど、バックグラウンドでキャッシュされるプロセスも多くなります。システムメモリが不足している場合、システムは、ローメモリキラーと呼ばれる、必要なアプリのためにメモリを解放するために、独自のプロセスリサイクル機構のセットに基づいて、どのプロセスを殺すべきかを決定し始めます。メモリ閾値は /sys/module/lowmemorykiller/parameters/minfree ファイルに格納されており、閲覧には root 権限が必要なので、ここではメモリ閾値については触れないことにします。

これを読むと、今メモリ不足で空のプロセスのkillを開始しなければならない場合、空のプロセスが複数ある場合は一度にすべてkillすべきなのでしょうか?この優先順位は、プロセスの oom_adj 値に反映されます。これは、linux カーネルが各プロセスに割り当てる値で、プロセスの優先順位を表し、プロセスのリサイクル機構はこの優先順位に基づいてリサイクルをするかどうかを決定しているのです。oom_adjの値が小さいほどプロセスの優先順位が高く、通常のプロセスでは0より大きく、システムプロセスでは0より小さい。

現在のプロセスの oom_adj 値は、以下のように cat /proc/processid/oom_adj で確認することができます。

oom_adj の値が 0 であることを見て、0 はプロセスがフォアグラウンドであることを意味します。Back を押してアプリをバックグラウンドに切り替え、次のように oom_adj の値を再度確認します。

oom_adjの値が何であるかは、携帯電話メーカーごとに異なる場合があり、正確な意味は解析されません oom_adj が大きいほど、プロセスの優先順位が低くなり、システムによって強制終了される可能性が高くなることだけは知っておいてください。

II. プロセス保存スキーム

2.1 1画素でアクティビティを開く

基本的な考え方は、システムは一般的にフォアグラウンドプロセスを殺すことはありませんので、プロセスを常駐させるために、我々はちょうどこのプロセスでActivityを開く必要があるときにロック画面は、ユーザーを欺くために、我々はこの活動のサイズは1ピクセルであり、アニメーションを切り替えずに透明にして、オープン画面では、このアクティビティを閉じ、これはシステムを聞く必要がありますので、これはシステムのロック画面の放送を聞く必要がある。

1ピクセルのAliveActivityを作成し、画面オフ時にAliveActivityを起動し、画面オン時に閉じるので、MainActivityにAliveActivityの起動または終了を通知するシステムロックブロードキャストをコールバックとしてリッスンします。

public interface ScreenStateCallback {
    /**
     * open screen
     */
    void onScreenOn();

    /**
     * Lock the screen
     */
    void onScreenOff();
}
public class ScreenStateManager {
    private Context mContext;
    private WeakReference<AliveActivity> mAliveActivityWrf;
    private static volatile ScreenStateManager instance;
    private ScreenStateReceiver mReceiver;
    private ScreenStateCallback mCallback;

    private ScreenStateManager(Context context) {
        this.mContext = context;
        mReceiver = new ScreenStateReceiver();
    }

    /**
     * Get the ScreenStateManager object
     *
     * @param context
     * @return
     */
    public static ScreenStateManager getInstance(Context context) {
        if (instance == null) {
            synchronized (ScreenStateManager.class) {
                if (instance == null) {
                    instance = new ScreenStateManager(context.getApplicationContext());
                }
            }
        }
        return instance;
    }

    /**
     * Set up a listener
     *
     * @param callback
     */
    public void setScreenStateCallback(ScreenStateCallback callback) {
        this.mCallback = callback;
    }


    /**
     * Register for screen status broadcast
     */
    public void registerScreenStateBroadcastReceiver() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        mContext.registerReceiver(mReceiver, filter);
    }

    /**
     * Unregister the broadcast
     */
    public void unregisterScreenStateBroadcastReceiver() {
        mContext.unregisterReceiver(mReceiver);
    }


    /**
     * Set up AliveActivity
     *
     * @param aliveActivity
     */
    public void setAliveActivity(AliveActivity aliveActivity) {
        this.mAliveActivityWrf = new WeakReference<>(aliveActivity);
    }

    /**
     * Open AliveActivity
     */
    public void openAliveActivity() {
        AliveActivity.openAliveActivity(mContext);
    }


    /**
     * Close AliveActivity
     */
    public void closeAliveActivity() {
        if (mAliveActivityWrf ! = null) {
            AliveActivity aliveActivity = mAliveActivityWrf.get();
            if (aliveActivity ! = null) {
                aliveActivity.finish();
            }
        }
    }

    /**
     * Screen status broadcast
     */
    private class ScreenStateReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
                // open the screen
                if (mCallback ! = null) {
                    mCallback.onScreenOn();
                }
            } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
                // Lock the screen
                if (mCallback ! = null) {
                    mCallback.onScreenOff();
                }
            }
        }
    }

public class AliveActivity extends Activity {
  private static final String TAG = AliveActivity.class.getSimpleName();

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_alive);

      Log.d(TAG, "AliveActivity onCreate");

      // set to 1 pixel
      Window window = getWindow();
      // put it in the top left corner
      window.setGravity(Gravity.START | Gravity.TOP);
      WindowManager.LayoutParams params = window.getAttributes();
      // set the width and height to 1 pixel
      params.width = 1;
      params.height = 1;
      // Start coordinates
      params.x = 0;
      params.y = 0;
      window.setAttributes(params);
       
      ScreenStateManager.getInstance(this).setAliveActivity(this);

  }

  /**
    * Open AliveActivity
    *
    * @param context
    */
  public static void openAliveActivity(Context context) {
      Intent intent = new Intent(context, AliveActivity.class);
      intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
      context.startActivity(intent);
  }

  @Override
  protected void onDestroy() {
      super.onDestroy();
      Log.d(TAG, "AliveActivity onDestroy");
  }
}

public class MainActivity extends AppCompatActivity {
    private ScreenStateManager manager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        manager = ScreenStateManager.getInstance(this);
        // Register the broadcast
        manager.registerScreenStateBroadcastReceiver();
        manager.setScreenStateCallback(new ScreenStateCallback() {
            @Override
            public void onScreenOn() {
                manager.closeAliveActivity();
            }

            @Override
            public void onScreenOff() {
                manager.openAliveActivity();
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (manager ! = null) {
            manager.unregisterScreenStateBroadcastReceiver();
        }
    }
}

public class AliveService extends Service {
    public static final int NOTIFICATION_ID = 0x001;
    private static final String NOTIFICATION_CHANNEL_ID = "active_notification_id";

    @Override
    public void onCreate() {
        super.onCreate();
        // Below API 18, send Notification directly and set it to the foreground
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
            startForeground(NOTIFICATION_ID, new Notification());
        } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            //API 18+, after sending Notification and setting it to the foreground, start InnerService
            Notification.Builder builder = new Notification;
            builder.setSmallIcon(R.mipmap.ic_launcher);
            startForeground(NOTIFICATION_ID, builder.build());
            startService(new Intent(this, InnerService.class));
        } 
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return null;
    return null; }

    @Override
    public void onDestroy() {
        super.onDestroy();
        stopForeground(true);
    }

    public static class InnerService extends Service {
        @Override
        public void onCreate() {
            super.onCreate();
                // Send Notification with the same ID as in ALiveService, then cancel it and remove itself from the foreground
                Notification.Builder builder = new Notification;
                builder.setSmallIcon(R.mipmap.ic_launcher);
                builder.setSmallIcon(R.mipmap.ic_launcher); startForeground(NOTIFICATION_ID, builder.build());

                // 100ms destroy
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        stopForeground(true);
                        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                        manager.cancel(NOTIFICATION_ID);
                        stopSelf();
                    }
                }, 100);
            } else {
                // send Notification with the same ID as in ALiveService, then cancel it and cancel itself from the foreground
                Notification notification = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .build();
                notification.flags |= Notification.FLAG_NO_CLEAR;
                startForeground(NOTIFICATION_ID, notification);

                // 100ms destroy
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        stopForeground(true);
                        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                        manager.cancel(NOTIFICATION_ID);
                        stopSelf();
                    }
                }, 100);
            }
        }

        @Nullable
        @Override
        public IB


Before the foreground service is taken, the application is started and the oom_adj value is 0. After the return key is pressed, it becomes 8 (which may vary from ROM to ROM), as shown in the following figure.

MainActivityを以下のように変更します。

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class AliveJobService extends JobService {
    private static final String TAG = AliveJobService.class.getSimpleName();

    public static void startJobScheduler(Context context) {
        try {
            JobScheduler jobScheduler = (JobScheduler) context.getSystemService(JOB_SCHEDULER_SERVICE);
            JobInfo.Builder builder = new JobInfo,
                    new ComponentName(context.getPackageName(), AliveJobService.class.getName()));

            // Set the device to execute even after reboot
            builder.setPersisted(true);

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                //7.0+ delay 1s execution
                builder.setMinimumLatency(1000);
            } else {
                //execute jobs every 1s
                builder.setPeriodic(1000);
            }

            jobScheduler.schedule(builder.build());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean onStartJob(JobParameters params) {
        Log.e(TAG, "Start job");
        // 7.0 and above turn on polling
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            startJobScheduler(this);
        }
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }

}

実行後、最初にホームボタンを押し、次にロック画面を押すと、以下のようになります。

上記の結果からわかるように、AliveActivityはすでにロック画面で起動されており、プロセスの優先度も上がっています。

その後、再度画面を開くと、以下のような結果になります。

画面を開くと、AliveActivityも終了しています。

デメリット AliveActivityの存在は十分にクリーンではないので、画面がロックされたときに耳を傾ける必要があります。ユーザーが画面に留まる場合、oom_adjの値は小さくならず、システムがメモリ不足になった場合にもkillされる。

2.2 フォアグラウンドサービス

原則は、startForegroundメソッドを呼び出してサービスの優先順位を確認することで、サービスをフォアグラウンドにすることです。

APIレベル18の場合:startForeground(ID,new Notification())を呼び出すと、空のNotificationが送信され、アイコンが表示されなくなります。

APIレベル>=18およびAPIレベル<26の場合。

startForeground(ID,new Notification())を呼び出すと、アイコンは表示されますが、空のNotificationが送信されます。

そこで、優先すべきサービスAでInnerServiceを起動し、両方のサービスを同時にstartForegroundし、同じIDをバインドします。InnerServiceを停止して、通知バーのアイコンを削除しますが、この方法はAPI level >=26 以上では動作せず、InnerServiceを停止しても、通知バーは表示されます。

コードは次のようになります。

<service
    android:name=".service.AliveJobService"
    android:enabled="true"
    android:exported="true"
    android:permission="android.permission.BIND_JOB_SERVICE" />

AliveJobService.startJobScheduler(this);

フォアグラウンドサービスを取ってアプリを起動した後、oom_adjの値が0になり、リターンキーを押した後に1になり(ROMによって違うかもしれませんが)、確かにプロセスの優先順位が上がりました。

III. ライブを引っ張るプロセス

3.1 相互ウェイクアップ

Alipay、Taobao、Tmall、UC、その他のAliasアプリをスマートフォンに入れている場合、どのAliasアプリを開いても、他のAliasアプリを起動させることができるのです。これは完全に可能なことです。

アプリが生き残りたいなら、QQやWeChatが救ってくれるならいいのですが、QQやWeChatがないスマホがどれだけあるでしょうか?あるいは、au、Xiaomi、Huawei、SymbianなどのプッシュSDKがあり、アプリを起こす機能がある。

3.2 ブロードキャスト・プル

システムブロードキャストを受信することで、プロセスを生存させることができますが、Androidは7.0以降ブロードキャストにいくつかの制限を加え、8.0以降さらに厳しくなったため、システムブロードキャストの受信は基本的に使用できなくなりました。

3.3 ジョブスケジューラ

JobScheduler は、アプリが特定の条件に達したときに実行するように指定できる、システムの定時タスクで、アプリが強制終了された場合でも、以前にスケジュールされたタスクは実行されます。通常のタイマーとは異なり、スケジューリングはシステムによって行われるため、プロセスを継続させるために使用することができます。

JobScheduleの動作には、android 5.0以上が必要です。

JobSchedulerは、プロセスが死んだ後に復活させるための手段です。ネイティブプロセス方式の最大の欠点は、電力を消費することです。ネイティブプロセスが電力を消費する理由は、メインプロセスが生きているかどうかを感知する方法が2つあるためです。次に、5.0以上のシステムでサポートされていないことです。しかし、JobScheduler は、Android 5.0 以上でネイティブ プロセス アプローチの代替となり、ユーザーが強制的にシャットダウンしてもプルアップすることが可能です。

// IAliveAidlInterface.aidl
package com.lx.keep.alive;

// Declare any non-default types here with import statements

interface IAliveAidlInterface {
    /*
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

AndroidManifest.xmlでサービスを設定します。

public class LocalAliveService extends Service {
    private static final String TAG = LocalAliveService.class.getSimpleName();
    private ServiceConnection mConnection;
    private LocalAliveBinder mBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        mBinder = new LocalAliveBinder(). mConnection = new LocalAliveBinder();
        mConnection = new LocalAliveServiceConnect();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //bind the local daemon service
        bindService(new Intent(this, RemoteAliveService.class), mConnection, BIND_AUTO_CREATE);
        return super.onStartCommand(intent, flags, startId);
    }

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

    class LocalAliveServiceConnect implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // Callback after service connection
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "Remote child process may have been taken out, pulling live");
            // Callback after the connection is broken, then start the Service where the child process is located, and bind, forcing the remote service to pull live by starting the child process
            startService(new Intent(LocalAliveService.this, RemoteAliveService.class));
            bindService(new Intent(LocalAliveService.this, RemoteAliveService.class), mConnection,
                    BIND_AUTO_CREATE);
        }
    }

    class LocalAliveBinder extends IAliveAidlInterface.Stub {

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }
    }
}


MainActivityのonCreateメソッドで呼び出されます。

public class RemoteAliveService extends Service {
    private static final String TAG = RemoteAliveService.class.getSimpleName();
    private ServiceConnection mConnection;
    private RemoteAliveBinder mBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        mBinder = new RemoteAliveBinder(). mConnection = new RemoteAliveBinder();
        mConnection = new RemoteAliveServiceConnect();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //bind the local daemon service, must implement AIDL otherwise bindService has no role here
        bindService(new Intent(this, LocalAliveService.class), mConnection, BIND_AUTO_CREATE);
        return super.onStartCommand(intent, flags, startId);
    }

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

    class RemoteAliveServiceConnect implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // Callback after service connection
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "The local master process may be taken out, pull live");
            // Callback after the connection is broken, then start the Service where the main process is located, and bind, forcing the local service to pull live by starting the main process
            startService(new Intent(RemoteAliveService.this, LocalAliveService.class));
            bindService(new Intent(RemoteAliveService.this, LocalAliveService.class), mConnection,
                    BIND_AUTO_CREATE);
        }
    }

    class RemoteAliveBinder extends IAliveAidlInterface.Stub {

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }
    }
}

実行後、killすると、最後にJobscheduleはまだ実行されており、プロセスは以下の結果で再スタートします。

3.4 デュアルプロセスガード

デュアルプロセスガードは、基本的にマスタープロセス(ローカルサービスを含む)とチャイルドプロセス(リモートサービスを含む)の2つのプロセスを開き、そのうちの1つが殺されると、もう1つのプロセスが自動的に殺されたものを生き返らせるというものです。その模式図を以下に示す。

(1) AIDLファイルの実装

これはライブで引っ張るためだけのもので、特定の機能のリモートコールを必要としないため、仕様がなくても実装可能ですが、欠かすことはできません、作成手順は以下の通りです。

そのコードを以下に示す。

<service
    android:name=".service.RemoteAliveService"
    android:enabled="true"
    android:exported="true"
    android:process=":remote"></service>
<service
    android:name=".service.LocalAliveService"
    android:enabled="true"
    android:exported="true"/>

(2)マスタープロセスに対するローカルサービスの実装

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            //dual process Service daemon
            startService(new Intent(this, LocalAliveService.class));//start main thread daemon service
            startService(new Intent(this, RemoteAliveService.class));//start the main thread daemon service
        }
    }
}

(3) 子プロセスのリモートサービスの実装

public class AuthenticationService extends Service {
    private AccountAuthenticator accountAuthenticator;


    @Override
    public void onCreate() {
        super.onCreate();
        accountAuthenticator = new AccountAuthenticator(this);
    }

    @Override
    public IBinder onBind(Intent intent) {
        // return the Binder that operates on the data
        return accountAuthenticator.getIBinder();
    }

    /**
     * Account operation class
     */
    static class AccountAuthenticator extends AbstractAccountAuthenticator {

        public AccountAuthenticator(Context context) {
            super(context);
        }

        @Override
        public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
            return null;
        }

        @Override
        public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
            return null;
        }

        @Override
        public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
            return null;
        }

        @Override
        public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
            return null;
        }

        @Override
        public String getAuthTokenLabel(String authTokenType) {
            return null;
        }

        @Override
        public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
            return null;
        }

        @Override
        public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
            return null;
        }
    }
}


(4) AndroidManifest.xml は、本サービスを以下のように設定します。

<service
    android:name=".service.AuthenticationService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.accounts.AccountAuthenticator"/>
    </intent-filter>
    <meta-data
        android:name="android.accounts.AccountAuthenticator"
        android:resource="@xml/authenticator"/>
</service>

(5) デュアルプロセスガードをONにする

<?xml version="1.0" encoding="utf-8"? >
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="com.lx.keep.alive.account"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name" />

<! --accountType indicates the account type and must be unique -->

注意:Android O以降、アプリがバックグラウンドに入ると、しばらくしてアイドル状態になるので、startServiceでサービスを開始しないと、以下のようなエラーが報告されるようになりました。

Android O が startForegroundService() でフォアグラウンドサービスを開始し、システムがサービスを作成した後、一定時間内に startForeground( ) を呼び出してサービスをユーザーに見えるようにする必要があります。そうしないと、システムはサービスを停止して ANR をスローしますが、上記の説明のように通知バーに警告ボックスが表示されることになります。

つまり、互換性スキームは状況に応じて選択するものなのです。

実行結果は以下の通りです。

3.5 アカウント同期のプルライブ

サードパーティーのアプリであれば、このアカウントに我々のアプリを登録し、一定時間内にサーバーにデータを同期させることができます。 システムがAppアカウントを同期すると、自動的にそのために開かれたAppプロセスを生きたまま引き込みます .

(1) アカウントサービスをオンにする

AuthenticationServiceはServiceを継承しており、本来は他のプロセスが利用するためのAIDLであり、これを実装して宣言すると、アンドロイドシステムが 主なものは、一度実装して宣言すると、アンドロイドシステムはそれを Android.accounts.AccountAuthenticator AccountAuthenticatorはAbstractAccountAuthenticatorを継承したクラスで、AbstractAccountAuthenticatorは携帯電話のシステム設定にある"Account and Authenticator"機能を実装するために使われます。このアクションはアカウントをシステム設定のインターフェースに登録するために使われます。電話のシステム設定の "アカウントと同期" セクションでは、以下のようになります。 アカウントは、いくつかの基本的な機能の追加、削除、および認証に使用される AbstractAccountAuthenticatorは、追加、削除、認証などの基本的な機能を実装するために使用されます。 アカウント 電話システム設定の "Account and Sync" セクションにあります。当然ながら、AbstractAccountAuthenticator には IAccountAuthenticator.Stub を継承した内部クラスがあり、AbstractAccountAuthenticator のリモートインターフェース呼び出しをラップしています。そのため、AbstractAccountAuthenticatorのgetIBinder()メソッドを使用すると、内部クラスのIBinderフォームを返すことができます。


public class AccountHelper {
    // Consistent with accountType in authenticator.xml
    private static final String ACCOUNT_TYPE = "com.lx.keep.alive.account";

    /**
     * Adding an account requires the "android.permission.GET_ACCOUNTS" permission
     * @param context
     */
    public static void addAccount(Context context){
        AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
        Account[] accounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
        if(accounts.length >0 ){
            // The account already exists
            return;
        }

        Account account = new Account("test",ACCOUNT_TYPE);
        // Add an account
        accountManager.addAccountExplicitly(account,"123456",new Bundle());
    }
}

AndroidManifest.xmlでサービスを設定します。


public class SyncAccountService extends Service {
    private static final String TAG = SyncAccountService.class.getSimpleName();
    private SyncAdapter syncAdapter;

    @Override
    public void onCreate() {
        super.onCreate();
        syncAdapter = new SyncAdapter(this, true);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return syncAdapter.getSyncAdapterBinder();
    }

    static class SyncAdapter extends AbstractThreadedSyncAdapter {

        public SyncAdapter(Context context, boolean autoInitialize) {
            super(context, autoInitialize);
        }

        @Override
        public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
            Log.d(TAG, "Account synced");
        }
    }
}

xmlにauthenticator.xmlを追加します。

icon, label はそれぞれ Account list でのアイコンと表示名です。 そして accountTypeは、ユーザーに対する操作に必要なパラメータの1つです。 .


<service
    android:name=".service.SyncAccountService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.content.SyncAdapter" />
    </intent-filter>
    <meta-data
        android:name="android.content.SyncAdapter"
        android:resource="@xml/sync_account_adapter" />
</service>

上記の手順の後、Apkをインストールし、設定のアカウント -> アカウントの追加を再度開くと、元のアカウントリストに1行分のデータが追加されていることが確認できますので、当アプリがこのアカウントシステムにも対応したことが分かります。

(2) アカウントの追加


<?xml version="1.0" encoding="utf-8"? >
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="com.lx.keep.active.account"
    android:allowParallelSyncs="true"
    android:contentAuthority="com.lx.keep.alive.provider"
    android:isAlwaysSyncable="true"
    android:supportsUploading="false"
    android:userVisible="false" />

<! --contentAuthority The system will look for this auth's ContentProvider when syncing accounts -- >
<! --accountType indicates the account type, which should be the same as in authenticator.xml -->
<! --userVisible whether to show in "settings" -->
<! -- supportsUploading Whether notifyChange notifications are required to synchronize -- >
<! -- allowParallelSyncs Allow multiple accounts to sync at the same time -->
<! --isAlwaysSyncable Set isSyncable to 1 for all accounts -->

を呼び出す。 addAccount このメソッドを実行すると、システム設定の「アカウント」画面にアカウントが追加されます。これは、次の画像のようになります。

(3) シンクロナイゼーションサービス

同期ServiceとしてServiceを作成し、AbstractThreadedSyncAdapter onBindのgetSyncAdapterBinderを返します。


public class AccountProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { return null; } @Override public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
}

AndroidManifest.xmlでサービスを設定します。


<provider
    android:name=".helper.AccountProvider"
    android:authorities="com.lx.keep.active.provider"
    android:exported="false" />

xmlにsync_account_adapter.xmlを追加します。


AccountHelper.addAccount(this);
AccountHelper.autoSyncAccount();

(4) ContentProviderの作成


public class AccountProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { return null; } @Override public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
}

AndroidManifest.xmlにContentProviderを設定します。


<provider
    android:name=".helper.AccountProvider"
    android:authorities="com.lx.keep.active.provider"
    android:exported="false" />


(5) 同期をオンにする

プロセスを存続させるために、自動同期をオンにすることができます。時間間隔は1秒に設定されていますが、Android自体は同期の消費を考慮して1秒を基準時間としており、端末の起動回数を減らすことができます


public class AccountHelper {
    // authenticator.xmlのaccountTypeと一致する。
    private static final String ACCOUNT_TYPE = "com.lx.keep.alive.account".ACCOUNT_TYPE = "com.lx.keep.alive.account";
    private static final String CONTENT_AUTHORITY = "com.lx.keep.active.provider";

    // ...

    /**
     * アカウントの同期を設定する、つまり、システムが私たちのためにアカウントの同期を行う必要があることを伝える、セットアップ後にのみ、システムは自動的に行くでしょう。
     * SyncAccountAdapter#onPerformSync メソッドを起動します。
     */
    public static void autoSyncAccount(){ (パブリックスタティックボイドオートシンクアカウント)。
        Account account = new Account("test",ACCOUNT_TYPE);
        // 同期を設定する
        ContentResolver.setIsSyncable(account,CONTENT_AUTHORITY,1);
        // 自動同期を設定する
        ContentResolver.setSyncAutomatically(account,CONTENT_AUTHORITY,true);
        // 同期期間を設定する
        ContentResolver.addPeriodicSync(account,CONTENT_AUTHORITY,new Bundle(),1)を実行。
    }

}


最後に、MainActivityのonCreateで、以下のように呼び出します。


AccountHelper.addAccount(this);
AccountHelper.autoSyncAccount();

テストでは、Appをスマホに実行した後、プロセスをkillすると、アカウントの同期が始まると、再びプロセスが開始され、以下のような結果になります。

上記は、ライブを引っ張るためにアカウント同期を使用する主な核となる考え方です。テスト中に、システムによって挙動が異なること、同期期間が完全にシステムによって制御されていること、より安定しているが期間が制御できないことがわかりました。

下のQRコードを読み取ると、技術的なヒントを得るための公開ページが表示されます。