關於Android進程保活

    最近開發了個需要進程不被殺死的功能,很麻煩,我在網上找了很久關於進程常駐、進程保活等等的文章,很多文章寫得都很亂,而且也很雜亂。而且很多都是過時了的方法,有很多都是Android5.0之前的方法。

進程保活就需要先了解一下Android的進程回收機制Low Memory Killer:

    Low Memory Killer基於Linux內核的 OOM Killer機制誕生的進程回收內存的機制。在系統內存不足的情況下,系統開始依據自身的一套進程回收機制來判斷要kill掉哪些進程,以騰出內存來供給需要的app

我看很多文章都列舉了黑灰白的保活方法,在這裏先介紹一下這三種方法:

黑色保活

黑色保活手段主要是指利用不同的app進程廣播來進行互相的喚醒。有的是利用系統的廣播來喚醒他的app,有的是在你介入第三方SDK就會喚醒他們的app進程,如微信sdk會喚醒微信,支付寶sdk會喚醒支付寶。還有類似阿里騰訊等等公司都有自己一個系列的app,那麼如果你打開他們這個系列的app。他們這個系列其他的app也會被喚醒。

這種喚醒方法很消耗手機,安卓手機比蘋果手機用起來卡很大原因就是因爲這一塊互相喚醒導致的,用很多類似安全管家之類的都可以看到這些喚醒啓動,但是一般要有root權限才能關閉喚醒自啓。

白色保活

白色保活手段是指啓動service時會發出一個notification消息在通知欄,用於和service綁定,這樣提高service的優先級,被推遲收回,保證service存活。 
service的onCreate方法裏面添加如下代碼:

 Notification.Builder builder = new Notification.Builder(this);
        builder.setContentInfo("補充內容");
        builder.setContentText("正在運行...");
        builder.setContentTitle("進行標題");
        builder.setSmallIcon(R.mipmap.appicon);
        builder.setTicker("新消息");
        builder.setAutoCancel(true);
        builder.setWhen(System.currentTimeMillis());
        Intent notificationIntent = new Intent(this, TaskDetailActivity.class);


        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
        builder.setContentIntent(pendingIntent);
        Notification notification = null;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
            notification = builder.build();
        }else{
            notification = builder.getNotification();
        }
        //把該service創建爲前臺service
        startForeground(1, notification);

這段代碼就是創建一個notification然後利用startForeground來讓你的服務在前臺運行,要從前臺刪除服務,需要調用stopForeground()方法,這個方法需要一個布爾型參數,指示是否刪除狀態欄通知。這個方法不終止服務。但是,如果你終止了正在運行的前臺服務,那麼通知也會被刪除。

前臺服務是哪些被認爲用戶知道的並且在內存低的時候不允許系統殺死的服務。前臺服務必須給狀態欄提供一個通知,他被放到了“正在進行中(Ongoing)”標題之下,這就意味着直到這個服務被終止或從前臺刪除通知才能被解除。

灰色保活

灰色保活,這種保活手段是應用範圍最廣泛。它是利用系統的漏洞來啓動一個前臺的Service進程,與普通的啓動方式區別在於,它不會在系統通知欄處出現一個Notification,看起來就如同運行着一個後臺Service進程一樣。這樣做帶來的好處就是,用戶無法察覺到你運行着一個前臺進程(因爲看不到Notification),但你的進程優先級又是高於普通後臺進程的。那麼如何利用系統的漏洞呢,大致的實現思路和代碼如下:

public class GrayService extends Service {

    private final static int GRAY_SERVICE_ID = 1001;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (Build.VERSION.SDK_INT < 18) {
            startForeground(GRAY_SERVICE_ID, new Notification());//API < 18 ,此方法能有效隱藏Notification上的圖標
        } else {
            Intent innerIntent = new Intent(this, GrayInnerService.class);
            startService(innerIntent);
            startForeground(GRAY_SERVICE_ID, new Notification());
        }

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

    ...
    ...

    /**
     * 給 API >= 18 的平臺上用的灰色保活手段
     */
    public static class GrayInnerService extends Service {

        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            startForeground(GRAY_SERVICE_ID, new Notification());
            stopForeground(true);
            stopSelf();
            return super.onStartCommand(intent, flags, startId);
        }

    }
}

除了這三種保活以外還有其它幾種,不過感覺沒啥用:

1.雙進程守護

首先是一個AIDL接口,兩邊的Service都要通過繼承Service_1.Stub來實現AIDL接口中的方法,這裏做一個空實現,目的是爲了實現進程通信。接口聲明如下:

package com.ph.myservice;


interface Service_1 {
    String getName();
}

然後是兩個Service,爲了保持連接,內部寫一個內部類實現ServiceConnection的接口,當外部殺了其中一個進程的時候,會進入onDisConnection中,那麼此時要做的就是start和bind另一個進程,因爲Service的啓動是可以多次的,所以這樣是沒問題的,代碼如下:

package com.ph.myservice;

import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.widget.Toast;

import java.util.List;

public class LocalService extends Service {
    private ServiceConnection conn;
    private MyService myService;

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


    @Override
    public void onCreate() {
        super.onCreate();
        init();

    }

    private void init() {
        if (conn == null) {
            conn = new MyServiceConnection();
        }
        myService = new MyService();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(getApplicationContext(), "本地進程啓動", Toast.LENGTH_LONG).show();
        Intent intents = new Intent();
        intents.setClass(this, RemoteService.class);
        bindService(intents, conn, Context.BIND_IMPORTANT);
        return START_STICKY;
    }

    class MyService extends Service_1.Stub {


        @Override
        public String getName() throws RemoteException {
            return null;
        }
    }

    class MyServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            System.out.println("獲取連接");

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Toast.makeText(LocalService.this, "遠程連接被幹掉了", Toast.LENGTH_SHORT).show();
            LocalService.this.startService(new Intent(LocalService.this,
                    RemoteService.class));
            LocalService.this.bindService(new Intent(LocalService.this,
                    RemoteService.class), conn, Context.BIND_IMPORTANT);

        }

    }

}

遠程服務類如下:

package com.ph.myservice;

import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.widget.Toast;

public class RemoteService extends Service {
    private MyBinder binder;
    private ServiceConnection conn;

    @Override
    public void onCreate() {
        super.onCreate();
        // System.out.println("遠程進程開啓");
        init();

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(getApplicationContext(), "遠程進程啓動", Toast.LENGTH_LONG).show();
        Intent intents = new Intent();
        intents.setClass(this, LocalService.class);
        bindService(intents, conn, Context.BIND_IMPORTANT);
        return START_STICKY;
    }

    private void init() {
        if (conn == null) {
            conn = new MyConnection();
        }
        binder = new MyBinder();
    }

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

    static class MyBinder extends Service_1.Stub {


        @Override
        public String getName() throws RemoteException {
            return "遠程連接";
        }
    }

    class MyConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            System.out.println("獲取遠程連接");
        }

        @Override
        public void onServiceDisconnected(ComponentName nme) {
            Toast.makeText(RemoteService.this, "本地連接被幹掉了", Toast.LENGTH_SHORT).show();
            RemoteService.this.startService(new Intent(RemoteService.this,
                    LocalService.class));
            RemoteService.this.bindService(new Intent(RemoteService.this,
                    LocalService.class), conn, Context.BIND_IMPORTANT);
        }
    }

}

佈局文件:

<service android:name=".LocalService" />
        <service
            android:name=".RemoteService"
            android:process=":remote" />

這種方法在Android5.0以後並沒有用。

2.JobScheduler執行任務調度保活

JobScheduler這個類是21版本google新出來的api,我們看他的文檔可以知道大致這個類的作用如下:

框架將智能當你收到你的回調,並嘗試批並儘可能推遲。通常如果你不指定期限在你的工作,它可以運行在任何時候根據作業調度器的當前狀態的內部隊列,然而它可能是延遲只要直到下一次設備被連接到一個電源。

這個任務其實是在設備空閒期執行的,而且系統設計的這個api不會很耗電,本意是用來執行一些任務調度的,但是我們設想一下,如果用這個類來執行我們的開啓雙進程,那麼也是一定會在設備空閒期執行的,因此我們寫一個類繼承JobService,在onstart裏聲明創建JobScheduler對象,並設置多就執行一次和開機自啓動,這樣就能確保及時在息屏狀態,也能夠執行重啓進程,所以我們在JobService的onStopJob方法裏判斷我們的進程是否被回收了,如果被回收了就重啓進程,這樣子就可以實現5.0以上的進程保活了。具體代碼如下:


package com.ph.myservice;

import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.widget.Toast;

import java.util.List;

/**
 * Created by 86119 on 2017/1/6.
 */

@SuppressLint("NewApi")
public class JobHandlerService extends JobService {
    private JobScheduler mJobScheduler;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        System.out.println("服務被創建");

//        startService(new Intent(this, LocalService.class));
//        startService(new Intent(this, RemoteService.class));

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
            JobInfo.Builder builder = new JobInfo.Builder(startId++,
                    new ComponentName(getPackageName(), JobHandlerService.class.getName()));

            builder.setPeriodic(5000); //每隔5秒運行一次
            builder.setRequiresCharging(true);
            builder.setPersisted(true);  //設置設備重啓後,是否重新執行任務
            builder.setRequiresDeviceIdle(true);

            if (mJobScheduler.schedule(builder.build()) <= 0) {
                //If something goes wrong
                System.out.println("工作失敗");
            } else {
                System.out.println("工作成功");
            }
        }
        return START_STICKY;
    }


    @Override
    public boolean onStartJob(JobParameters params) {

        Toast.makeText(this, "服務啓動", Toast.LENGTH_SHORT).show();
//        || isServiceRunning(this, "com.ph.myservice.RemoteService") == false
        System.out.println("開始工作");
//        if (!isServiceRunning(getApplicationContext(), "com.ph.myservice") || !isServiceRunning(getApplicationContext(), "com.ph.myservice:remote")) {
//            startService(new Intent(this, LocalService.class));
//            startService(new Intent(this, RemoteService.class));
//        }

       /* boolean serviceRunning = isServiceRunning(getApplicationContext(), "com.ph.myservice");
        System.out.println("進程一" + serviceRunning);

        boolean serviceRunning2 = isServiceRunning(getApplicationContext(), "com.ph.myservice:remote");
        System.out.println("進程二" + serviceRunning2);*/
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        if (!isServiceRunning(this, "com.ph.myservice.LocalService") || !isServiceRunning(this, "com.ph.myservice.RemoteService")) {
            startService(new Intent(this, LocalService.class));
            startService(new Intent(this, RemoteService.class));
        }
        return false;
    }

    // 服務是否運行
    public boolean isServiceRunning(Context context, String serviceName) {
        boolean isRunning = false;
        ActivityManager am = (ActivityManager) this
                .getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> lists = am.getRunningAppProcesses();


        for (ActivityManager.RunningAppProcessInfo info : lists) {// 獲取運行服務再啓動
            System.out.println(info.processName);
            if (info.processName.equals(serviceName)) {
                isRunning = true;
            }
        }
        return isRunning;

    }


}
package com.ph.myservice;

import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            openJobService();
        } else {
            openTwoService();
        }

    }

    private void openTwoService() {
        startService(new Intent(this, LocalService.class));
        startService(new Intent(this, RemoteService.class));
    }

    private void openJobService() {

        Intent intent = new Intent();
        intent.setClass(MainActivity.this, JobHandlerService.class);
        startService(intent);

    }
}

3、開啓一個像素的Activity

據說這個是手Q的進程保活方案,基本思想,系統一般是不會殺死前臺進程的。所以要使得進程常駐,我們只需要在鎖屏的時候在本進程開啓一個Activity,爲了欺騙用戶,讓這個Activity的大小是1像素,並且透明無切換動畫,在開屏幕的時候,把這個Activity關閉掉,所以這個就需要監聽系統鎖屏廣播
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
   }
}
如果直接啓動一個Activity,當我們按下back鍵返回桌面的時候,oom_adj的值是8,上面已經提到過,這個進程在資源不夠的情況下是容易被回收的。現在造一個一個像素的Activity。
public class LiveActivity extends Activity {

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

    public static void actionToLiveActivity(Context pContext) {
        Intent intent = new Intent(pContext, LiveActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        pContext.startActivity(intent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate");
        setContentView(R.layout.activity_live);

        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);

        ScreenManager.getInstance(this).setActivity(this);
    }

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

爲了做的更隱藏,最好設置一下這個Activity的主題,當然也無所謂了

<style name="LiveStyle">
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowAnimationStyle">@null</item>
        <item name="android:windowNoTitle">true</item>
   </style>

在屏幕關閉的時候把LiveActivity啓動起來,在開屏的時候把LiveActivity 關閉掉,所以要監聽系統鎖屏廣播,以接口的形式通知MainActivity啓動或者關閉LiveActivity。

public class ScreenBroadcastListener {

    private Context mContext;

    private ScreenBroadcastReceiver mScreenReceiver;

    private ScreenStateListener mListener;

    public ScreenBroadcastListener(Context context) {
        mContext = context.getApplicationContext();
        mScreenReceiver = new ScreenBroadcastReceiver();
    }

    interface ScreenStateListener {

        void onScreenOn();

        void onScreenOff();
    }

    /**
     * screen狀態廣播接收者
     */
    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();
            }
        }
    }

    public void registerListener(ScreenStateListener listener) {
        mListener = listener;
        registerListener();
    }

    private void registerListener() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        mContext.registerReceiver(mScreenReceiver, filter);
    }
}
public class ScreenManager {

    private Context mContext;

    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() {
        //結束掉LiveActivity
        if (mActivityWref != null) {
            Activity activity = mActivityWref.get();
            if (activity != null) {
                activity.finish();
            }
        }
    }
}

現在MainActivity改成如下

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final ScreenManager screenManager = ScreenManager.getInstance(MainActivity.this);
        ScreenBroadcastListener listener = new ScreenBroadcastListener(this);
         listener.registerListener(new ScreenBroadcastListener.ScreenStateListener() {
            @Override
            public void onScreenOn() {
                screenManager.finishActivity();
            }

            @Override
            public void onScreenOff() {
                screenManager.startActivity();
            }
        });
    }
}
但是還有一個問題,內存也是一個考慮的因素,內存越多會被最先kill掉,所以把上面的業務邏輯放到Service中,而Service是在另外一個 進程中,在MainActivity開啓這個服務就行了,這樣這個進程就更加的輕量。

public class LiveService extends Service {

    public  static void toLiveService(Context pContext){
        Intent intent=new Intent(pContext,LiveService.class);
        pContext.startService(intent);
    }

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


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //屏幕關閉的時候啓動一個1像素的Activity,開屏的時候關閉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;
    }
}
 <service android:name=".LiveService"
            android:process=":live_service"/>

4、粘性服務&與系統服務捆綁

這個是系統自帶的,onStartCommand方法必須具有一個整形的返回值,這個整形的返回值用來告訴系統在服務啓動完畢後,如果被Kill,系統將如何操作,這種方案雖然可以,但是在某些情況or某些定製ROM上可能失效,我認爲可以多做一種保保守方案

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    return START_REDELIVER_INTENT;
}
  • START_STICKY 
    如果系統在onStartCommand返回後被銷燬,系統將會重新創建服務並依次調用onCreate和onStartCommand(注意:根據測試Android2.3.3以下版本只會調用onCreate根本不會調用onStartCommand,Android4.0可以辦到),這種相當於服務又重新啓動恢復到之前的狀態了)。

  • START_NOT_STICKY 
    如果系統在onStartCommand返回後被銷燬,如果返回該值,則在執行完onStartCommand方法後如果Service被殺掉系統將不會重啓該服務。

  • START_REDELIVER_INTENT 
    START_STICKY的兼容版本,不同的是其不保證服務被殺後一定能重啓。

相比與粘性服務與系統服務捆綁更厲害一點,這個來自愛哥的研究,這裏說的系統服務很好理解,比如NotificationListenerService,NotificationListenerService就是一個監聽通知的服務,只要手機收到了通知,NotificationListenerService都能監聽到,即時用戶把進程殺死,也能重啓,所以說要是把這個服務放到我們的進程之中,那麼就可以呵呵了

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class LiveService extends NotificationListenerService {

    public LiveService() {

    }

    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {
    }

    @Override
    public void onNotificationRemoved(StatusBarNotification sbn) {
    }
}

但是這種方式需要權限

  <service
            android:name=".LiveService"
            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
            <intent-filter>
                <action android:name="android.service.notification.NotificationListenerService" />
            </intent-filter>
        </service>






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