Android進(jìn)程間的通信 - 耍流氓的方式贝航校活Service

1. 概述


上一期我們已經(jīng)閱讀了源碼Android進(jìn)程間的通信 - IPC(機(jī)制)Binder的原理和源碼閱讀,這一期我們就得用到它了泣港。記得前幾年在公司做購物商城,有一個倒計時的功能价匠,上頭非得要實現(xiàn)這個功能当纱,當(dāng)時的想法就是引咎辭職,因為APP應(yīng)用一退出后臺啟動的Service根本不管用直接被殺掉了踩窖,前幾年的事今天我們就來拯救拯救坡氯。
  聲明:
  1.文章綜合了很多其他的一些資料,所以非常感謝某些哥們的分享精神洋腮;
  2.不能夠保證所有的機(jī)型都適用箫柳,因為我們要面對的是各大Rom廠商;
  3.不想帶壞大家啥供,我們應(yīng)該把更多的時間放在內(nèi)存管理和性能優(yōu)化上面悯恍。

效果演示.gif

所有分享大綱:2017Android進(jìn)階之路與你同行

視頻講解地址:http://pan.baidu.com/s/1pLrvFj9

2. 論Service為什么會被殺死


Service被強(qiáng)一般通常是三個方面,第一種現(xiàn)象是當(dāng)手機(jī)內(nèi)存不足伙狐,你的應(yīng)用或者Service在后臺這個時候你精力或者營養(yǎng)更不上可能就被強(qiáng)了涮毫;第二種現(xiàn)象是用了第三方的一些管理軟件比如流氓360,騰訊管家去清理進(jìn)程他們也會對你下手贷屎;第三種現(xiàn)象就是各大Rom廠商罢防,在你退出應(yīng)用的時候會做一些清理工作。

2.1 進(jìn)程的優(yōu)先級:

google的官網(wǎng)給進(jìn)程劃分了等級唉侄,并且明確告訴你哥們我不會亂來咒吐,回收進(jìn)程的時候有自己套路。擔(dān)心有些哥們不想翻墻這里我就Copy一下属划。
  Android 系統(tǒng)將盡量長時間地保持應(yīng)用進(jìn)程恬叹,但為了新建進(jìn)程或運行更重要的進(jìn)程,最終需要移除舊進(jìn)程來回收內(nèi)存同眯。 為了確定保留或終止哪些進(jìn)程妄呕,系統(tǒng)會根據(jù)進(jìn)程中正在運行的組件以及這些組件的狀態(tài),將每個進(jìn)程放入“重要性層次結(jié)構(gòu)”中嗽测。 必要時绪励,系統(tǒng)會首先消除重要性最低的進(jìn)程肿孵,然后是重要性略遜的進(jìn)程,依此類推疏魏,以回收系統(tǒng)資源停做。

重要性層次結(jié)構(gòu)一共有 5 級。以下列表按照重要程度列出了各類進(jìn)程(第一個進(jìn)程最重要大莫,將是最后一個被終止的進(jìn)程):

1.前臺進(jìn)程

用戶當(dāng)前操作所必需的進(jìn)程蛉腌。如果一個進(jìn)程滿足以下任一條件,即視為前臺進(jìn)程:

  • 托管用戶正在交互的Activity已調(diào)用Activity的onResume()方法
  • 托管某個Service只厘,后者綁定到用戶正在交互的 Activity
  • 托管正在“前臺”運行的Service服務(wù)已調(diào)用startForeground()
  • 托管正執(zhí)行一個生命周期回調(diào)的Service (onCreate() 烙丛、onStart()或onDestroy())
  • 托管正執(zhí)行其onReceive()方法的BroadcastReceiver

通常,在任意給定時間前臺進(jìn)程都為數(shù)不多羔味。只有在內(nèi)存不足以支持它們同時繼續(xù)運行這一萬不得已的情況下河咽,系統(tǒng)才會終止它們。 此時赋元,設(shè)備往往已達(dá)到內(nèi)存分頁狀態(tài)忘蟹,因此需要終止一些前臺進(jìn)程來確保用戶界面正常響應(yīng)。

2.可見進(jìn)程

沒有任何前臺組件搁凸、但仍會影響用戶在屏幕上所見內(nèi)容的進(jìn)程媚值。 如果一個進(jìn)程滿足以下任一條件,即視為可見進(jìn)程:

  • 托管不在前臺护糖、但仍對用戶可見的Activity已調(diào)用其onPause()方法褥芒。例如,如果前臺 Activity 啟動了一個對話框嫡良,允許在其后顯示上一 Activity喂很,則有可能會發(fā)生這種情況。
  • 托管綁定到可見(或前臺)Activity 的Service皆刺。

可見進(jìn)程被視為是極其重要的進(jìn)程少辣,除非為了維持所有前臺進(jìn)程同時運行而必須終止,否則系統(tǒng)不會終止這些進(jìn)程羡蛾。

3.服務(wù)進(jìn)程

正在運行已使用startService()方法啟動的服務(wù)且不屬于上述兩個更高類別進(jìn)程的進(jìn)程漓帅。盡管服務(wù)進(jìn)程與用戶所見內(nèi)容沒有直接關(guān)聯(lián),但是它們通常在執(zhí)行一些用戶關(guān)心的操作(例如痴怨,在后臺播放音樂或從網(wǎng)絡(luò)下載數(shù)據(jù))忙干。因此,除非內(nèi)存不足以維持所有前臺進(jìn)程和可見進(jìn)程同時運行浪藻,否則系統(tǒng)會讓服務(wù)進(jìn)程保持運行狀態(tài)捐迫。

4.后臺進(jìn)程

包含目前對用戶不可見的 Activity 的進(jìn)程已調(diào)用 Activity 的onStop()方法。這些進(jìn)程對用戶體驗沒有直接影響爱葵,系統(tǒng)可能隨時終止它們施戴,以回收內(nèi)存供前臺進(jìn)程反浓、可見進(jìn)程或服務(wù)進(jìn)程使用。 通常會有很多后臺進(jìn)程在運行赞哗,因此它們會保存在 LRU (最近最少使用)列表中雷则,以確保包含用戶最近查看的 Activity 的進(jìn)程最后一個被終止。如果某個 Activity 正確實現(xiàn)了生命周期方法肪笋,并保存了其當(dāng)前狀態(tài)月劈,則終止其進(jìn)程不會對用戶體驗產(chǎn)生明顯影響,因為當(dāng)用戶導(dǎo)航回該 Activity 時藤乙,Activity 會恢復(fù)其所有可見狀態(tài)猜揪。 有關(guān)保存和恢復(fù)狀態(tài)的信息,請參閱Activity文檔坛梁。

5.空進(jìn)程

不含任何活動應(yīng)用組件的進(jìn)程而姐。保留這種進(jìn)程的的唯一目的是用作緩存,以縮短下次在其中運行組件所需的啟動時間罚勾。 為使總體系統(tǒng)資源在進(jìn)程緩存和底層內(nèi)核緩存之間保持平衡,系統(tǒng)往往會終止這些進(jìn)程吭狡。

根據(jù)進(jìn)程中當(dāng)前活動組件的重要程度尖殃,Android 會將進(jìn)程評定為它可能達(dá)到的最高級別。例如划煮,如果某進(jìn)程托管著服務(wù)和可見 Activity送丰,則會將此進(jìn)程評定為可見進(jìn)程,而不是服務(wù)進(jìn)程弛秋。
  此外器躏,一個進(jìn)程的級別可能會因其他進(jìn)程對它的依賴而有所提高,即服務(wù)于另一進(jìn)程的進(jìn)程其級別永遠(yuǎn)不會低于其所服務(wù)的進(jìn)程蟹略。 例如登失,如果進(jìn)程 A 中的內(nèi)容提供程序為進(jìn)程 B 中的客戶端提供服務(wù),或者如果進(jìn)程 A 中的服務(wù)綁定到進(jìn)程 B 中的組件挖炬,則進(jìn)程 A 始終被視為至少與進(jìn)程 B 同樣重要揽浙。
  由于運行服務(wù)的進(jìn)程其級別高于托管后臺 Activity 的進(jìn)程,因此啟動長時間運行操作的 Activity 最好為該操作啟動服務(wù)意敛,而不是簡單地創(chuàng)建工作線程馅巷,當(dāng)操作有可能比 Activity 更加持久時尤要如此。例如草姻,正在將圖片上傳到網(wǎng)站的 Activity 應(yīng)該啟動服務(wù)來執(zhí)行上傳钓猬,這樣一來,即使用戶退出 Activity撩独,仍可在后臺繼續(xù)執(zhí)行上傳操作敞曹。使用服務(wù)可以保證账月,無論 Activity 發(fā)生什么情況,該操作至少具備“服務(wù)進(jìn)程”優(yōu)先級异雁。 同理捶障,廣播接收器也應(yīng)使用服務(wù),而不是簡單地將耗時冗長的操作放入線程中纲刀。

以上是google官網(wǎng)的進(jìn)程的優(yōu)先級劃分项炼,就是說越是前面越不容易被殺,越是后面越不容易被殺示绊,那么5是最容易被殺的锭部,下面我們來看一看殺進(jìn)程回收內(nèi)存的機(jī)制Low Memory Killer 到底是怎么對我們下手的:

2.2 LowmemoryKiller的工作機(jī)制:

LowmemoryKiller會在內(nèi)存不足的時候掃描所有的用戶進(jìn)程,找到不是太重要的進(jìn)程殺死面褐,至于LowmemoryKiller殺進(jìn)程夠不夠狠拌禾,要看當(dāng)前的內(nèi)存使用情況,內(nèi)存越少展哭,下手越狠湃窍。在內(nèi)核中,lowmemorykiller.c定義了幾種內(nèi)存回收等級如下:

static short lowmem_adj[6] = {
    0,
    1,
    6,
    12,
};
static int lowmem_adj_size = 4;

static int lowmem_minfree[6] = {
    3 * 512,    
    2 * 1024,    
    4 * 1024,   
    16 * 1024,   
};
static int lowmem_minfree_size = 4;

lowmem_adj中各項數(shù)值代表閾值的警戒級數(shù)匪傍,lowmem_minfree代表對應(yīng)級數(shù)的剩余內(nèi)存您市,兩者一一對應(yīng),比如當(dāng)系統(tǒng)的可用內(nèi)存小于6MB時役衡,警戒級數(shù)為0茵休;當(dāng)系統(tǒng)可用內(nèi)存小于8M而大于6M時,警戒級數(shù)為1手蝎;當(dāng)可用內(nèi)存小于64M大于16MB時榕莺,警戒級數(shù)為12。LowmemoryKiller就是根據(jù)當(dāng)前系統(tǒng)的可用內(nèi)存多少來獲取當(dāng)前的警戒級數(shù)棵介,如果進(jìn)程的oom_adj大于警戒級數(shù)并且占內(nèi)存最大钉鸯,將會被優(yōu)先殺死。omm_adj越小邮辽,代表進(jìn)程越重要亏拉。一些前臺的進(jìn)程,oom_adj會比較小逆巍,而后臺的服務(wù)及塘,omm_adj會比較大,所以當(dāng)內(nèi)存不足的時候锐极,Lowmemorykiller先殺掉的是后臺服務(wù)而不是前臺的進(jìn)程笙僚。對于LowmemoryKiller的殺死,這里有一句話很重要灵再,就是: 具有相同omm_adj的進(jìn)程肋层,則殺死占用內(nèi)存較多的亿笤,因此,如果我們的APP進(jìn)入后臺栋猖,就盡量釋放不必要的資源净薛,以降低自己被殺的風(fēng)險。簡單看一下實現(xiàn)源碼:

static int lowmem_shrink(int nr_to_scan, gfp_t gfp_mask)
{
    ...        
    <!--獲取free內(nèi)存狀況-->
    int other_free = global_page_state(NR_FREE_PAGES);
    int other_file = global_page_state(NR_FILE_PAGES);
    <!--找到min_adj -->
    for(i = 0; i < array_size; i++) {
        if (other_free < lowmem_minfree[i] &&
            other_file < lowmem_minfree[i]) {
            min_adj = lowmem_adj[i];
            break;
        }
    }
  <!--找到p->oomkilladj>min_adj并且oomkilladj最大蒲拉,內(nèi)存最大的進(jìn)程-->
    for_each_process(p) {
        // 找到第一個大于等于min_adj的肃拜,也就是優(yōu)先級比閾值低的
        if (p->oomkilladj < min_adj || !p->mm)
            continue;
        // 找到tasksize這個是什么呢
        tasksize = get_mm_rss(p->mm);
        if (tasksize <= 0)
            continue;
        if (selected) {
        // 找到優(yōu)先級最低,并且內(nèi)存占用大的
            if (p->oomkilladj < selected->oomkilladj)
                continue;
            if (p->oomkilladj == selected->oomkilladj &&
                tasksize <= selected_tasksize)
                continue;
        }
        selected = p;
        selected_tasksize = tasksize;
        lowmem_print(2, "select %d (%s), adj %d, size %d, to kill\n",
                     p->pid, p->comm, p->oomkilladj, tasksize);
    }
    if(selected != NULL) {...
        force_sig(SIGKILL, selected);
    }
    return rem;
}

那么現(xiàn)在我們就可以針對性的做一些工作了比如:

1.提高進(jìn)程的優(yōu)先級雌团,其實就是減小進(jìn)程的p->oomkilladj(越小越重要)燃领,如啟動Service調(diào)用startForeground()盡量提高進(jìn)程的優(yōu)先級;

2.當(dāng)應(yīng)用推到后臺適當(dāng)釋放資源然后降低APP的內(nèi)存占用量锦援,因為在oom_adj相同的時候猛蔽,會優(yōu)先干掉內(nèi)存消耗大的進(jìn)程;

3.對于要一直在后臺運行的Service我們一定要輕灵寺,不能太風(fēng)騷曼库。

3. 針對各大Rom廠商和清理軟件:


我其實要面對的不是google原生的系統(tǒng),在各大手機(jī)廠商以及各種流氓清理軟件面前我們按套路出牌往往然并卵略板,我們要面對的也是他們毁枯,這也是說往往寫的代碼你會發(fā)現(xiàn)這里行那里不行,真是心中有一萬只動物蚯根。會發(fā)現(xiàn)采用上面的方式根本沒用后众,網(wǎng)上也有很多的解決方案胀糜,如QQ的1像素的Activity颅拦,某些第三方應(yīng)用救救你,NDK方式hook子線程等等等等教藻。我們這里決定采用雙進(jìn)程守護(hù)相互喚醒以及5.0以上的JobSheduler:

public class MessageService extends Service {

    private static final String TAG = "MessageService";

    private MessageServiceConnection mServiceConnection;
    private MessageBind mMessageBind;

    @Override
    public void onCreate() {
        super.onCreate();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    Log.e(TAG, "等待接收消息");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();


        if (mServiceConnection == null) {
            mServiceConnection = new MessageServiceConnection();
        }

        if (mMessageBind == null) {
            mMessageBind = new MessageBind();
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        MessageService.this.bindService(new Intent(MessageService.this, GuardService.class),
                mServiceConnection, Context.BIND_IMPORTANT);
        return Service.START_STICKY;
    }

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

    private class MessageBind extends ProcessConnection.Stub {

        @Override
        public void processConnected() throws RemoteException {

        }
    }

    private class MessageServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 建立連接
            Toast.makeText(MessageService.this, "建立連接", Toast.LENGTH_LONG).show();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // 斷開連接
            Toast.makeText(MessageService.this, "斷開連接", Toast.LENGTH_LONG).show();
            Intent guardIntent = new Intent(MessageService.this, GuardService.class);
            // 發(fā)現(xiàn)斷開我就從新啟動和綁定
            startService(guardIntent);
            MessageService.this.bindService(guardIntent,
                    mServiceConnection, Context.BIND_IMPORTANT);
        }
    }
}

雙進(jìn)程守護(hù)和喚醒在5.0以下還是很爽的距帅,至少能保證各種流氓的清理軟件不能夠清理掉我們的Service,但是在5.0以后就不管用了括堤,尤其實在小米碌秸、魅族這些手機(jī)上面。不過柳暗花明又一村悄窃,我們可以利用JobSheduler讥电。

/**
 * Created by Darren on 2017/3/25.
 * Email: 240336124@qq.com
 * Description:
 */
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class JobAwakenService extends JobService{

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        JobInfo.Builder builder = new JobInfo.Builder(1,new ComponentName(this,JobAwakenService.class));
        builder.setPeriodic(500);
        JobInfo jobInfo = builder.build();
        JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        jobScheduler.schedule(jobInfo);
        return START_STICKY;
    }

    @Override
    public boolean onStartJob(JobParameters params) {
        Log.e("TAG","onStartJob");

        // boolean isGuardAlive = isServiceWork(this,GuardService.class.getName());
        boolean isMessageAlive = isServiceWork(this,MessageService.class.getName());

        if(!isMessageAlive){
            // startService(new Intent(this,GuardService.class));
            startService(new Intent(this,MessageService.class));
        }

        return false;
    }

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

    /**
     * 判斷某個服務(wù)是否正在運行的方法
     *
     * @param mContext
     * @param serviceName
     *            是包名+服務(wù)的類名(例如:net.loonggg.testbackstage.TestService)
     * @return true代表正在運行,false代表服務(wù)沒有正在運行
     */
    public boolean isServiceWork(Context mContext, String serviceName) {
        boolean isWork = false;
        ActivityManager myAM = (ActivityManager) mContext
                .getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningServiceInfo> myList = myAM.getRunningServices(100);
        if (myList.size() <= 0) {
            return false;
        }
        for (int i = 0; i < myList.size(); i++) {
            String mName = myList.get(i).service.getClassName().toString();
            if (mName.equals(serviceName)) {
                isWork = true;
                break;
            }
        }
        return isWork;
    }
}

所有分享大綱:2017Android進(jìn)階之路與你同行

視頻講解地址:http://pan.baidu.com/s/1pLrvFj9

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末轧抗,一起剝皮案震驚了整個濱河市恩敌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌横媚,老刑警劉巖纠炮,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件月趟,死亡現(xiàn)場離奇詭異,居然都是意外死亡恢口,警方通過查閱死者的電腦和手機(jī)孝宗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耕肩,“玉大人因妇,你說我怎么就攤上這事】戳疲” “怎么了沙峻?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長两芳。 經(jīng)常有香客問我摔寨,道長,這世上最難降的妖魔是什么怖辆? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任是复,我火速辦了婚禮,結(jié)果婚禮上竖螃,老公的妹妹穿的比我還像新娘淑廊。我一直安慰自己,他們只是感情好特咆,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布季惩。 她就那樣靜靜地躺著,像睡著了一般腻格。 火紅的嫁衣襯著肌膚如雪画拾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天菜职,我揣著相機(jī)與錄音青抛,去河邊找鬼。 笑死酬核,一個胖子當(dāng)著我的面吹牛蜜另,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嫡意,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼举瑰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蔬螟?” 一聲冷哼從身側(cè)響起此迅,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后邮屁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體整袁,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年佑吝,在試婚紗的時候發(fā)現(xiàn)自己被綠了坐昙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡芋忿,死狀恐怖炸客,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情戈钢,我是刑警寧澤痹仙,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站殉了,受9級特大地震影響开仰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜薪铜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一众弓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧隔箍,春花似錦谓娃、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至俯艰,卻和暖如春捡遍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蟆炊。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工稽莉, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留瀑志,地道東北人涩搓。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像劈猪,于是被迫代替她去往敵國和親昧甘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

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