APP進程保活

進程優(yōu)先級原理

https://developer.android.google.cn/guide/components/processes-and-threads.html?hl=zh-cn

LMK(LowMemoryKiller)
  • 為什么引入LMK错洁?
    進程的啟動分冷啟動和熱啟動酥夭,當用戶退出某一個進程的時候暂幼,并不會真正的將進程退出藐窄,而是將這個進程放到后臺,以便下次啟動的時候可以馬上啟動起來楞慈,這個過程名為熱啟動幔烛,這也是Android的設計理念之一。這個機制會帶來一個問題囊蓝,每個進程都有自己獨立的內存地址空間饿悬,隨著應用打開數(shù)量的增多,系統(tǒng)已使用的內存越來越大,就很有可能導致系統(tǒng)內存不足聚霜。為了解決這個問題狡恬,系統(tǒng)引入LowmemoryKiller(簡稱lmk)管理所有進程珠叔,根據一定策略來kill某個進程并釋放占用的內存,保證系統(tǒng)的正常運行
  • LMK基本原理
    所有應用進程都是從zygote孵化出來的弟劲,記錄在AMS中mLruProcesses列表中祷安,由AMS進行統(tǒng)一管理,AMS中會根據進程的狀態(tài)更新進程對應的oom_adj值兔乞,這個值會通過文件傳遞到kernel中去汇鞭,kernel有個低內存回收機制,在內存達到一定閥值時會觸發(fā)清理oom_adj值高的進程騰出更多的內存空間
  • LMK殺進程標準

minfree(存放6個數(shù)值庸追,單位內存頁面數(shù)(一個頁面4kb))

adb shell
su
cat /sys/module/lowmemorykiller/parameters/minfree

adj

cat /sys/module/lowmemorykiller/parameters/adj

查看進程的adj值

/proc/<pid>/oom_adj
/proc/<pid>/oom_score_adj

內存閾值在不同的手機上不一樣霍骄,一旦低于該值,Android便開始按順序關閉進程. 因此Android開始結束優(yōu)先級最低的空進程,即當可用內存小于180MB(46080)

常用的钡荩活方式
  1. Activity提權
  2. Service提權
  3. 廣播拉活
  4. “全家桶”拉活
  5. Service機制(Sticky)拉活
  6. 賬戶同步拉活
  7. JobScheduler拉活
  8. 推送拉活
  9. Native拉活(NDK)
  10. 雙進程守護(JobScheduler和兩個Service結合用)
Activity提權
  • 原理
    監(jiān)控手機鎖屏解鎖事件读整,在屏幕鎖屏時啟動1個像素透明的 Activity,在用戶解鎖時將 Activity 銷毀掉咱娶,從而達到提高進程優(yōu)先級的作用米间。
  • 代碼實現(xiàn)
  1. 創(chuàng)建1個像素的Activity KeepActivity
Window window = getWindow();
window.setGravity(Gravity.START | Gravity.TOP);
WindowManager.LayoutParams attributes = window.getAttributes();
attributes.width = 1;
attributes.height = 1;
attributes.x = 0;
attributes.y = 0;
window.setAttributes(attributes);

manifest注冊
styles添加主題樣式

  1. 創(chuàng)建廣播接收者KeepReceiver
if (TextUtils.equals(action, Intent.ACTION_SCREEN_OFF)) {
    //滅屏 開啟1px activity
    KeepManager.getInstance().startKeep(context);
} else if (TextUtils.equals(action, Intent.ACTION_SCREEN_ON)) {
    //亮屏 關閉
    KeepManager.getInstance().finishKeep();
}
  1. 創(chuàng)建廣播注冊管理單例類KeepManager
    注冊/反注冊廣播
    啟動/關閉keepActivity
    設置keepActivity弱引用
Service提權
廣播拉活
  • 在發(fā)生特定系統(tǒng)事件時豺总,系統(tǒng)會發(fā)出廣播车伞,通過在 AndroidManifest 中靜態(tài)注冊對應的廣播監(jiān)聽器择懂,即可在發(fā)生響應事件時拉活喻喳。但是從android 7.0開始,對廣播進行了限制困曙,而且在8.0更加嚴格
  • 可靜態(tài)注冊廣播列表https://developer.android.google.cn/guide/components/broadcast-exceptions.html
“全家桶”拉活
  • 有多個app在用戶設備上安裝表伦,只要開啟其中一個就可以將其他的app也拉活。比如手機里裝了手Q慷丽、QQ空間蹦哼、興趣部落等等,那么打開任意一個app后要糊,其他的app也都會被喚醒纲熏。
Service機制(Sticky)拉活
  • 將 Service 設置為 START_STICKY,利用系統(tǒng)機制在 Service 掛掉后自動拉活
  1. START_STICKY:
    “粘性”锄俄。如果service進程被kill掉局劲,保留service的狀態(tài)為開始狀態(tài),但不保留遞送的intent對象奶赠。隨后系統(tǒng)會嘗試重新創(chuàng)建service鱼填,由于服務狀態(tài)為開始狀態(tài),所以創(chuàng)建服務后一定會調用onStartCommand(Intent,int,int)方法毅戈。如果在此期間沒有任何啟動命令被傳遞到service苹丸,那么參數(shù)Intent將為null愤惰。
  2. START_NOT_STICKY:
    “非粘性的”。使用這個返回值時赘理,如果在執(zhí)行完onStartCommand后宦言,服務被異常kill掉,系統(tǒng)不會自動重啟該服務商模。
  3. START_REDELIVER_INTENT:
    重傳Intent蜡励。使用這個返回值時,如果在執(zhí)行完onStartCommand后阻桅,服務被異常kill掉凉倚,系統(tǒng)會自動重啟該服務,并將Intent的值傳入嫂沉。
  4. START_STICKY_COMPATIBILITY:
    START_STICKY的兼容版本稽寒,但不保證服務被kill后一定能重啟。
  • 只要 targetSdkVersion 不小于5趟章,就默認是 START_STICKY杏糙。
    但是某些ROM 系統(tǒng)不會拉活。并且經過測試蚓土,Service 第一次被異常殺死后很快被重啟宏侍,第二次會比第一次慢,第三次又會比前一次慢蜀漆,一旦在短時間內 Service 被殺死4-5次谅河,則系統(tǒng)不再拉起。
賬戶同步拉活
  • 手機系統(tǒng)設置里會有“帳戶”一項功能确丢,任何第三方APP都可以通過此功能將數(shù)據在一定時間內同步到服務器中去绷耍。系統(tǒng)在將APP帳戶同步時,會將未啟動的APP進程拉活https://github.com/googlesamples/android-BasicSyncAdapter.比較繁瑣鲜侥,效果還不一定好褂始,這里就不研究了.
JobScheduler拉活(為后續(xù)雙進程守護做準備)
  • JobScheduler允許在特定狀態(tài)與特定時間間隔周期執(zhí)行任務∶韬可以利用它的這個特點完成逼槊纾活的功能,效果即開啟一個定時器,與普通定時器不同的是其調度由系統(tǒng)完成舀寓。
  • 創(chuàng)建一個JobService
    注意setPeriodic方法
    在7.0以上如果設置小于15min不起作用胆数,可以使用setMinimumLatency設置延時啟動,并且輪詢
推送拉活
  • 根據終端不同基公,在小米手機(包括 MIUI)接入小米推送幅慌、華為手機接入華為推送。
Native拉活(NDK)
  • Native fork子進程用于觀察當前app主進程的存亡狀態(tài)轰豆。對于5.0以上成功率極低胰伍。
雙進程守護(最后一個當然是重點啦)
  • 兩個進程共同運行齿诞,如果有其中一個進程被殺,那么另外一個進程就會將被殺的進程重新拉起骂租,結合JobScheduler一起拉活祷杈,這種效率是最高的。會附上核心代碼渗饮。
public class LocalService extends Service {
    private MyBinder myBinder;

    class MyBinder extends IMyAidlInterface.Stub{

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
                               double aDouble, String aString) throws RemoteException {

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

    @Override
    public void onCreate() {
        super.onCreate();
        myBinder = new MyBinder();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel("deamon", "deamon",
                    NotificationManager.IMPORTANCE_LOW);
            NotificationManager manager = (NotificationManager) getSystemService(
                    Context.NOTIFICATION_SERVICE);
            if (manager == null)
                return;
            manager.createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this,
                    "deamon").setAutoCancel(true).setCategory(
                    Notification.CATEGORY_SERVICE).setOngoing(true).setPriority(
                    NotificationManager.IMPORTANCE_LOW).build();
            startForeground(10, notification);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            //如果 18 以上的設備 啟動一個Service startForeground給相同的id
            //然后結束那個Service
            startForeground(10, new Notification());
            startService(new Intent(this, InnnerService.class));
        } else {
            startForeground(10, new Notification());
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        bindService(new Intent(this, RemoteService.class), new MyServiceConnection(),
                BIND_AUTO_CREATE);
        return super.onStartCommand(intent, flags, startId);
    }

    private class MyServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            //回調

        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            //
            startService(new Intent(LocalService.this, RemoteService.class));
            bindService(new Intent(LocalService.this, RemoteService.class), new MyServiceConnection(),
                    BIND_AUTO_CREATE);
        }
    }

    public static class InnnerService extends Service {

        @Override
        public void onCreate() {
            super.onCreate();
            startForeground(10, new Notification());
            stopSelf();
        }

        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    }
}
public class RemoteService extends Service {
    private MyBinder myBinder;

    class MyBinder extends IMyAidlInterface.Stub {

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
                               double aDouble, String aString) throws RemoteException {

        }
    }

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

    @Override
    public void onCreate() {
        super.onCreate();
        myBinder = new MyBinder();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel("deamon", "deamon",
                    NotificationManager.IMPORTANCE_LOW);
            NotificationManager manager = (NotificationManager) getSystemService(
                    Context.NOTIFICATION_SERVICE);
            if (manager == null)
                return;
            manager.createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this,
                    "deamon").setAutoCancel(true).setCategory(
                    Notification.CATEGORY_SERVICE).setOngoing(true).setPriority(
                    NotificationManager.IMPORTANCE_LOW).build();
            startForeground(10, notification);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            //如果 18 以上的設備 啟動一個Service startForeground給相同的id
            //然后結束那個Service
            startForeground(10, new Notification());
            startService(new Intent(this, InnnerService.class));
        } else {
            startForeground(10, new Notification());
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        bindService(new Intent(this, LocalService.class), new MyServiceConnection(),
                BIND_AUTO_CREATE);
        return super.onStartCommand(intent, flags, startId);
    }

    private class MyServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {

        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            startService(new Intent(RemoteService.this, LocalService.class));
            bindService(new Intent(RemoteService.this, LocalService.class),
                    new MyServiceConnection(), BIND_AUTO_CREATE);
        }
    }

    public static class InnnerService extends Service {

        @Override
        public void onCreate() {
            super.onCreate();
            startForeground(10, new Notification());
            stopSelf();
        }

        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    }
}
@SuppressLint("NewApi")
public class MyJobService extends JobService {
    private static final String TAG = "MyJobService";

    public static void startJob(Context context) {
        JobScheduler jobScheduler = (JobScheduler) context.getSystemService(
                Context.JOB_SCHEDULER_SERVICE);
        JobInfo.Builder builder = new JobInfo.Builder(10,
                new ComponentName(context.getPackageName(),
                        MyJobService.class.getName())).setPersisted(true);
        /**
         * I was having this problem and after review some blogs and the official documentation,
         * I realised that JobScheduler is having difference behavior on Android N(24 and 25).
         * JobScheduler works with a minimum periodic of 15 mins.
         *
         */
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            //7.0以上延遲1s執(zhí)行
            builder.setMinimumLatency(1000);
        } else {
            //每隔1s執(zhí)行一次job
            builder.setPeriodic(1000);
        }
        jobScheduler.schedule(builder.build());
    }

    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        Log.e(TAG, "開啟job");
        //7.0以上輪詢
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            startJob(this);
        }
        //判斷服務是否在運行
        boolean isLocalServiceRun = isServiceRunning(this, LocalService.class.getName());
        boolean isRemoteServiceRun = isServiceRunning(this, RemoteService.class.getName());
        if (!isLocalServiceRun || !isRemoteServiceRun) {

            startService(new Intent(this, LocalService.class));
            startService(new Intent(this, RemoteService.class));
        }
        return false;
    }

    private boolean isServiceRunning(Context context, String serviceName) {
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningServiceInfo> runningServices = am.getRunningServices(10);
        for (ActivityManager.RunningServiceInfo runningService : runningServices) {
            if (TextUtils.equals(runningService.service.getClassName(), serviceName)) {
                return true;
            }
        }

        return false;
    }

    @Override
    public boolean onStopJob(JobParameters jobParameters) {
        return false;
    }
}

總結:目前沒有說百分之百保證能拉活的一些方法但汞,除非你叫廠商給你開白名單。你不是大廠的APP并且有幾千萬用戶的沒這個必要互站,一般來說你給不起廠商這個錢私蕾。以上說的這些拉活其實是些流氓軟件常常用到的,有風險胡桃。并且慎用踩叭,這些APP裝在手機上面雖然功能實現(xiàn)了,但是可能會造成內存不足(贝湟龋活了就釋放不了了嘛,根據需求)容贝。
應用場景:
比如你的APP對于用戶地理位置很敏感,需要5分鐘上傳一次給服務器記錄每個用戶的地理位置之景,按照普通的做法沒苯锔唬活的話那肯定實現(xiàn)不了嘛,用這種方式是可以的锻狗。
自身經歷:
幾年前因為業(yè)務需要满力,跟著我?guī)煾杆麄児咀隽艘粋€短信扣費的功能的,也就是傳說中的SP吧屋谭,屬于打擦邊球的脚囊,也算是屬于和中國xx合作龟糕。我用的廣播的形式桐磁,只要用戶一啟動APP,就自動獲取手機信息讲岁,包括SIM卡我擂,上傳到服務器,然后服務器根據這些信息直接去扣費缓艳,或者服務器給客戶端發(fā)送什么指令校摩,要用戶根據指令發(fā)送短信(目前也可以實現(xiàn)),如果是在2012年左右阶淘,這項業(yè)務是非常非常賺錢的(可惜那時候我還沒學安卓).如果是4.4手機系統(tǒng)的話衙吩,收到扣費信息都可以屏蔽掉,現(xiàn)在都是高版本的手機一般來說屏蔽不了短信了溪窒。那時候各項機制都不是特別健全坤塞。所以很多這種冯勉。所以,少去不正經的網站下載小游戲摹芙,一不小心灼狰,你口袋里面20塊可能就沒了。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末浮禾,一起剝皮案震驚了整個濱河市交胚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盈电,老刑警劉巖蝴簇,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異匆帚,居然都是意外死亡军熏,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門卷扮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來荡澎,“玉大人,你說我怎么就攤上這事晤锹∧︶#” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵鞭铆,是天一觀的道長或衡。 經常有香客問我,道長车遂,這世上最難降的妖魔是什么封断? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮舶担,結果婚禮上坡疼,老公的妹妹穿的比我還像新娘。我一直安慰自己衣陶,他們只是感情好柄瑰,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著剪况,像睡著了一般教沾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上译断,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天授翻,我揣著相機與錄音,去河邊找鬼。 笑死堪唐,一個胖子當著我的面吹牛隆箩,可吹牛的內容都是我干的。 我是一名探鬼主播羔杨,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼捌臊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了兜材?” 一聲冷哼從身側響起理澎,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎曙寡,沒想到半個月后糠爬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡举庶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年执隧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片户侥。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡镀琉,死狀恐怖,靈堂內的尸體忽然破棺而出蕊唐,到底是詐尸還是另有隱情屋摔,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布替梨,位于F島的核電站钓试,受9級特大地震影響,放射性物質發(fā)生泄漏副瀑。R本人自食惡果不足惜弓熏,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望糠睡。 院中可真熱鬧挽鞠,春花似錦、人聲如沸铜幽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽除抛。三九已至,卻和暖如春母截,著一層夾襖步出監(jiān)牢的瞬間到忽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留喘漏,地道東北人护蝶。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像翩迈,于是被迫代替她去往敵國和親持灰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內容