Android進程與進程保活(涉及Notification)以及app crash表現

Android進程與進程保活(涉及Notification)

由於內存限制,android系統會在內存不足時回收進程,
一、進程回收順序
回收優先級:前臺進程<可視進程<服務進程<後臺進程<內容供應根節點<空進程
oom_adj越大 越可能被回收
系統進程 <0 前臺進程0 可見進程1
進程級別參考:https://juejin.im/entry/58acf391ac502e007e9a0a11


1、Foreground process 前臺進程

下面幾種情況屬於前臺進程:
(1)Activity正在與用戶進程交互(Activity的onResume已經被調用)
(2)與正在和用戶交互的Activity綁定的Service
(3)Service運行在前臺——Service中調用了startForeground函數
(4)Service正在執行生命週期回調函數(onCreate,onStart,onDestory)
(5)BroadcastReceiver正在執行onReceive方法

2、Visible process 可視進程

下面幾種情況屬於可視進程:
(1)Activity沒有運行在前臺,但是用戶仍然可見(它的onPause方法被調用),例如:當前臺Activity啓動了一個Dialog,這樣Dialog運行在前臺,Activity仍然可見,屬於可視進程。
(2)與一個可視的Activity綁定的服務所在的進程

3、Service process 服務進程
運行服務的進程被startService()啓動,並且沒有進入上面1中(3)、(4)這兩種情況。例如,音樂播放、網絡下載數據

4、Background process 後臺進程
當Activity不可見的時候,它的進程屬於後臺進程(Activity的onStop方法被調用)

5、Empty process 空進程
沒有包含活動應用組件的進程爲空進程,也就是進程的應用組件已經運行完畢。


查看某個包的進程
Terminal 或者 cmd 進入 adb shell 模式
ps|grep 包名
第一個參數:u0_a124 當前用戶
第二個參數:進程ID
第三個參數: 進程的父進程ID
第四個參數:進程的虛擬內存大小
第五個參數:實際內存大小
最後一個參數:進程名

查看某個進程的進程優先級(oom_adj)
cat /proc/進程id/oom_adj
(注意 cat後要加空格)
permission denied
要root

檢查Service是否開啓在前臺
1 用 cat /proc/進程名/包名
查看開啓前後的進程優先級變化 優先級降低了並且爲0即爲前臺進程
2 或者
dumpsys activity services PackageName

查看 services 找到相應的services名
發現 isForeground=true 即爲前臺進程


進程保活常見方式

一、白色手段
開啓前臺Service,會在通知欄顯示
通過notification方式 如音樂播放

如果希望從前臺移除這個服務,只需要調用stopForeground(),一般情況我們只需要在onStartCommand裏面調用 startForeground,然後再onDestroy裏面調用stopForeground即可。

public class WhiteService extends Service {
    private static final String TAG = WhiteService.class.getSimpleName();
    private static final int NOTIFICATION_FLAG =0X11;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind");
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        // 在Android進行通知處理,首先需要重系統哪裏獲得通知管理器NotificationManager,它是一個系統Service。
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        // 設置點擊通知跳轉的Intent
        Intent nfIntent = new Intent(this, MainActivity.class);
        // 設置 延遲Intent 
        // 最後一個參數可以爲PendingIntent.FLAG_CANCEL_CURRENT 或者 PendingIntent.FLAG_UPDATE_CURRENT
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, nfIntent, 0);

        //構建一個Notification構造器
        Notification.Builder builder = new Notification.Builder(this.getApplicationContext());

        builder.setContentIntent(pendingIntent)   // 設置點擊跳轉界面
                .setLargeIcon(BitmapFactory.decodeResource(this.getResources(),
                        R.mipmap.fight_total_money2x)) // 設置下拉列表中的圖標(大圖標)
                .setTicker("您有一個notification")// statusBar上的提示
                .setContentTitle("這是標題") // 設置下拉列表裏的標題
                .setSmallIcon(R.mipmap.fight_total_order2x) // 設置狀態欄內的小圖標24X24
                .setContentText("這是內容") // 設置詳細內容
                .setContentIntent(pendingIntent) // 設置點擊跳轉的界面
                .setWhen(System.currentTimeMillis()); // 設置該通知發生的時間
				.setDefaults(Notification.DEFAULT_VIBRATE) //默認震動方式
                .setPriority(Notification.PRIORITY_HIGH)   //優先級高
                
        Notification notification = builder.build(); // 獲取構建好的Notification
        
        notification.defaults = Notification.DEFAULT_SOUND; //設置爲默認的聲音
        notification.flags |= Notification.FLAG_AUTO_CANCEL; // FLAG_AUTO_CANCEL表明當通知被用戶點擊時,通知將被清除。
        notification.flags |= FLAG_ONGOING_EVENT; //將此通知放到通知欄的"Ongoing"即"正在運行"組中
		notification.flags |= FLAG_NO_CLEAR; //表明在點擊了通知欄中的"清除通知"後,此通知不清除,常與FLAG_ONGOING_EVENT一起使用
		
		
        manager.notify(NOTIFICATION_FLAG, notification);
        // 啓動前臺服務
        // 參數一:唯一的通知標識;參數二:通知消息。
        startForeground(NOTIFICATION_FLAG, notification);// 開始前臺服務

        Log.d(TAG, "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 停止前臺服務--參數:表示是否移除之前的通知
        stopForeground(true);
        Log.d(TAG, "onDestroy");
    }

}

開啓服務:

Intent intent = new Intent(MainActivity.this,WhiteService.class);
        startService(intent);// (服務與開啓者無聯繫的啓動形式  )

結束服務:

 button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                stopService(intent);
            }
        });

更新通知:
重新創建notification.builder和notification
notificationManager調用 notify方法
其實就是再創建一個notification 只是標誌和之前的那個一樣 這樣就會更新前面的通知了


二、灰色保活
也是開啓前臺Service,但是不會在通知欄顯示
adb shell 模式下:
dumpsys activity services PackageName
若Service有 isForeground=true 而通知欄卻沒有顯示 則是灰色保活方式

方式:
API < 18,啓動前臺Service時直接傳入空的 new Notification();
API >= 18,在需要提優先級的service A啓動一個InnerService,兩個服務同時startForeground,且綁定同樣的 ID。Stop 掉InnerService ,這樣通知欄圖標即被移除

public class GrayService extends Service {
    private static final String TAG = GrayService.class.getSimpleName();
    private final static int GRAY_SERVICE_ID = 0x12;

    public GrayService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //API 18以下,直接發送Notification並將其置爲前臺
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
            startForeground(GRAY_SERVICE_ID, new Notification());
        } else {
            //API 18以上,發送Notification並將其置爲前臺後,啓動InnerService
            Notification.Builder builder = new Notification.Builder(this);
            builder.setSmallIcon(R.mipmap.fight_total_money2x);
            Notification notification = builder.build(); // 獲取構建好的Notification

            startForeground(GRAY_SERVICE_ID, notification);

            startService(new Intent(this, GrayInnerService.class));
        }
        Log.d(TAG, "GrayServiceOnCreate");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 停止前臺服務--參數:表示是否移除之前的通知
        stopForeground(true);
        Log.d(TAG, "onDestroy");
    }
}
public class GrayInnerService extends Service {
    private static final String TAG = GrayInnerService.class.getSimpleName();
    public static final int GRAY_INNER_SERVICE_ID=0x12;
    public GrayInnerService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //發送與GrayService中ID相同的Notification,然後將其取消並取消自己的前臺顯示
        Notification.Builder builder = new Notification.Builder(this);
        builder.setSmallIcon(R.mipmap.fight_total_order2x);
        Notification notification = builder.build(); // 獲取構建好的Notification

        startForeground(GRAY_INNER_SERVICE_ID,notification);

        // 延遲0.1s 終止掉innerService 這樣 通知欄圖標會清除
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                stopForeground(true);
                NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                if (manager != null) {
                    manager.cancel(GRAY_INNER_SERVICE_ID);
                }else {
                    Log.e(TAG, "notification is null!");
                }
                stopSelf();
            }
        },100);
        Log.d(TAG, "GrayInnerServiceOnCreate");
    }
}

三、黑色手段
利用不同的app進程使用廣播來進行相互喚醒
(1)開機,網絡切換、拍照、拍視頻時候,利用系統產生的廣播喚醒app
最新的Android N取消了 ACTION_NEW_PICTURE(拍照),ACTION_NEW_VIDEO(拍視頻),CONNECTIVITY_ACTION(網絡切換)等三種廣播
(2)接入第三方SDK也會喚醒相應的app進程,如微信sdk會喚醒微信,支付寶sdk會喚醒支付寶
(3)app互相喚醒


四、白名單方式
系統給app加入白名單,系統不會殺死白名單中的app


後臺服務

Android8.0以上 電池優化策略 應用在後臺經過 idle timeout後 會禁用後臺服務 stopService 並禁止啓動服務

解決:
1 設置爲前臺服務
2 用JobIntentService 參考:https://blog.csdn.net/weixin_37577039/article/details/78495357
3 用JobService+JobScheduler

JobScheduler

1 可以推遲的非面向用戶的任務(如定期數據庫數據更新)
2 當充電時才希望執行的工作(如備份數據)
3 需要訪問網絡或 Wi-Fi 連接的任務(如向服務器拉取內置數據)
4 希望作爲一個批次定期運行的許多任務

開啓時間能保證實時嗎 好像有延遲

能掛多久後臺

doze mode(息屏了低電耗模式) 系統不允許運行 JobScheduler

1 創建JobService

class XXXJobService : JobService() {
override fun onStartJob(params: JobParameters): Boolean {
        mXX = params.extras.getString(XX_KEY)
        開啓線程執行任務
        }
 override fun onStopJob(params: JobParameters): Boolean {
        Logger.t(TAG).d("===onStop SSDPJob===")
        return false
    }
    override fun onDestroy() {
        super.onDestroy()
        Logger.t(TAG).d("===onDestroy SSDPJob===")
    }

2 通過JobScheduler開啓JobService

if (mJobScheduler == null) {
            mJobScheduler = applicationContext.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
        }
        val bundle = PersistableBundle()
        bundle.putString(XXKEY,XXValue)
        val builder: JobInfo.Builder = JobInfo.Builder(XX_JOB_ID,
                ComponentName(applicationContext,XXXJobService::class.java))
        builder.setMinimumLatency(latency) // 延遲lantency毫秒後開啓任務
        builder.setPersisted(false) // 是否設備重啓扔執行
        builder.setExtras(bundle) // 設置額外參數
        mJobScheduler?.schedule(builder.build())

3 停止JobService

mJobScheduler?.cancel(SSDP_JOB_ID)

appcrash表現

若在前臺:
不包含service/broadcastReceiver,只有一個Activity,那麼系統不會重新啓動該應用
包含service/broadcastReceiver,只有一個Activity,那麼系統會重新啓動該應用(包括service/broadcastReceiver) 但不會重啓該界面

不包含service/broadcastReceiver,但是當前棧中包含兩個Activity, A–>B, 如果B crash,那麼系統會重啓該應用(application)也會進入A
不包含service/broadcastReceiver,但是當前棧中包含三個Activity, A–>B–>C, 如果C crash,那麼系統會重啓該應用(application) 進入B,並且A仍然存在,即可以從重啓的Back到A
若在後臺:
不會重啓
但是若重新在前臺了 是會和在前臺的表現一樣

sevice是可以根據START_STICK標誌位判斷是否crash了重新啓動

靜態廣播
收到了廣播纔會創建 而且是收到一次創建一次 執行一次構造函數
若crash了 不管應用在前臺還是後臺 只要應用創建了(application oncreate) 都是會重啓
動態廣播
是不會自動重啓的 除非進入到了你的啓動邏輯 因爲動態廣播啓動和關閉是自己設置的

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