Androidプロセス生存のためのソリューション
前置き
Androidは、システムを円滑に稼働させるため、メモリが不足すると一部のプロセスを強制終了し、メモリを解放します。しかし、メッセージをタイムリーに受け取りたい重要なアプリ(QQ、WeChatなど)については、プロセスをアクティブにしておく必要があり、そのためにはプロセスを生かすための対策、つまりAndroidプロセス生かしを実施する必要があります。
Androidのプロセス保存は、一般的に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コードを読み取ると、技術的なヒントを得るための公開ページが表示されます。
関連
-
Android: インポートモジュールエラー Android リソースのリンクに失敗しました
-
最新のandroidプロジェクトディレクトリにあるarmeabi-v7aとarmeabiの具体的な意味とその違いを教えてください。
-
エラーが発生しました。ArrayAdapter は、リソース ID が TextView である必要があります。
-
アンドロイドスタジオでJunitのエラー問題を解決する
-
Android studioのインストールと問題発生、Emulator: PANIC: AVDのシステムパスが見つかりません。
-
android studioが "The activity must be exported or contain an intent-filter" と表示され実行される。
-
例外「指定された子にはすでに親がいます」の解決方法。removeViewを呼び出す必要があります" の解決方法(ソースコード付き例)
-
Android ProgressBarの色を変更する
-
Android Studio常见错误之:Rendering Problems/The following classes could not be instantiated
-
Android Studio http://schemas.android.com/apk/res/android 「URIが登録されていません」の解決方法について
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
java.lang.NullPointerException: NULLオブジェクト参照で仮想メソッド......を呼び出そうとしました。
-
AndroidエミュレーターのADBサーバーがACKしない問題
-
デフォルトのアクティビティが見つからない場合の対処法
-
ジャークとして。起動アクティビティを特定できませんでした。デフォルトのアクティビティが見つかりません アクティビティ起動中のエラー
-
GIF、Lottie、SVGA
-
ActivityはOnFragmentInteractionListenerを実装しなければならないに関する質問
-
指定された子にはすでに親がいます。まず、その子の親に対して removeView() をコールする必要があります。
-
アンドロイドのエリプサイズを使用する
-
WeChatとQQは、他のアプリのオープンリストに自分のアプリを追加し、ファイルパスを取得することができます
-
CursorIndexOutOfBoundsException:インデックス -1 が要求されました。