Android 进程保活方案

前言

Android 系统为了保持系统运行流畅,在内存吃紧的情况下,会将一些进程给杀掉,以释放一部分内存。然而,对于一些(如:QQ、微信等)比较重要的、我们希望能及时收到消息的App,需要保持进程持续活跃,那么就需要实施一些保活措施来保证进程能够持续存活,即 Android 进程保活。

Android 进程保活一般可以从两个方面进行:

  1. 运行中保活:提高进程优先级,降低被系统杀掉的概率。

  2. 杀掉后拉活:被系统杀掉之后,将进程再拉活(重启)。

一、进程

默认情况下,同一个 App 的所有组件均运行在相同的进程中,但是也可以根据需要,通过在 AndroidManifest.xml 清单文件中配置来控制某些组件所属的进程,因此,每一个 Android 应用启动之后至少对应一个进程,有的应用是多个进程,目前主流的应用基本上都是多个进程。

1.1 进程分类

Android 系统将尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,最终需要移除旧进程来回收内存。 为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。 必要时,系统会首先消除重要性最低的进程,然后是重要性略低的进程,依此类推,以回收系统资源。

重要性层次结构一共有 5 级。以下列表按照重要程度列出了各类进程(重要性从高到低):

1. 前台进程

如果一个进程满足以下任一条件,即视为前台进程:

(1) 托管用户正在交互的 Activity (已调用 Activity 的 onResume 方法)

(2) 托管某个 Service , 并且与用户正在交互的 Activity 绑定。

(3) 托管正在前台运行的 Service(服务已经调用 startForeground方法)

(4) 托管正在执行一个生命周期的 Service ( onCreate 、onStart 或 onDestroy)

(5) 托管正在执行其 onReceiver 方法的 BroadcastReceiver

通常在任意给定的时间前台进程都不是很多,一般系统是不会杀死前台进程的,只有在内存不足以支持它们继续运行的情况下系统才会杀死它们。

2. 可见进程

没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 如果一个进程满足以下任一条件,即视为可见进程:

(1) 托管不在前台,但对用户可见的Activity(已调用 onPause 方法)。

(2) 托管绑定到可见(或前台)Activity 的 Service 。

用户正在使用,看得到,但是摸不着,没有覆盖到整个屏幕,只有屏幕的一部分可见进程不包含任何前台组件,一般系统也是不会杀死可见进程的,除非要在资源吃紧的情况下,要保持某个或多个前台进程存活。

3. 服务进程

如果一个进程满足以下任一条件,即视为服务进程:

(1) 在运行已使用 startService 方法启动的服务且不属于上述两个更高类别进程的进程

在内存不足以维持所有前台进程和可见进程同时运行的情况下,服务进程会被杀死

4. 后台进程

如果一个进程满足以下任一条件,即视为后台进程:

(1) 包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。

系统可能随时终止它们,回收内存

5. 空进程

如果一个进程满足以下任一条件,即视为空进程:

(1) 不含任何活动应用组件的进程 。

保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。

1.2 内存阈值

系统出于体验和性能上的考虑,app 在退到后台时系统并不会真正的 kill 掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要 kill 掉哪些进程,以腾出内存来供给需要的 app 使用, 这套杀进程回收内存的机制就叫 Low Memory Killer。那这个不足怎么来规定呢,那就是内存阈值,当内存小于内存阈值就开始杀进程,内存阈值是存放在 /sys/module/lowmemorykiller/parameters/minfree 文件中的,这个需要root权限才能查看,因此这里就不继续研究内存阈值了。

读到这里,如果现在内存不足了,要开始杀空进程了,如果空进程不止一个,难道要一次性将空进程全部杀掉吗?当然不是的,进程是有它的优先级的,这个优先级通过进程的 oom_adj 值来反映,它是 linux 内核分配给每个进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收,oom_adj 的值越小,进程的优先级越高,普通进程 oom_adj 值是大于等于 0 的,而系统进程 oom_adj 的值是小于0的。

我们可以通过 cat /proc/进程id/oom_adj 可以看到当前进程的 oom_adj 值,如下图所示:

看到 oom_adj 值为 0, 0 就是表这个进程属于前台进程,我们按下 Back 键,将应用切换到后台,再次查看 oom_adj 值,如下所示:

oom_adj 值是多少,每个手机的厂商可能都不一样,具体含义也就不做分析了,只用知道 oom_adj 越大,进程的优先级就越低,就越容易被系统杀掉

二、进程保活方案

2.1 开启一个像素的Activity

基本思想,系统一般不会杀死前台进程,所以要使得进程常驻,我们只需要在锁屏的时候在本进程中开启一个 Activity ,为了欺骗用户,我们让这个 Activity 的大小是 1 像素,并且透明无切换动画,在开屏幕的时候,把这个 Activity 关闭掉,所以这个就需要监听系统锁屏广播。

创建一个一像素的 AliveActivity ,在屏幕关闭的时候把 AliveActivity 启动起来,在开屏的时候把 AliveActivity 关闭掉,所以要监听系统锁屏广播,以回调的形式通知 MainActivity 启动或者关闭 AliveActivity。

public interface ScreenStateCallback {
    /**
     * 开屏
     */
    void onScreenOn();
​
    /**
     * 锁屏
     */
    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();
    }
​
    /**
     * 获取ScreenStateManager对象
     *
     * @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;
    }
​
    /**
     * 设置监听
     *
     * @param callback
     */
    public void setScreenStateCallback(ScreenStateCallback callback) {
        this.mCallback = callback;
    }
​
​
    /**
     * 注册屏幕状态广播
     */
    public void registerScreenStateBroadcastReceiver() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        mContext.registerReceiver(mReceiver, filter);
    }
​
    /**
     * 解注册广播
     */
    public void unregisterScreenStateBroadcastReceiver() {
        mContext.unregisterReceiver(mReceiver);
    }
​
​
    /**
     * 设置AliveActivity
     *
     * @param aliveActivity
     */
    public void setAliveActivity(AliveActivity aliveActivity) {
        this.mAliveActivityWrf = new WeakReference<>(aliveActivity);
    }
​
    /**
     * 开启AliveActivity
     */
    public void openAliveActivity() {
        AliveActivity.openAliveActivity(mContext);
    }
​
​
    /**
     * 关闭AliveActivity
     */
    public void closeAliveActivity() {
        if (mAliveActivityWrf != null) {
            AliveActivity aliveActivity = mAliveActivityWrf.get();
            if (aliveActivity != null) {
                aliveActivity.finish();
            }
        }
    }
​
    /**
     * 屏幕状态广播
     */
    private class ScreenStateReceiver extends BroadcastReceiver {
​
        @Override
        public void onReceive(Context context, Intent intent) {
            if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
                // 开屏
                if (mCallback != null) {
                    mCallback.onScreenOn();
                }
            } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
                // 锁屏
                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");
​
      // 设置为1像素
      Window window = getWindow();
      // 放在左上角
      window.setGravity(Gravity.START | Gravity.TOP);
      WindowManager.LayoutParams params = window.getAttributes();
      // 宽高设置为1个像素
      params.width = 1;
      params.height = 1;
      // 起始坐标
      params.x = 0;
      params.y = 0;
      window.setAttributes(params);
       
      ScreenStateManager.getInstance(this).setAliveActivity(this);
​
  }
​
  /**
    * 打开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");
  }
}

MainActivity 的修改如下:

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);
        // 注册广播
        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();
        }
    }
}

运行之后,先点击home键,再进行锁屏,结果如下

由以上结果可以看出,在锁屏的时候 AliveActivity 已经启动,进程的优先级也提高了。

然后再开屏,结果如下所示:

当我们开屏时 ,AliveActivity 也退出了。

缺点: 存在一个AliveActivity 不够干净,同时需要在锁屏时才能监听到,如果用户一直处于亮屏状态,oom_adj 的值不会变小,如果系统内存不足,还是会被杀死。

2.2 前台服务

原理是通过调用 startForeground 方法提示 Service 的优先级,让 Services 变为前台服务。

对于 API level < 18:调用 startForeground(ID,new Notification()),发送空的Notification ,图标则不会显示。

对于 API level >= 18 并且 API leve < 26:

调用 startForeground(ID,new Notification()),发送空的Notification ,图标却会显示。

因此在需要提优先级的 service A 启动一个 InnerService,两个服务同时 startForeground,且绑定同样的 ID。Stop 掉 InnerService ,这样通知栏图标即被移除,这种方式在 API leve >=26 以上就不可以生效了,即使 Stop 调 InnerServices ,通知栏依然会有显示。

代码如下:

public class AliveService extends Service {
    public static final int NOTIFICATION_ID = 0x001;
    private static final String NOTIFICATION_CHANNEL_ID = "alive_notification_id";
​
    @Override
    public void onCreate() {
        super.onCreate();
        // API 18 以下,直接发送 Notification 并将其置为前台
        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以上,发送Notification并将其置为前台后,启动InnerService
            Notification.Builder builder = new Notification.Builder(this);
            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;
    }
​
    @Override
    public void onDestroy() {
        super.onDestroy();
        stopForeground(true);
    }
​
    public static class InnerService extends Service {
        @Override
        public void onCreate() {
            super.onCreate();
                //发送与ALiveService中ID相同的Notification,然后将其取消并取消自己的前台显示
                Notification.Builder builder = new Notification.Builder(this);
                builder.setSmallIcon(R.mipmap.ic_launcher);
                startForeground(NOTIFICATION_ID, builder.build());
​
                // 100ms 销毁
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        stopForeground(true);
                        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                        manager.cancel(NOTIFICATION_ID);
                        stopSelf();
                    }
                }, 100);
            } else {
                //发送与ALiveService中ID相同的Notification,然后将其取消并取消自己的前台显示
                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 销毁
                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 IBinder onBind(Intent intent) {
            return null;
        }
    }
}
在没有采取前台服务之前,启动应用,oom_adj 值是 0,按下返回键之后,变成 8(不同ROM可能不一样),如下图所示:

在采取前台服务之后,启动应用,oom_adj 值是 0,按下返回键之后,变成 1(不同ROM可能不一样),确实进程的优先级有所提高。

 

三、进程拉活

3.1 相互唤醒

相互唤醒的意思就是,假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的 app,那么你打开任意一个阿里系的 app 后,有可能就顺便把其他阿里系的 app 给唤醒了。这个完全有可能的。

如果应用想保活,要是 QQ,微信愿意救你也行,有多少手机上没有 QQ,微信呢?或者像友盟、小米、华为、信鸽这种推送 SDK,也存在唤醒 app 的功能。

3.2 广播拉活

通过接收系统广播去拉活进程,但是 Android 在7.0之后对广播增加了一些限制,在8.0以后就更加严格了,现在接收系统广播的拉活方式基本上已经用不了了。

3.3 JobScheduler

JobScheduler 简单来说就是一个系统定时任务,在 app 达到一定条件时可以指定执行任务,且如果 app 被强迫终止,此前预定的任务还可执行。与普通定时器不同的是其调度由系统来完成,因此可以用来做进程保活。

JobSchedule 需要在android 5.0系统以上才能使用。

JobScheduler 是作为进程死后复活的一种手段,native 进程方式最大缺点是费电, Native 进程费电的原因是感知主进程是否存活有两种实现方式,在 Native 进程中通过死循环或定时器,轮训判断主进程是否存活,当主进程不存活时进行拉活。其次 5.0 以上系统不支持。 但是 JobScheduler 可以替代在 Android5.0 以上 native 进程方式,这种方式即使用户强制关闭,也能被拉起来。

@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.Builder(1,
                    new ComponentName(context.getPackageName(), AliveJobService.class.getName()));
​
            // 设置设备重启依然执行
            builder.setPersisted(true);
​
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                //7.0以上延迟1s执行
                builder.setMinimumLatency(1000);
            } else {
                //每隔1s执行一次job
                builder.setPeriodic(1000);
            }
​
            jobScheduler.schedule(builder.build());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
​
    @Override
    public boolean onStartJob(JobParameters params) {
        Log.e(TAG, "开启job");
        // 7.0 以上开启轮询
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            startJobScheduler(this);
        }
        return false;
    }
​
    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }
​
}

在 AndroidManifest.xml 中配置 Service 。

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

在 MainActivity 的 onCreate 方法中调用。

AliveJobService.startJobScheduler(this);

运行,然后在 kill ,最后 Jobschedule 依然在运行,而且进程重新启动,结果如下所示:

 

3.4 双进程守护

双进程守护本质上是开启两个进程,一个主进程(包含一个本地服务)和一个子进程(包含一个远程服务),当其中一个进程被杀死时,另一个进程会自动的把被杀死的那个进程拉活。原理图如下所示:

(1) 实现一个 AIDL 文件

此处仅仅是为了拉活,不需要远程调用某些功能,可以不用具体实现,但是不能缺少,创建过程如下所示:

代码如下所示:

// 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);
}

(2) 实现主进程的本地服务

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 LocalAliveServiceConnect();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //绑定本地守护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) {
            //服务连接后回调
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "远程子进程可能被干掉了,拉活");
            //连接中断后回调,再启动子进程所在的Service,并进行绑定,通过启动子进程的远程服务强行拉活
            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 {

        }
    }
}

(3) 实现子进程的远程服务

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 RemoteAliveServiceConnect();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //绑定本地守护Service,必须实现AIDL否则bindService在这没有作用
        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) {
            //服务连接后回调
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "本地主进程可能被干掉了,拉活");
            //连接中断后回调,再启动主进程所在的Service,并进行绑定,通过启动主进程的本地服务强行拉活
            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 {

        }
    }
}

(4) AndroidManifest.xml 配置 Service 如下:

<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"/>

(5) 开启双进程守护

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) {
            //双进程Service守护
            startService(new Intent(this, LocalAliveService.class));//启动主线程守护服务
            startService(new Intent(this, RemoteAliveService.class));//启动主线程守护服务
        }
    }
}

说明: 在 Android O之后,当应用进入后台时,过一段时间就会变成 idle 状态,这时就不能通过 startService 启动一个服务,不然会报如下错误:

在 Android O 之后启动服务通过 startForegroundService() 启动一个前台服务,并且在系统创建 Service 后,需要在一定时间内调用startForeground( )让 Service 为用户可见通知,否则则系统将停止此 Service,抛出 ANR,但是会在通知栏有提示框,上文有说明。

因此兼容方案就根据实际情况而选择了。

运行结果如下所示:

3.5 账户同步拉活

手机系统设置里会有 Account 账户一项功能,任何第三方 App 都可以通过此功能将我们自己的 App 注册到这个 Account 账户中,并且将数据在一定时间内同步到服务器中去。系统在将 App 账户同步时,自动将为开启的 App 进程拉活

(1) 开启账户服务

AuthenticationService 继承自 Service 本质上是一个 AIDL ,提供给其他的进程使用的,主要我们实现并且声明了之后,android 系统会通过 android.accounts.AccountAuthenticator 这个 Action 找到它,并通过它来把我们自己的账号注册到系统设置界面,其中AccountAuthenticator 是一个继承自 AbstractAccountAuthenticator 的类,而 AbstractAccountAuthenticator 是用于实现对手机系统设置里“账号与同步”中 Account 的添加、删除和验证等一些基本功能。很明显 AbstractAccountAuthenticator 里面有个继承于IAccountAuthenticator.Stub 的内部类,以用来对 AbstractAccountAuthenticator 的远程接口调用进行包装。所以可以通过AbstractAccountAuthenticator 的 getIBinder() 方法,返回内部类的 IBinder 形式。

public class AuthenticationService extends Service {
    private AccountAuthenticator accountAuthenticator;


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

    @Override
    public IBinder onBind(Intent intent) {
        // 返回操作数据的Binder
        return accountAuthenticator.getIBinder();
    }

    /**
     * 账户操作类
     */
    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;
        }
    }
}

在 AndroidManifest.xml 中配置 service。

<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>

在 xml 中添加 authenticator.xml 。

其中icon、label分别是Account列表中的图标和显示名称,而accountType则是操作用户所必须的参数之一

<?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表示账户类型,必须唯一-->

经过以上步骤之后,安装 Apk,再次打开设置中账户 -> 添加账户,你会发现原来的 Account 列表多了一行数据,说明我们的 App 也可以支持这个Account 系统了。

(2) 添加账户

public class AccountHelper {
    // 与 authenticator.xml 中  accountType 一致
    private static final String ACCOUNT_TYPE = "com.lx.keep.alive.account";

    /**
     * 添加账户 需要 "android.permission.GET_ACCOUNTS"权限
     * @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 ){
            // 账户已存在
            return;
        }

        Account account = new Account("test",ACCOUNT_TYPE);
        // 添加账户
        accountManager.addAccountExplicitly(account,"123456",new Bundle());
    }
}

调用 addAccount 这个方法之后就会在系统设置的 Account 界面多了一个 Account 。如下图所示:

(3) 同步服务

创建一个 Service 作为同步 Service,并且在 onBind 返回 AbstractThreadedSyncAdapter 的 getSyncAdapterBinder。

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, "账户同步了");
        }
    }
}

在 AndroidManifest.xml 中配置 service。

<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>

在 xml 中添加 sync_account_adapter.xml 。

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

<!--contentAuthority 系统在进行账户同步的时候会查找 此auth的ContentProvider-->
<!--accountType表示账户类型,与authenticator.xml里要一致-->
<!-- userVisible 是否在“设置”中显示-->
<!-- supportsUploading 是否必须notifyChange通知才能同步-->
<!-- allowParallelSyncs 允许多个账户同时同步-->
<!--isAlwaysSyncable 设置所有账号的isSyncable为1-->

(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 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.alive.provider"
    android:exported="false" />

(5) 开启同步

为了达到进程保活的效果,可以开启自动同步。时间间隔虽然设置了1s,但是 Android 本身为了考虑同步所带来的消耗和减少唤醒设备的次数,1s只是一个参考时间

public class AccountHelper {
    // 与 authenticator.xml 中  accountType 一致
    private static final String ACCOUNT_TYPE = "com.lx.keep.alive.account";
    private static final String CONTENT_AUTHORITY = "com.lx.keep.alive.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 运行到手机之后,然后杀掉进程,当账户开始同步时,进程又启动了,结果如下:

以上就是利用账户同步进行拉活的主要核心思想,测试过程中发现,不同系统表现不同,至于同步周期完全是由系统进行控制的,虽然比较稳定但是周期不可控。

 

扫描下方二维码关注公众号,获取更多技术干货。

 

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值