美文网首页Android技术知识
GitHub本周热榜:Android 进程保活方案

GitHub本周热榜:Android 进程保活方案

作者: Android开发工作者 | 来源:发表于2020-12-26 14:19 被阅读0次

前言

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 值,如下所示:

image

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 已经启动,进程的优先级也提高了。

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

image

当我们开屏时 ,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可能不一样),如下图所示:
image

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

image

三、进程拉活

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 双进程守护

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

image

(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 启动一个服务,不然会报如下错误:

image

在 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 运行到手机之后,然后杀掉进程,当账户开始同步时,进程又启动了,结果如下:

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

相关文章

  • GitHub本周热榜:Android 进程保活方案

    前言 Android 系统为了保持系统运行流畅,在内存吃紧的情况下,会将一些进程给杀掉,以释放一部分内存。然而,对...

  • 第十六周 进程保活

    话题:进程保活 这个问题时常在面试中被问到关键字:Android 进程保活招式大全 参考答案 1.进程保活方案 -...

  • Android进程保活实践总结

    Android进程保活

  • Android进程保活方案

    目的 进程保活是各个公司想要实现的一个功能,可以“无赖”地一直霸占你的手机,不被系统和第三方杀掉,以下是几种方案,...

  • Android 进程保活方案

    Android 系统为了保持系统运行流畅,在内存吃紧的情况下,会将一些进程 kill ,以释放一部分内存。然而,对...

  • Android进程保活方案

    自己曾经也在这个问题上伤过脑经,前几日刚好有一个北京的哥们在QQ说在做IM类的项目,问我进程保活如何处理比较恰当,...

  • Android进程保活方案

    自己曾经也在这个问题上伤过脑经,前几日刚好有一个北京的哥们在QQ说在做IM类的项目,问我进程保活如何处理比较恰当,...

  • Android进程保活方案

    自己曾经也在这个问题上伤过脑经,前几日刚好有一个北京的哥们在QQ说在做IM类的项目,问我进程保活如何处理比较恰当,...

  • 关于 Android 进程保活

    关于 Android 进程保活 Android进程保活手段主要分3种: 1:利用不同的app进程使用广播来进...

  • 进程保活方案学习

    进程保活方案 进程保活主要有两个方案 提高进程优先级,降低死亡几率 在进程被杀死后进行拉活 进程为什么会死亡 从L...

网友评论

    本文标题:GitHub本周热榜:Android 进程保活方案

    本文链接:https://www.haomeiwen.com/subject/xucgnktx.html