【Android开发高级系列】进程保活专题

2023-10-16 12:01:51 浏览数 (1)

1 概述

        进程在内存中时活动主要有五种状态,即前台进程、可见进程、服务进程、后台进程、空进程,这几种状态的进程优先级由高到低,oom_adj值由低到高(在ProcessList定义),然后Android系统会根据当前系统资源和进程oom_adj值来回收相应的进程,前台进程一般不会被回收,空进程最容易被回收,这种管理规则就是"传说中"的Low Memory Killer。

        各种类型进程的oom_adj值

        注:优先级1表示最高级,普通进程的oom_adj>=0,系统进程oom_adj<0,系统会根据相应的内存阀值对符合某段oom_adj值的进程进行回收。另外,oom_adj值也会随着占用物理内存越大而增大,系统进程绝对不会被系统杀死。

2 常见的保活拉起方式

        了解进程被杀死的相关场景后,相信大家对进程保活已经有了初步的认识,接下来我将给大家介绍一下,现在市面上存在的各种常见的保活拉起方式,这些保活方式如下:

    a) 将Service设置为前台服务

    b) 在service的onstart方法里返回STATR_STICK

    c) 添加Manifest文件属性值为android:persistent=“true”

    d) 覆写Service的onDestroy方法

    e) 监听一堆系统静态广播

    f) 监听第三方应用的静态广播

    g) AlarmManager唤醒

    h) 账户同步,定时唤醒

    i) 1像素悬浮层

    j) GCM或其它3方推送

    k) 应用间互相拉起

    l) 心跳唤醒

    m)Native进程拉起

    n) 双进程守护**

2.1 将Service设置为前台服务

思路:启用前台服务,主要是startForeground()

保活程度:一般情况下不被杀,部分定制ROM会在应用切到后台即杀,会被 force stop 杀死

代码实现:

Notificationnotification = newNotification(R.drawable.queen2, "有消息来了", System.currentTimeMillis());

notification.setLatestEventInfo(this, "双11,上天猫!", "一律5折", null);

//设置通知默认效果

notification.flags = Notification.FLAG_SHOW_LIGHTS;

startForeground(1, notification);

2.2 在service的onstart方法里返回STATR_STICK

思路:其实就是onStartCommand中返回STATR_STICK

保活程度:有次数和时间的限制,会被 force stop 杀死

代码实现:

@Override

public int onStartCommand(Intent intent, int flags, int startId) {

   // TODO Auto-generated method stub

   return START_STICKY;

   //return super.onStartCommand(intent, flags, startId);

}

2.3 添加Manifest文件属性值为android:persistent=“true”

代码实现(清单文件中配置):

保活程度:一般情况下不被杀,会被 force stop 杀死

        注意:该方法需要系统签名

2.4 覆写Service的onDestroy方法

思路:在onDestroy中再次启动该服务

保活程度:很弱,只在两种情况下work:正在运行里杀服务、DDMS里stop进程

代码实现:

@Override

public void onDestroy() {

   Intent intent = new Intent(this, KeeLiveService.class);

   startService(intent);

   super.onDestroy();

}

2.5 监听一堆系统静态广播

思路:

        在发生特定系统事件时,系统会发出响应的广播,通过在 AndroidManifest 中“静态”注册对应的广播监听器,即可在发生响应事件时拉活。

        可以监听的系统静态广播列表如下:

保活强度:

        我们可以发现,这个方法都是监听系统的一些广播,所以我们需要在我们的应用中注册静态广播,但是静态广播又会出现问题,那就是在4.0版本以上,没有启动过的应用或Force-Stop后收不到静态广播,也就是说4.0以后,如果我们应用从未启动过,或者被Force-Stop杀死过,是无法接收到静态广播的。

        如果是两个应用相互拉起,那么在一个应用内可发送带FLAG_INCLUDE_STOPPED_PACKAGES的Intent,那即使另一个应用也是以上两种情况,也可以接收到系统的广播

        应用1的代码实现:

//应用1,发送拉起服务的广播

Intent intent = new Intent();

intent.setAction("com.action.keepLive");

intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);

this.sendBroadcast(intent);

应用2的代码实现:

public class KeepLiveReceiver extends BroadcastReceiver{

   //应用2中,接受应用1发送的广播,进行服务的拉起

   @Override

   public void onReceive(Contextcontext, Intent intent) {

        Intent i = newIntent(context, KeeLiveService.class);

        context.startService(i);

   }

}

2.6 监听第三方应用的静态广播

思路:

        通过反编译第三方 Top 应用,如:手机QQ、微信、支付宝、UC浏览器等,以及友盟、信鸽、个推等 SDK,找出它们外发的广播,在应用中进行监听,这样当这些应用发出广播时,就会将我们的应用拉活。

保活强度:

        该方案的局限性除与系统广播一样的因素外,主要受如下因素限制:

    1) 反编译分析过的第三方应用的多少

    2) 第三方应用的广播属于应用私有,当前版本中有效的广播,在后续版本随时就可能被移除或被改为不外发,这些因素都影响了拉活的效果。

2.7 AlarmManager唤醒

思路:通过AlarmManager设置一个定时器,定时的唤醒服务

    **保活强度:**killBackgroundProcess下,大部分情况work,

    不敌force-stop,闹钟会被清除。

代码实现:

public void startKeepLiveService(Context context, int timeMillis, String action) {

   //获取AlarmManager系统服务

   AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);

   //包装Intent

   Intent intent = newIntent(context,KeepLiveServie.class);

   intent.setAction(action);

   PendingIntent pendingIntent = PendingIntent.getService(context,0,intent,

PendingIntent.FLAG_UPDATE_CURRENT);

   //添加到AlarmManager     

    alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), timeMillis,pendingIntent);

}

2.8 账户同步,定时唤醒

思路:android系统里有一个账户系统,系统定期唤醒账号更新服务,同步的事件间隔是有限制的,最短1分钟。

难点:需要手动设置账户,你如何骗你的用户给你手动设置账户完了之后不卸载你,必须联网

代码实现:

    ① 建立数据同步系统(ContentProvider)

        通过一个ContentProvider用来作数据同步,由于并没有实际数据同步,所以此处就直接建立一个空的ContentProvider即可。

public class XXAccountProvider extends ContentProvider{

   public static final String AUTHORITY = "包名.provider";

   public static final String CONTENT_URI_BASE = "content://" AUTHORITY;

   public static final String TABLE_NAME = "data";

   public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_BASE "/" TABLE_NAME);

   @Override

   public boolean onCreate() {

        return true;

   }

   @Nullable

   @Override

   public Cursor query(Uri uri, String[] projection,String selection, String[] selectionArgs, String sortOrder) {

        return null;

   }

   @Nullable

   @Override

   public String getType(Uri uri) {

        return newString();

   }

   @Nullable

   @Override

   public Uri insert(Uri uri, ContentValues values) {

        return null;

   }

   @Override

   public int delete(Uri uri, String selection, String[] selectionArgs) {

        return 0;

   }

   @Override

   public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {

        return 0;

   }

}

然后再Manifest中声明

    ② 建立Sync系统(SyncAdapter)

        通过实现SyncAdapter这个系统服务后, 利用系统的定时器对程序数据ContentProvider进行更新,具体步骤为:

- 创建Sync服务

public class XXSyncService extends Service{

   private static final Object sSyncAdapterLock = newObject();

   private static XXSyncAdapter sSyncAdapter = null;

   @Override

   public void onCreate() {

        synchronized(sSyncAdapterLock) {

            if (sSyncAdapter == null) {

                sSyncAdapter = newXXSyncAdapter(getApplicationContext(), true);

            }

        }

   }

   @Override

   public IBinder onBind(Intent intent) {

        return sSyncAdapter.getSyncAdapterBinder();

   }

   static class XXSyncAdapter extends AbstractThreadedSyncAdapter{

        public XXSyncAdapter(Context context, boolean autoInitialize) {

            super(context, autoInitialize);

        }

        @Override

        public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {

            getContext().getContentResolver().notifyChange(XXAccountProvider.CONTENT_URI, null, false);

        }

   }

}

    声明Sync服务

        其中sync_adapter为:

        参数说明:

    android:contentAuthority 指定要同步的ContentProvider在其AndroidManifest.xml文件中有个android:authorities属性。

    android:accountType 表示进行同步的账号的类型。

    android:userVisible 设置是否在“设置”中显示

    android:supportsUploading 设置是否必须notifyChange通知才能同步

    android:allowParallelSyncs 是否支持多账号同时同步

    android:isAlwaysSyncable 设置所有账号的isSyncable为1

    android:syncAdapterSettingsAction 指定一个可以设置同步的activity的Action。

账户调用Sync服务

    首先配置好Account(第三步),然后再通过ContentProvider实现

    手动更新

public void triggerRefresh() {

   Bundle b = newBundle();

   b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);

   b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);

   ContentResolver.requestSync(account, CONTENT_AUTHORITY, b);

}

添加账号

Account account = AccountService.GetAccount();

AccountManager accountManager = (AccountManager)context.getSystemService(Context.ACCOUNT_SERVICE);

accountManager.addAccountExplicitly(...)

同步周期设置

ContentResolver.setIsSyncable(account, CONTENT_AUTHORITY, 1);

ContentResolver.setSyncAutomatically(account, CONTENT_AUTHORITY, true);

ContentResolver.addPeriodicSync(account, CONTENT_AUTHORITY, new Bundle(), SYNC_FREQUENCY);

    ③ 建立账号系统(Account Authenticator)

        通过建立Account账号,并关联SyncAdapter服务实现同步

- 创建Account服务

public class XXAuthService extends Service{

   private XXAuthenticator mAuthenticator;

   @Override

   public void onCreate() {

        mAuthenticator = new XXAuthenticator(this);

   }

   private XXAuthenticator getAuthenticator() {

        if (mAuthenticator == null)

            mAuthenticator = new XXAuthenticator(this);

        return mAuthenticator;

   }

   @Override

   public IBinder onBind(Intent intent) {

        returngetAuthenticator().getIBinder();

   }

   class XXAuthenticator extends AbstractAccountAuthenticator {

        private final Context context;

        private AccountManager accountManager;

        public XXAuthenticator(Contextcontext) {

            super(context);

            this.context = context;

            accountManager = AccountManager.get(context);

        }

        @Override

        public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throwsNetworkErrorException 

        {

            // 添加账号 示例代码

            final Bundle bundle = newBundle();

            final Intent intent = newIntent(context, AuthActivity.class);

           intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);

           bundle.putParcelable(AccountManager.KEY_INTENT, intent);

            return bundle;

        }

        @Override

        public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {

            // 认证 示例代码

            String authToken = accountManager.peekAuthToken(account, getString(R.string.account_token_type));

            //if not, might be expired, register again

            if(TextUtils.isEmpty(authToken)) {

                final String password = accountManager.getPassword(account);

                if (password != null) {

                    //get new token

                    authToken = account.name password;

                }

            }

            //without password, need to sign again

            final Bundle bundle = newBundle();

            if(!TextUtils.isEmpty(authToken)) {

               bundle.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);

               bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);

               bundle.putString(AccountManager.KEY_AUTHTOKEN, authToken);

                return bundle;

            }

            //no account data at all, need to do a sign

            final Intent intent = newIntent(context, AuthActivity.class);

           intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);

           intent.putExtra(AuthActivity.ARG_ACCOUNT_NAME, account.name);

           bundle.putParcelable(AccountManager.KEY_INTENT, intent);

            return bundle;

        }

        @Override

        public String getAuthTokenLabel(StringauthTokenType) {

//            throw newUnsupportedOperationException();

            return null;

        }

        @Override

        public Bundle editProperties(AccountAuthenticatorResponseresponse, String accountType) {

            return null;

        }

        @Override

        public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {

            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;

        }

   }

    声明Account服务

其中authenticator为:

    使用Account服务

    • 同SyncAdapter,通过AccountManager使用

    • 申请Token主要是通过 [AccountManager.getAuthToken]系列方法

    • 添加账号则通过[AccountManager.addAccount]

    - 查看是否存在账号通过[AccountManager.getAccountsByType]

保活强度:

        该方案适用于所有的Android 版本,包括被 fore stop 掉的进程也可以进行拉活。最新 Android 版本(Android N)中系统好像对账户同步这里做了变动,该方法不再有效。

2.9 1像素悬浮层

思路:

        1像素悬浮层是传说的QQ黑科技,监控手机锁屏解锁事件,在屏幕锁屏时启动1个像素的 Activity,在用户解锁时将 Activity 销毁掉。注意该 Activity 需设计成用户无感知。通过该方案,可以使进程的优先级在屏幕锁屏时间由4提升为最高优先级1。

保活强度:

        前台进程,跟前台服务差不多。需要权限,不敌force-stop。

实现代码:

        首先定义 Activity,并设置 Activity 的大小为1像素:

public class KeepLiveActivityextendsAppCompatActivity{

    private static final StringTAG = "keeplive";

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        //setContentView(R.layout.activity_main);

        Window window = getWindow();

       window.setGravity(Gravity.LEFT|Gravity.TOP);

        WindowManager.LayoutParamsparams = window.getAttributes();

        params.x = 0;

        params.y = 0;

        params.height = 1;

        params.width = 1;

       window.setAttributes(params);

    }

}

其次,从 AndroidManifest 中通过如下属性,排除 Activity 在 RecentTask 中的显示:

        最后,控制 Activity 为透明:

        Activity 启动与销毁时机的控制:

public class KeepLiveReceiver extends BroadcastReceiver{

    private Context mContext;

    @Override

    public void onReceive(Context context, Intentintent) {

        String action = intent.getAction();

        if(action.equals(Intent.ACTION_SCREEN_OFF)){

           KeepLiveManeger.getInstance(mContext).startKeepLiveActivity();

        }else if(action.equals(Intent.ACTION_USER_PRESENT)) {

           KeepLiveManeger.getInstance(mContext).destroyKeepLiveActivity();

        }

       KeepLiveManeger.getInstance(mContext).startKeepLiveService();

    }

}

2.10 应用间互相拉起

思路:

        app之间知道包名就可以相互唤醒了,比如你杀了我qq,只要微信还在就能确保随时唤醒qq。还有百度全系app都通过bdshare实现互拉互保,自定义一个广播,定时发,其他app收广播自起等。

2.11 心跳唤醒

思路:微信保活技术,依赖系统特性:长连接网络回包机制

保活强度:不敌force-stop,需要网络,API level >= 23的doze模式会关闭所有的网络

代码实现:

public class HeartbeatService extends Service implements Runnable{

    private Thread mThread;

    public int count = 0;

    private boolean isTip = true;

    private static String mRestMsg;

    private static String KEY_REST_MSG = "KEY_REST_MSG";

    @Override

    public void run() {

        while (true) {

            try{

                if (count > 1) {

                    count =1;

                    if(isTip) {

                        //判断应用是否在运行

                       ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);

                       List list = am.getRunningTasks(3);

                        for(RunningTaskInfo info :list) {

                            if (info.topActivity.getPackageName().equals("org.yhn.demo")) {

                                //通知应用,显示提示“连接不到服务器”

                               Intent intent = new Intent("org.yhn.demo");

                               intent.putExtra("msg", true);

                               sendBroadcast(intent);

                                break;

                            }

                        }

                        isTip = false;

                    }

                }

                if (mRestMsg != "" && mRestMsg != null) {

                    //向服务器发送心跳包

                   sendHeartbeatPackage(mRestMsg);

                    count = 1;

                }

                Thread.sleep(1000 * 3);

            }catch(InterruptedException e){

                e.printStackTrace();

            }

        }

    }

    private void sendHeartbeatPackage(String msg) {

        HttpGet httpGet = new HttpGet(msg);

        DefaultHttpClient httpClient = new DefaultHttpClient();

        // 发送请求

        HttpResponse httpResponse = null;

        try{

            httpResponse = httpClient.execute(httpGet);

        }catch(Exception e) {

            e.printStackTrace();

        }

        if (httpResponse == null) {

            return;

        }

        // 处理返回结果

        final int responseCode = httpResponse.getStatusLine().getStatusCode();

        if(responseCode == HttpStatus.SC_OK) {

            //只要服务器有回应就OK

            count = 0;

            isTip = true;

        }else{

            Log.i("@qi", "responseCode" responseCode);

        }

    }

    @Override

    public IBinder onBind(Intent intent) {

        return null;

    }

    @Override

    public void onCreate() {

        super.onCreate();

    }

    @Override

    public void onDestroy() {

        super.onDestroy();

    }

    public void onStart(Intent intent, intstartId) {

        Log.i("@qi", "service onStart");

        //从本地读取服务器的URL,如果没有就用传进来的URL

        mRestMsg = getRestMsg();

        if (mRestMsg == null || mRestMsg == "") {

            mRestMsg = intent.getExtras().getString("url");

        }

        setRestMsg(mRestMsg);

        mThread = new Thread(this);

        mThread.start();

        count = 0;

        super.onStart(intent, startId);

    }

    public String getRestMsg() {

        SharedPreferences prefer = getSharedPreferences("settings.data", Context.MODE_PRIVATE);

        return prefer.getString(KEY_REST_MSG, "");

    }

    public void setRestMsg(String restMsg) {

        SharedPreferences prefer = getSharedPreferences("settings.data", Context.MODE_PRIVATE);

        SharedPreferences.Editoreditor = prefer.edit();

       editor.putString(KEY_REST_MSG, restMsg);

        editor.commit();

    }

}

        最后别忘了注册Server和GET_TASKS。

2.12 Native进程拉起

思路:开启native子进程,定时发intent。

保活强度:单杀可以杀死,force close5.0以上无效,5.0以下部分手机无效,第三方软件下无效,且无法保证实时常驻。

实现代码:

    首先开启一个c进程,将需要保活的service名字传递进去。

private static void start(Context context, Class<?> daemonClazzName, int interval) 

{

    String cmd = context.getDir(BIN_DIR_NAME, Context.MODE_PRIVATE)

      .getAbsolutePath() File.separator DAEMON_BIN_NAME;

   /* create the command string */

   StringBuilder cmdBuilder = new StringBuilder();

   cmdBuilder.append(cmd);

   cmdBuilder.append(" -p ");

   cmdBuilder.append(context.getPackageName());

   cmdBuilder.append(" -s ");

   cmdBuilder.append(daemonClazzName.getName());

   cmdBuilder.append(" -t ");

   cmdBuilder.append(interval);

   try {

      Runtime.getRuntime().exec(cmdBuilder.toString()).waitFor();

   } catch (IOException |InterruptedException e) {

      Log.e(TAG, "start daemon error: " e.getMessage());

   }

}

        然后定时给自己主进程发一个intent,如果主进程挂掉了,就可以顺利拉起来保证存活。

while(sig_running)

{

   interval = interval

   select_sleep(interval, 0);

   LOGD(LOG_TAG,"check the service once, interval: %d", interval);

   /* start service */

   start_service(package_name, service_name);

}

        但这只是一个没有主动权的消息轮询器,说是守护其实很勉强,而且,这是要建立在保证c进程不挂的基础上,才能轮询,但是就目前来看,只有5.0以下的非国产机才会有这样的漏洞。也就是说在force close的时候,系统忽略c进程的存在,5.0以上包括5.0的哪怕源生系统也会连同c进程一起清理掉,国产机就更不用说了。就算是这样,在5.0以下的非国产机上,如果安装了获取root权限的360cm的话,也是可以直接清理掉,也就是说会失效。

        native进程守护缺点非常明显,那就是守护是单向的,也就是说只能a保b,b保不了a;a保b也不是在b死了立刻拉起来,要等到了时间才会去拉。那如何解决这个native进程的缺点呢?那就是通过双进程守护,下一篇我将详细讲解如何通过linux层来实现双进程守护。

3 参考链接

Android进程保活全攻略(上)

http://blog.csdn.net/u012124438/article/details/53141113

【腾讯Bugly干货分享】Android进程保活招式大全

https://blog.csdn.net/Tencent_Bugly/article/details/52192423

探讨Android6.0及以上系统APP常驻内存(保活)实现-争宠篇

http://blog.csdn.net/AndrExpert/article/details/75045678

Android进程保活总结

http://blog.csdn.net/superxlcr/article/details/70244803?ref=myread

Android进程保活的一般套路

http://blog.csdn.net/u013263323/article/details/56285475

Android进程管理三部曲[2]-进程的优先级

https://www.jianshu.com/p/0501bc2bbe7c

startForeground如何去除通知栏

https://blog.csdn.net/turkeycock/article/details/50911121

android中Service使用startService

https://blog.csdn.net/zjws23786/article/details/51800929

Android悬浮窗实现 使用WindowManager

https://www.cnblogs.com/mengdd/p/3824782.html

Android Demo: 悬浮窗(支持Android7.0)

https://www.jianshu.com/p/ac63c57d2555

0 人点赞