探索App保活黑科技

我们来聊聊目前可用的App保活技术。这些方法在目前看来都还可以用,并且效果也很好。但无法保证长期可用,因为Android操作系统本身可能会更改其策略,而且各厂商在定制Android时也会引入自家节电策略。这些都有可能破坏我们原本可用的保活逻辑,所以当Android系统本身发生策略改变时,还需要去查看官方文档,关注有关影响保活的策略改变,并适配它们。此外,针对各厂商定制的系统,还应该尽可能地多做测试,尽可能地确保App在大部分设备上是正常运行的。

基础知识

  • Android进程优先级
    一般情况下,Android会尽可能保持应用进程,但在特定场景下会对进程进行kill,例如为了清除旧进程来回收内存等。为了区分哪些进程最先被回收清理,而哪些不会,有一个优先级别,这就是Android的进程优先级,具体包括下面5种
Foreground/Active process 用户当前操作的进程,包括用户正在交互的Activity,绑定用户正在交互Activity的Service,使用 startForeground 的 Service,正在执行 onReceive 的 BroadcastReceiver 等
Vsible process 会影响用户所见内容的进程,如onPause 状态的 Activity 等
Service process 后合服务,如正在运行 startservice 启动的 Service。
Background process 对用户交互无影响,如 onStop 状态的 Activity 等,系统可能随时对其进行终止。
Empty process 一般用作缓存以缩短下次启动时间,系统往往会终止这些空进程。
  • Android进程回收策略
    Android 中主要通过 LMK (LoW Memory Killer)来对进程进行回收管理,LMK是在 Android
    系统内存不足而选择kill 部分进程以释放空间时,生死大权的决定者,其基于 Linux 的 OOM
    机制,阀值定义如下面所示 (lowmemorykiller 文件中),当然也可以通过系统的 init.ro 实现
    自定义。
static uint32_t lowmem_debug_level = 2;
static int lowmem_adj[6] = {
	0,
	1,
	6,
	12,
};
static int lowmem_adj_size = 4;
static int lowmem_minfree[6] = {
	3 * 512,	/* 6MB */
	2 * 1024,	/* 8MB */
	4 * 1024,	/* 16MB */
	16 * 1024,	/* 64MB */
};
static int lowmem_minfree_size = 4;

在LMK 中通过进程的oom_adj 与占用内存的大小决定要杀死的进程,oom_adj值越小,越不容易被杀死。其中,lowmem_minfiee 是杀进程的时机,谁被杀,则取决于 lowmem_adj,具体值参考ProcessList 类。
在init.rc中定义了init 进程(系統进程)的oom_adj 为-16,其不可能会被杀死(init 的PID是1),而前台进程是 0。(这里的前台进程是指用户正在使用的 activity所在的进程),例如用户按Home 键回到桌面时的优先级是6,普通的 Service 的进程优先级是 8。

  • 查看某个App的进程
    为了验证我们的保活方法是否有效,最直观的方法是通过 adb 命令查看具体 App 的进程信息,具体命令如下。
adb shell
ps|grep 进程名
cat /proc/PID/oom_adj //其中PID是上述grep得到的进程号
  • Linux am命令
    am 命令是Android 系统中通过adb shell 启动某个 Activity、Service、拨打电话、启动浏
    览器等操作 Android 的命令,其源码在 Am.java中,在shell 环境下执行 am 命令实际是启动
    一个线程执行 Am.iava 中的主函数(main 方法),am 命令后跟的参数都会当作运行时参
    数传递到主函数中,主要实现在 Am.java 的run 方法中。
例如:拨打电话
adb shell
am start -a android.intent.action.CALL -d tel:10086
  • NotificationListenerService
    NotificationListenerService用来监听通知的发送以及移除和排名位置变化,如果我们注册了这个服务,当系统任何一条通知到来或者被移除掉时,都能通过这个服务监听到,甚至做一些管理工作。

添加电池优化白名单

App保活的第一种就是添加白名单,让系统的电池优化机制忽略相应的App。
原生Android进行白名单的添加很方便,各品牌厂商的系统由于定制化了原生Android,在添加白名单的方法上略有不同。除了按照原生Android的逻辑进行添加外,还需要对不同品牌的机型做单独的逻辑处理。

  • 原生Android方法
    原生Android的电池优化机制从API Level引入,其目的在于通过使App进入“休眠”状态而节约电量开销。我们这里需要让App保持运行,于是理所当然要让该机制忽略我们开发的App。
    首先在AndroidManifest中声明权限:
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>

该权限无须用户批准,直接声明即可。
程序运行时先检查自身是否已经在电池优化白名单中,如果在,就无须任何操作;如果不在,就弹出请求添加到白名单的窗口,这个窗口由Android系统提供。无论用户选择拒绝添加活接受添加,我们都要获取用户的选择结果,以便今后在适当的时机弹出提示。

public class BackActivity extends Activity {
    @Override
    protected void onCreate( Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initData();
    }


    private void initData(){
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
            if(!isIgnoreBatteryOptimizationStatus(this)){
                requireIgnoreBatteryOptimization(this);
            }
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    private boolean isIgnoreBatteryOptimizationStatus(Activity context){
        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        return powerManager != null && powerManager.isIgnoringBatteryOptimizations(context.getPackageName());
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    private void requireIgnoreBatteryOptimization(Activity context){
        try{
            Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
            intent.setData(Uri.parse("package:"+context.getPackageName()));
            context.startActivityForResult(intent,0x00);
        }catch (Exception e){
            e.printStackTrace();
        }

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode == 0x00){
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
                Log.d("Activity","Ignore Battery Optimization Status is "+isIgnoreBatteryOptimizationStatus(this));
            }
        }
    }
}
  • 定制化Android操作系统的处理
    对于各厂商定制化的Android操作系统,除了按照原生Android的方法添加白名单外,还需要添加到各厂商手机管家或安全管家之类App的白名单中。很多App具有跳转到手机管家的功能,并给予用户提示,引导用户进行设置。想要实现这种功能,原理很简单。界面的跳转实际上就是常见的startActivity方法。难点在于如何获取跳转目标的相关设置的包名和activity类名。
    下面介绍三种获取方法:
  1. 使用logcat,大部分手机都是可以看见内置的日志的
  2. 使用RE浏览器
  3. Google上查询

白名单内的App为何会被杀

我们都知道,Andnoid 操作系统有这样的机制:当内存空间不足、系统资源过低时,系统会按照 App 的优先级清理掉那些重要度不高的程序。如果我们的推送服务恰好在被清理的名单中,那么被清理只是时问问题。可见,在测试人员处能及时发现这个问题是当么宝费。毕竟用户的手机可能会同时运行更多的 App,被清理的可能性会更大,也可能会更加频繁。
那么,如何判断确实是由于内存不足造成App被杀呢?这就要通过获取App运行状态来判断了。
首先,我们要拿到那台可以复现问题的机器,然后到去看看它触发内存清理的时刻。
Android 系统中使用一个配置文件来表示内存阀值,该值根据 App 不同运行状态有多个值。一旦可用内存小于等于这个值,相应运行状态的 App 就会被清理掉。下表是配置了2GB内存的小米手机的内存清理阀值以及对应的App的运行状态。

运行状态 內存阈值
FOREGROUD APP (前台进程) 14746(大约 57MB)
VISIBLE APP(可见进程) 18432 (72MB)
SECONDARY SERVER(次要服务) 22118 (大约86MB)
HIDDEN APP(后台进程) 25805(大约 100MB)
CONTENT PROVIDER(内容提供者) 40000(大约156MB)
EMPTY APP(空进程) 55000 (44 215MB)

很明显,在这合设备上,一旦可用内存小于或等于 72MB,只要程序不在前台运行,将会被清理掉。
这里要特别说明前台进程和可见进程的区别。一般来讲,它们本质的不同是当前是否正在处于与用户交互的状态。比如,你现在正在和一个名为A的 App 进行交互,突然名为B的App弹出了一个对话框,此时A 由前合进程转为可见进程(此时 A 的界面被 B的对话框挡住,虽然部分可见,但用户无法直接和 A 交互),B变为前合进程。
注意,即使是相同生产厂商,内存阙值也会由于设备本身搭载内存大小的不同而有所变化。所以,笔者建议在做保活测试时,最好选择一款内存小的机器,并同时运行多个市场上常见的 App,让问题尽快暴露。
只知道系统清理内存的原则还不够,还需要了解你开发的App占用内存以及各进程优先级的情况,这样才能确定问题是否确实是由于内存不足造成的。
要获取App的内存占用量,一种方法是利用Android Profiler,只要App处于Debug模式,就可以使用;另一种方法是通过adb命令,该方法适用型更广泛,不要求App必须对于Debug模式,而且能看到整个手机的内存占用情况。

adb shell dumpsys meminfo

接着,获取该App的运行优先级(需要取得Root权限)

cat /proc/进程ID/oom_adj

OOM_ADJ值 含义
-16 一般指system进程
-11 框架进程或常驻App,即AndroidManifest中persistent值为true的app
0 前台App
1 可见应用或有persistent进程关联的Service或Provider
2 可感知进程,通常指startService且调用了startForeground的服务
5/8 startService启动的服务,但没有Activity在运行
6 Home Launcher
9-16 缓存进程,通常是切回Home的App、Empty进程等

看到这,你或许明白了那些网上所谓的通告提高优先级或保持前台的方式来保活App的原理了。实际上,它们就是利用OOM_ADJ值的不同,努力多争取进程不被清理掉的可能。
此外,对于没有Root权限的设备,还可以通过执行:

adb shell dumpsys meminfo

通过名称来获取进程优先级
系统总共的可用内存量可以通过以下命令查看:

adb shell cat /proc/menifo

现在,有了系统可用内存、App所属进程优先级以及App内存占用量的信息,足够我们做出判断了。

重新设计推送服务

如何保证推送服务在设备上稳定运行呢?
单方面提升进程优先级,或直接长时间播放无声音频。。。如果每个App都如此暴力,最终的结果就是手机整体运行体验不佳。

  • 推送服务的实现原则
    正如前文所达,笔者不提倡用暴力的方式实现保活,这样会降低用户的整体使用体验,还会徒增手机功耗。所以,要实现推送服务的保活,一方面要结合前面的内容,引导用户添加白名单;另一方面,要使用“轻量级”进程,降低内存占用率。也就是说,让每个 App 的推送服务都单独占用极低的内存。同时,当 App 不再是前台应用时,及时清理内存消耗,让消息接收的逻辑只存在于推送服务的进程中。反过来,该进程也只包含推送服务的相关逻辑。
    另外,由于这样的进程占用的内存极少,可以适当使用提升优先级以及被杀后及时复活的策咯来保护这个进程。
  • 推荐推送服务保活的方式
  1. 创建轻量级进程
    示例如下:
class CounterService extends Service{
    @Override
    public void onCreate() {
        super.onCreate();
    }

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

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

接着,在AndroidManifest文档中注册该Service

<service 
   android:name = ".CounterService"
   android:process = ":counter"/>

此外,如果读者想要进一步增强进程的存活能力,还可以监听某些系统广播,实现更快地重启进程。当然,这要求用户己经将 App 添加到白名单作为前提。
另一方面,由于用户将我们的 App 添加到白名单中,这就等于充分相信 App 不会在手机上胡作非为,包括系统资源的滥用,这是作为开发者特别要警戒的一点。
2. 集成厂商推送
另一种方式是集成各厂商的推送服务。别急,这里并不是要求开发者去各厂商分别下载推送 SDK,分别阅读开发文档,这样太费事了。
笔者建议使用极光推送,截至作者撰稿时,该平台已经集合了华为、小米、魅族、Vivo 和Oppo 五家手机厂商的推送通道,此外,还支持 Google 的Firebase 以及自建推送通道。
有了这几家厂商的推送通道,实际上就覆盖了市场上的大部分用户。虽然通过极光使用各厂商自己的推送通道可以达到一次集成的目的,但各厂商的 AppID、AppKey 等参数还需要分别进行注册才行。但是,接入这种第三方推送有一个弊端,会被流量劫持。

如果想要进一步了解进程保活相关的技术,可以参考下面这篇博客:《一种提高Android应用进程存活率新方法 》

你可能感兴趣的:(性能优化,科技,android,android,studio)