安卓進程保活解決方案

公司產品最近提出惡劣的需求,讓我們的app像微信一樣永遠不被殺掉,隨時給用戶最友好的體驗,當時我想到的是根據手機殼變色的產品經理,心中翻滾着MMP,當然功能還是要做的;

1,除了微信這樣的白名單大佬,沒有app可以青春永駐
2,保活只能是使用一些歪門邪道來延長進程的持續時間
3,如果在原生的安卓系統去做,可能會好很多,但介於國內環境,只能盡力而爲,有些機型可謂聖鬥士一樣
4,網上保活方案衆多,不可能全部用上,分析使用
5,所謂保活是延長我們核心進程的壽命,比如推送,或者IM,推送其實各廠家做了很多的保活機制,多數爲共享長鏈接,小米這種系統級別的不說,IM也一樣,但我們其實也可以在他們的基礎上想辦法再做一層保活,像個推需要自己集成實現一個service.

研究一下市面上常見的保活方法
1,經典的一像素保活,據說QQ是這門乾的(據說),很流氓,很有效,就是在鎖屏和解鎖的時候創建一個一個像素的Activity,這樣做的目的是提升進程的優先級,不過有部分手機好像把解鎖屏幕的廣播給拿掉了
2,雙進程守護,開倆個進程相互喚起,因爲系統的進程回收機制是一個個回收的,利用這個時間差來相互喚起,當一個進程被磨滅掉,另一個馬上重啓,缺點是現在大部分機型只要一鍵清理就玩完了,不過也沒有更好的辦法,而且8.0之後對這個做了限制,想要在一個後再服務中啓動另一個服務會報錯,可以用startForegroundService方法,但是會有一個通知在通知欄,這就有點不太友好了,不過介於8.0以下手機還有很多,可以考慮
3,native進程(已報廢)
4,JobIntentService,這個好多人不知道,其實就是之前JobService,利用系統的調度去開啓一個服務,不過這個很簡單,比JobService的使用簡單多了,這個東西會在解鎖屏幕,充電啊這一些動作的時候去重啓執行裏面的job,甚至手機重啓,所以用它來總一些事情再覈實不過了,不過它也是會被殺死的.
5,利用賬號同步機制拉活,不過貌似被改了,失效了
6,在後臺播放一個無聲的音頻,看起來很不錯,不過總感覺該方案有點....
7,將service設置爲前臺進程,通2方法一樣,會強制有個通知,可以考慮用innerService發送倆個id一樣的通知,然後結束掉一個(8.0之後無效),該方法很好

一,先創建一個OnePixelService來監聽屏幕的廣播,之後就假設這個OnePixelService是我們的核心進程,我們就保活它

public class OnePixelService extends Service implements ScreenStateListener {

    private ScreenBroadcastReceiver screenBroadcastReceiver;


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

    @Override
    public void onCreate() {
        super.onCreate();
        screenBroadcastReceiver = new ScreenBroadcastReceiver(this);
        //動態註冊廣播
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_SCREEN_ON);
        if (getApplication() == null) return;
        getApplication().registerReceiver(screenBroadcastReceiver, filter);
    }

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

    /**
     * 屏幕打開
     */
    @Override
    public void onScreenOn() {
        //屏幕關閉之後->啓動一個activity一個像素的留在界面上
        if (getApplication() == null) return;
        WeakReference<Activity> mActivityWref = OnePixelLive.getmActivityWref();
        if (mActivityWref != null) {
            Activity activity = mActivityWref.get();
            if (activity != null) {
                activity.finish();
            }
        }
    }

    /**
     * 屏幕關閉
     */
    @Override
    public void onScreenOff() {
        //屏幕關閉之後->啓動一個activity一個像素的留在界面上
        //當app在後臺,或者按下home鍵之後,創建一個activity會慢,可能會在再次解鎖之後創建
        if (getApplication() == null) return;
        //boolean runBackground = BackFogetUtil.isRunBackground(getApplicationContext());
        getApplication().startActivity(new Intent(getApplication(), OnePixelActvity.class));
    }


}

然後創建一像素Activity

public class OnePixelActvity extends Activity {

    public static final String TAG = OnePixelActvity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.e(TAG, "onCreate");
        Window window = getWindow();
        //放在左上角
        window.setGravity(Gravity.START | Gravity.TOP);
        WindowManager.LayoutParams attributes = window.getAttributes();
        //寬高設計爲1個像素
        attributes.width = 1;
        attributes.height = 1;
        //起始座標
        attributes.x = 0;
        attributes.y = 0;
        window.setAttributes(attributes);
        boolean screenon = BackFogetUtil.isScreenon(this);
        if (screenon) {
            finish();
        } else {
            OnePixelLive.setActivity(this);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.e(TAG, "onDestroy");
    }

這裏啓動activity通過Application啓動,所以即使你按了back鍵回退到後臺還是會啓動這個一像素的,但是又個問題就是現在安卓對這種情況啓動界面會有個五秒的延遲限制,然後就會出現剛鎖屏有解鎖的情況下,activity被創建,然後沒有解鎖廣播了,無法銷燬了,卡住了的現象,所以我做了一個判斷如果屏幕真亮着的時候創建activity,那麼馬上把他銷燬掉;

然後很重要的一個地方
1,啓動模式要設置成singleInstance,不然會有問題,當你的app在後臺的時候,你解鎖屏幕,創建銷燬OnePixelActvity的同時會把整個棧的activity彈出來,然會用戶就會莫名其妙的看到你的app自己打開了;
2,一定要把主題設置爲透明,不然在你操作飛速的時候,屏幕總是會閃過一抹五彩斑斕的黑;

 <activity
            android:name=".preserving.ui.OnePixelActvity"
            android:launchMode="singleInstance"
            android:theme="@android:style/Theme.Translucent.NoTitleBar" />

然後效果實現了

08-18 22:27:55.304 10201-10201/com.lxf.processpreserving E/live: ScreenBroadcastReceiver --> ACTION_SCREEN_OFF
08-18 22:27:55.319 10201-10201/com.lxf.processpreserving E/OnePixelActvity: onCreate
08-18 22:27:56.282 10201-10201/com.lxf.processpreserving E/live: ScreenBroadcastReceiver --> ACTION_SCREEN_ON
08-18 22:27:56.285 10201-10201/com.lxf.processpreserving E/OnePixelActvity: onDestroy

一像素這個鎖屏問題實現了,但是沒鎖屏的情況下被幹掉怎們辦,想到可以開啓一個進程用jobservice來進行拉活,因爲他是一個足夠強大的機制,開一個定時任務去檢測OnePixelActvity的存活狀態來進行拉活,雖然它也會掛掉,但它會在各種情況下重啓,而且鎖屏狀態下不會被強制殺掉;但是又一個問題,8.0以上不能後臺拉起服務,8.0下可以直接啓動服務;怎們解決,這裏我用了一種比較噁心的做法,就像上面一像素玩法一樣,當我們要拉起這個服務的時候,啓動一個一像素的activity,然後再啓動服務,然後銷燬activity;這個很好使,完美拉起,但是呢用戶有時候會莫名其妙的感覺到屏幕一卡,我們也成功的甩鍋到手機商和其他應用;不過穩妥的辦法還是在屏幕鎖屏的狀態下進行拉活,不然會出問題的;

public class LiveJobService extends JobIntentService {


    public static String serviceName = "CLASSNAME";
    private String liveService;
    private Timer timer = new Timer();

    private long count;
    private TimerTask timerTask = new TimerTask() {
        @Override
        public void run() {
            count++;
            if (TextUtils.isEmpty(liveService)) return;
            boolean serviceWork = BackFogetUtil.isServiceWork(getApplicationContext(), liveService);
            if (!serviceWork) {
                Log.e("LiveJobService", "服務" + liveService + "被幹掉了" + count);
                count = 0;
                try {
                    Class<Service> serviceClass = (Class<Service>) Class.forName(liveService);
                    //嘗試拉起該服務
                    if (Build.VERSION.SDK_INT >= 26) {
                        getApplication().startActivity(new Intent(getApplication(), JobActvity.class));
                    } else {
                        startService(new Intent(getApplicationContext(), serviceClass));
                    }

                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            } else {
                Log.e("LiveJobService", "服務" + liveService + "正在運行" + count);
            }
        }
    };


    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e("LiveJobService", "進程被殺掉了");
    }

    @Override
    protected void onHandleWork(@NonNull Intent intent) {
        liveService = intent.getStringExtra(serviceName);
        //LiveJobService使用的是隊列的數據結構
        //我們搞一個定時任務來拉活傳過來的服務
        if (timer != null && timerTask != null) {
            timer.schedule(timerTask, 1000, 1000);
        }
    }
}

其實jobservice也就是爲了鎖屏的情況下去拉活,因爲鎖屏之後它不會被幹掉,這個樣子就有多了一份生存的希望,但是還不夠;如果服務掛掉了,用戶也一直沒有鎖屏怎們辦?不考慮通知欄的情況下通過aidl雙進程拉活可以很好的起到作用,但鑑於8.0通知欄這個問題暫時不考慮守護的做法,同時將服務設爲前臺的做法也就不考慮,當然也可以判斷系統8.0以下去加上這個做法;

目前沒有發現8.0以上不顯示通知欄又能拉活的辦法,所以只判斷8.0以下開啓一個進程去守護,8.0等用戶鎖屏之後器拉活(當然也可以流氓點)

總結感覺可行的做法:
1,假入我們要保活一個HttpService
2,HttpService中加入一像素的保活代碼
3,8.0以下將HttpService設置爲前臺進程(基本就不會被殺了)
4,開啓一個jobService去定時拉活HttpService,8.0啓動一像素activity去啓動進程(時機自己決定)
5,jobService也有機率被殺,可以考慮在8.0以下開啓進程守護互相拉活
6,不要花太多時間去挑戰rom系統的意見清理,目前發現只有部分手機可能清理不趕緊,大部分手機手機清理非常徹底
7,8.0以上真心不太好搞,貌似9.0更加不好弄,加入了服務分組,電量優化等一系列內容
8,白名單最可靠
9,優化產品最實在

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章