進程也想長生不死

想要一個進程永遠的活著不被kill掉买决,這個觀點備受駁議督赤。對于開發(fā)來說泻蚊,想要實現(xiàn)的一些需求性雄,保證消息的到達率,這一項的實現(xiàn)是無可厚非诀拭;對于用戶來說病蛉,被不知名的服務(wù)一直生存在我的后臺進程铺然。就像隔壁老王在藏在衣柜里偷偷輸出魄健,是不是很難受沽瘦!有些進程农尖,又不能強制用戶給你加入白名單盛卡,那能怎么辦呢滑沧?只能用一些黑科技了滓技。


但是Android系統(tǒng)中在沒有白名單的情況下做一個任何情況下都不被殺死的應(yīng)用是基本不可能的令漂,只能大幅度提高進程的存活率。強制性的流氓手段外潜,有些不道德处窥。更何況我也不會QAQ~ 我也只不過看了大牛們的技術(shù)博客,自己寫幾個demo玄组,演示記錄一下而已滔驾。

進程的級別

首先谒麦,要了解進程的級別。Android的應(yīng)用中哆致,進程可分為:

  1. 前臺進程绕德,在手機頁面可被看見的。如:正在被onResume()的Activity摊阀;綁定該Activity的Service耻蛇;正執(zhí)行其 onReceive()的BroadcastReceiver胞此。
  2. 可見進程臣咖,雖然可被看見,但是在執(zhí)行onPause()方法中Activity漱牵。如:彈出dialog被遮擋的Activity夺蛇;綁定該Activity的Service也級別也隨之降低。
  3. 服務(wù)進程酣胀,獨立于應(yīng)用的進程或者是私有進程刁赦,且正在運行 startService() 方法啟動的服務(wù)。
  4. 后臺進程闻镶,不可見Activity甚脉,執(zhí)行了onStop()方法。
  5. 空進程铆农,不包含任何活躍的應(yīng)用組件的進程


    圖1.jpg

    (盜用一下網(wǎng)上的圖)
    對應(yīng)的不同級別的進程牺氨,系統(tǒng)都有一個oom_adj的值來標識,不同進程的級別高低顿涣。


    圖2.jpg

    綠色標識不容易被系統(tǒng)回收的進程波闹。系統(tǒng)當處于內(nèi)存緊缺時候,需要通過Lowmemorykiller 涛碑,kill掉一些對系統(tǒng)來說無關(guān)緊要的進程精堕。斬殺的規(guī)則就是,根據(jù)oom_adj大小蒲障。從最大的開始歹篓,當兩個oom_adj大小一樣時,先kill占用內(nèi)存大的那個揉阎。

ADB Shell使用

哇庄撮,第一次使用adb命令,感覺low爆了毙籽。要看進程活著還是死的洞斯,總得有個地方可以查看的嘛。原來Android Studio 命令框可以這么用的坑赡。用真機調(diào)試時候烙如,adb shell 進入的并不是root么抗,對應(yīng)的角標“$”,其權(quán)限也只能看到對應(yīng)進程的列表亚铁,只有手機root之后或者用模擬器才能看到進程的詳細信息蝇刀。角標也是“#”

圖3.jpg

可以通過 cat /proc/進程id/oom_adj 來查看。返回了一個值徘溢,對應(yīng)上面那張表吞琐,可直接看出對應(yīng)的進程所處在的級別。我們要做的是然爆,對我們需要的進程站粟,希望在其被置為不可見時候,其進程級別不會被降低施蜜,或者說不被降很多卒蘸。所以也就有了一下的“黑科技”雌隅。

一個像素的Activity

這個方法翻默,好像在網(wǎng)上已經(jīng)人盡皆知了的樣子。實現(xiàn)原理恰起,也就是監(jiān)聽手機屏幕的開啟和關(guān)閉修械,來打開和關(guān)閉這個1像素的Activity。這個需求只能做到检盼,屏幕熄黑后肯污,進程的級別不會被降低。

//屏幕點亮關(guān)閉監(jiān)聽
interface ScreenStateListener {
        void onScreenOn();
        void onScreenOff();
    }
    /**
     * screen狀態(tài)廣播接收者
     */
    private class ScreenBroadcastReceiver extends BroadcastReceiver {
        private String action = null;
        @Override
        public void onReceive(Context context, Intent intent) {
            action = intent.getAction();
            if (Intent.ACTION_SCREEN_ON.equals(action)) { // 開屏
                mListener.onScreenOn();
            } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { // 鎖屏
                mListener.onScreenOff();
            }
        }
    }

這個是1像素Activity創(chuàng)建的管理類吨枉。因為該Activity頻繁被創(chuàng)建和銷毀蹦渣,所以將其放置弱引用堆棧里,便于內(nèi)存回收貌亭。

public class ScreenManager {

    private Context mContext;

    //弱引用柬唯,LiveActivity在應(yīng)用可視下finish,創(chuàng)建頻繁
    private WeakReference<Activity> mActivityWref;

    public static ScreenManager gDefualt;

    public static ScreenManager getInstance(Context pContext) {
        if (gDefualt == null) {
            gDefualt = new ScreenManager(pContext.getApplicationContext());
        }
        return gDefualt;
    }
    private ScreenManager(Context pContext) {
        this.mContext = pContext;
    }

    public void setActivity(Activity pActivity) {
        mActivityWref = new WeakReference<Activity>(pActivity);
    }

    public void startActivity() {
        LiveActivity.actionToLiveActivity(mContext);
    }

    public void finishActivity() {
        //結(jié)束掉LiveActivity
        if (mActivityWref != null) {
            Activity activity = mActivityWref.get();
            if (activity != null) {
                activity.finish();
            }
        }
    }
}

LiveService 的具體操作圃庭,在應(yīng)用的進程開啟時候同時開啟這個Service锄奢,對屏幕關(guān)閉開啟進行監(jiān)控

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //屏幕關(guān)閉的時候啟動一個1像素的Activity,開屏的時候關(guān)閉Activity
        final ScreenManager screenManager = ScreenManager.getInstance(LiveService.this);
        ScreenBroadcastListener listener = new ScreenBroadcastListener(this);
        listener.registerListener(new ScreenBroadcastListener.ScreenStateListener() {
            @Override
            public void onScreenOn() {
                screenManager.finishActivity();
            }
            @Override
            public void onScreenOff() {
                screenManager.startActivity();
            }
        });
        return START_REDELIVER_INTENT;
    }

主要的原理就熄屏時候剧腻,偷偷的開啟著一個無界面的Activity拘央,來告訴手機我這個進程是活躍著的。在屏幕亮起時候书在,則銷毀這個Activity灰伟,由現(xiàn)有的界面UI取代。但是我發(fā)現(xiàn)當應(yīng)用被置于后臺后儒旬,應(yīng)用的oom_adj直接變?yōu)?(后臺進程)栏账,所以滿足不了進程級別一直保持遏乔。我也很可愛的,在程序進入后臺也開啟這個1像素发笔,我在Application生命周期中盟萨,對這個Activity進行一樣的操作。奈何了讨。捻激。當我進入后臺后,過了一會前计,app又被重新拉起胞谭,顯示在頁面。關(guān)不掉了哈哈~

  @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            }

            @Override
            public void onActivityStarted(Activity activity) {
                final ScreenManager screenManager = ScreenManager.getInstance(this);
                screenManager.finishActivity();

            }
                        
            @Override
            public void onActivityResumed(Activity activity) {

            }

            @Override
            public void onActivityPaused(Activity activity) {

            }

            @Override
            public void onActivityStopped(Activity activity) {
               screenManager.startActivity();                   
            }
            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
            }
            @Override
            public void onActivityDestroyed(Activity activity) {

            }
        });
    }

既然不能滿足男杈,那只能換種方法思路丈屹。。伶棒。旺垒。。

設(shè)置為前臺服務(wù)

據(jù)說微信用的是這個方案肤无,實現(xiàn)原理是利用了Android的前臺漏洞先蒋。對API<180時,調(diào)用startForeground(ID宛渐, new Notification())竞漾,這樣就不就有有圖標顯示。在API>18時,需要調(diào)用一個輔助的Service,綁定同一個ID勇边,然后并且stop 這個輔助Service,這樣通知欄也不會有任何顯示

public class KeepLiveService extends Service {

    public static final int NOTIFICATION_ID=0x11;

    public KeepLiveService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();
         //API 18以下笔时,直接發(fā)送Notification并將其置為前臺
        if (Build.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BEAN_MR2) {
            startForeground(NOTIFICATION_ID, new Notification());
        } else {
            //API 18以上,發(fā)送Notification并將其置為前臺后幔荒,啟動InnerService
            Notification.Builder builder = new Notification.Builder(this);
            builder.setSmallIcon(R.mipmap.ic_launcher);
            startForeground(NOTIFICATION_ID, builder.build());
            startService(new Intent(this, FuZhuService.class));
        }
    }
}
public  class  FuZhuService extends Service{
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
        @Override
        public void onCreate() {
            super.onCreate();
            //發(fā)送與KeepLiveService中ID相同的Notification糊闽,然后將其取消并取消自己的前臺顯示
            Notification.Builder builder = new Notification.Builder(this);
            builder.setSmallIcon(R.mipmap.ic_launcher);
            startForeground(NOTIFICATION_ID, builder.build());
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    stopForeground(true);
                    NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                    manager.cancel(NOTIFICATION_ID);
                    stopSelf();
                }
            },100);
        }
    }

最后當應(yīng)用點擊后退后,退置后臺時候爹梁,該應(yīng)用的進程保持0變?yōu)?的程度右犹,也就保證了進程的存活率


圖3.jpg

系統(tǒng)自帶的服務(wù)

在Service的onStartCommand方法中返回一個整型,系統(tǒng)識別這個整型姚垃,進行判斷念链,如果被kill將會進行說明操作。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    return START_REDELIVER_INTENT;
}
  • START_STICKY
    如果系統(tǒng)在onStartCommand返回后被銷毀,系統(tǒng)將會重新創(chuàng)建服務(wù)并依次調(diào)用onCreate和onStartCommand(注意:根據(jù)測試Android2.3.3以下版本只會調(diào)用onCreate根本不會調(diào)用onStartCommand掂墓,Android4.0可以辦到)谦纱,這種相當于服務(wù)又重新啟動恢復到之前的狀態(tài)了)。

  • START_NOT_STICKY
    如果系統(tǒng)在onStartCommand返回后被銷毀君编,如果返回該值跨嘉,則在執(zhí)行完onStartCommand方法后如果Service被殺掉系統(tǒng)將不會重啟該服務(wù)。

  • START_REDELIVER_INTENT
    START_STICKY的兼容版本吃嘿,不同的是其不保證服務(wù)被殺后一定能重啟祠乃。

使用后好像不怎么靈巧,保留使用兑燥。亮瓷。。降瞳。

總結(jié)

總結(jié)一波嘱支,作為一個有情懷的開發(fā)者,我們都知道每當用戶關(guān)閉一個程序時挣饥,我們的程序就應(yīng)該徹底地死去并釋放其所占用的系統(tǒng)資源除师,這個淺顯的道理不僅適用于我們移動應(yīng)用開發(fā),也適用于任何桌面程序的開發(fā)亮靴。

但是作為開發(fā)者馍盟,我們更多的是面對產(chǎn)品經(jīng)理開發(fā)于置,針對各種市場資源需要茧吊,總會想盡辦法。對于常駐型進程八毯,在使用的時候搓侄,還是得三四而后行。當然特殊需求可以特殊考慮话速,為了用戶體驗更好的是沒話說的讶踪。
這篇文章更多的是站在網(wǎng)上各路大牛們的基礎(chǔ)上閱讀,尚未達到創(chuàng)造的級別泊交。慢慢努力乳讥。自此,感謝網(wǎng)上的大們牛寫的黑科技1 黑科技2

帥帥的你.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末廓俭,一起剝皮案震驚了整個濱河市云石,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌研乒,老刑警劉巖汹忠,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡宽菜,警方通過查閱死者的電腦和手機谣膳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铅乡,“玉大人继谚,你說我怎么就攤上這事≌笮遥” “怎么了犬庇?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長侨嘀。 經(jīng)常有香客問我臭挽,道長,這世上最難降的妖魔是什么咬腕? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任欢峰,我火速辦了婚禮,結(jié)果婚禮上涨共,老公的妹妹穿的比我還像新娘纽帖。我一直安慰自己,他們只是感情好举反,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布懊直。 她就那樣靜靜地躺著,像睡著了一般火鼻。 火紅的嫁衣襯著肌膚如雪室囊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天魁索,我揣著相機與錄音融撞,去河邊找鬼。 笑死粗蔚,一個胖子當著我的面吹牛尝偎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鹏控,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼致扯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了当辐?” 一聲冷哼從身側(cè)響起抖僵,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瀑构,沒想到半個月后裆针,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刨摩,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年世吨,在試婚紗的時候發(fā)現(xiàn)自己被綠了澡刹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡耘婚,死狀恐怖罢浇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沐祷,我是刑警寧澤嚷闭,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站赖临,受9級特大地震影響胞锰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜兢榨,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一嗅榕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吵聪,春花似錦凌那、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至块攒,卻和暖如春励稳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背局蚀。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工麦锯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人琅绅。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像鹅巍,于是被迫代替她去往敵國和親千扶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容