<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Jack Jiang

    我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
    posts - 494, comments - 13, trackbacks - 0, articles - 1

    本文原作者“minminaya”,作者網站:minminaya.cn,為了提升文章品質,即時通訊網對內容作了幅修訂和改動,感謝原作者。

    1、引言

    對于IM應用和消息推送服務的開發者來說,在Android機型上的后臺保活是個相當頭疼的問題。

    老板一句:“為什么微信、QQ能收到消息,而你寫的APP卻不行?”,直接讓人崩潰,話說老板你這APP要是整成微信、APP那么牛,直接進手機廠商白名單,還要程序員在這瞎忙活?

    好了,抱怨歸抱怨,活還得干,不然靠誰養活廣大苦逼的程序員?

    回到正題,Android程序員都知道,隨著Android系統的不斷完善和升級,Andriod應用的后臺保活是一次比一次難(詳見《Android P正式版即將到來:后臺應用保活、消息推送的真正噩夢》),但日子還得過,只能一次次絞盡腦汁想出各種黑科技。但不幸的是,因為Andriod系統的不斷升級,各種黑科技也只能適應某些版本的Android系統,無法一勞永逸解決問題。

    ▲ Android各版本都是用“甜品”命名的

    正因為Android系統版本的差異,也導致了各種保活黑科技的運行效果大相徑庭,所以本文正好借此機會,盤點一下當前主流(截止2019年前)的保活黑科技在市面上各版本Android手機上的運行效果,希望能給大家提供一些客觀的參考。

    學習交流:

    - 即時通訊/推送技術開發交流4群:101279154 [推薦]

    - 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM

    (本文同步發布于:http://www.52im.net/thread-2176-1-1.html

    2、先總結一下,Android端APP為何要搞保活黑科技?

    * 本節內容摘錄自即時通訊網整理的《Android P正式版即將到來:后臺應用保活、消息推送的真正噩夢》一文

    其實Android端APP搞保活的目的倒不是為了干什么見不得人的壞事(但不排除動機不純的開發者),主要是像IM即時通訊應用和資訊類應用等需要搞后臺消息推送、運動類應用需要在后臺實時監測用戶的運動數據等,因為現在越來越多的手機廠商為了省電策略考慮,基本上如果你的應用沒有被加入白名單,一旦處于后臺就會被系統限制甚至干掉,但使用APP的用戶才不聽你這些解釋——反正“我”就要你的APP能如期正常運行,開發者也是不得已而為之。

    以消息推送為例,當APP處于后臺或關閉時,消息推送對于某些應用來說非常有用,比如:

    1)IM即時通訊聊天應用:聊天消息通知、音視頻聊天呼叫等,典型代表有:微信、QQ、易信、米聊、釘釘、Whatsup、Line;

    2)新聞資訊應用:最新資訊通知等,典型代表有:網易新聞客戶端、騰訊新聞客戶端;

    3)SNS社交應用:轉發/關注/贊等通知,典型代表有:微博、知乎;

    4)郵箱客戶端:新郵件通知等,典型代表有:QQ郵箱客戶端、Foxmail客戶端、網易郵箱大師;

    5)金融支付應用:收款通知、轉賬通知等,典型代表有:支付寶、各大銀行的手機銀行等;

    .... ....

    在上述的各種應用中,尤其對于用戶接觸最多、最平常的IM聊天應用或新聞資訊來說,保活和消息推送簡直事關APP的“生死”,消息推送這種能力已經被越來越多的APP作為基礎能力之一,因為移動互聯網時代下,用戶的“全時在線”能力非常誘人和強大,能隨時隨地即時地將各種重要信息推送給用戶,無疑是非常有意義的。

    題外話:實際上,對于后臺消息推送能力,Android原版系統早就內置了系統級推送服務(跟iOS上的APNs服務是一個東西),它就是GCM服務(現在升級為FCM了),但眾所周之的原因,谷哥的服務在國內都是用不了的(你懂的)——無奈啊!

    (有關GCM的介紹詳見:《移動端IM實踐:谷歌消息推送服務(GCM)研究(來自微信)》、《為何微信、QQ這樣的IM工具不使用GCM服務推送消息?》、《求教android消息推送:GCM、XMPP、MQTT三種方案的優劣》)。

    ▲ 如果Android能有iOS的APNs這么強勢的方案存在,那該是多美的事 ...

    3、相關文章

    應用保活終極總結(一):Android6.0以下的雙進程守護保活實踐

    應用保活終極總結(二):Android6.0及以上的保活實踐(進程防殺篇)

    應用保活終極總結(三):Android6.0及以上的保活實踐(被殺復活篇)

    Android進程保活詳解:一篇文章解決你的所有疑問

    Android端消息推送總結:實現原理、心跳保活、遇到的問題等

    深入的聊聊Android消息推送這件小事

    微信團隊原創分享:Android版微信后臺保活實戰分享(進程保活篇)

    Android P正式版即將到來:后臺應用保活、消息推送的真正噩夢

    4、常見的Android端保活黑科技方案盤點

    主要黑科技方案有:

    1)監聽廣播:監聽全局的靜態廣播,比如時間更新的廣播、開機廣播、解鎖屏、網絡狀態、解鎖加鎖亮屏暗屏(3.1版本),高版本需要應用開機后運行一次才能監聽這些系統廣播,目前此方案失效。可以更換思路,做APP啟動后的保活(監聽廣播啟動保活的前臺服務);

    2)定時器、JobScheduler:假如應用被系統殺死,那么定時器則失效,此方案失效。JobService在5.0,5.1,6.0作用很大,7.0時候有一定影響(可以在電源管理中給APP授權);

    3)雙進程(NDK方式Fork子進程)、雙Service守護:高版本已失效,5.0起系統回收策略改成進程組。雙Service方案也改成了應用被殺,任何后臺Service無法正常狀態運行;

    4)提高Service優先級:只能一定程度上緩解Service被立馬回收。

    針對上述方案,具體的實現思路,通常是這樣的:

    1)進程拉活:AIDL方式單進程、雙進程方式保活Service(最極端的例子就是推送廠商的互相喚醒復活:極光、友盟、以及各大廠商的推送,同派系APP廣播互相喚醒:比如今日頭條系、阿里系);

    2)降低oom_adj的值:常駐通知欄(可通過啟動另外一個服務關閉Notification,不對oom_adj值有影響)、使用”1像素“的Activity覆蓋在getWindow()的view上(據傳某不可言說的IM大廠用過這個方案,雖然他們從未正面承認過)、循環播放無聲音頻(黑科技,7.0下殺不掉);

    3)監聽鎖屏廣播:使Activity始終保持前臺;

    4)使用自定義鎖屏界面:覆蓋了系統鎖屏界面;

    5)創建子進程:通過android:process屬性來為Service創建一個進程;

    6)白名單:跳轉到系統白名單界面讓用戶自己添加app進入白名單。

    5、匯總一下,主要的保活黑科技方案的具體代碼實現

    5.1 黑科技代碼實現1:雙進程拉活方案的代碼實現

    使用AIDL綁定方式新建2個Service優先級(防止服務同時被系統殺死)不一樣的守護進程互相拉起對方,并在每一個守護進程的ServiceConnection的綁定回調里判斷保活Service是否需要重新拉起和對守護線程進行重新綁定。

    關于本方案的具體實現,即時通訊網的以下文章有更詳細的介紹,您也可以仔細研讀:

    應用保活終極總結(一):Android6.0以下的雙進程守護保活實踐

    應用保活終極總結(二):Android6.0及以上的保活實踐(進程防殺篇)

    應用保活終極總結(三):Android6.0及以上的保活實踐(被殺復活篇)

    本方案的具體代碼實現,主要由以下4步構成。

    1)新建一個AIDL文件:

    KeepAliveConnection

    interfaceKeepAliveConnection  {

    }

    2)新建一個服務類StepService,onBind()方法返回new KeepAliveConnection.Stub()對象,并在ServiceConnection的綁定回調中對守護進程服務類GuardService的啟動和綁定:

    /**

    * 主進程 雙進程通訊

    *

    * @author LiGuangMin

    * @time Created by 2018/8/17 11:26

    */

    public class StepService extends Service {

       privatefinalstaticString TAG = StepService.class.getSimpleName();

       privateServiceConnection mServiceConnection = newServiceConnection() {

           @Override

           publicvoidonServiceConnected(ComponentName componentName, IBinder iBinder) {

               Logger.d(TAG, "StepService:建立鏈接");

               booleanisServiceRunning = ServiceAliveUtils.isServiceAlice();

               if(!isServiceRunning) {

                   Intent i = newIntent(StepService.this, DownloadService.class);

                   startService(i);

               }

           }


           @Override

           publicvoidonServiceDisconnected(ComponentName componentName) {

               // 斷開鏈接

               startService(newIntent(StepService.this, GuardService.class));

               // 重新綁定

               bindService(newIntent(StepService.this, GuardService.class), mServiceConnection, Context.BIND_IMPORTANT);

           }

       };


       @Nullable

       @Override

       publicIBinder onBind(Intent intent) {

           returnnewKeepAliveConnection.Stub() {

           };

       }


       @Override

       publicintonStartCommand(Intent intent, intflags, intstartId) {

           startForeground(1, newNotification());

           // 綁定建立鏈接

           bindService(newIntent(this, GuardService.class), mServiceConnection, Context.BIND_IMPORTANT);

           returnSTART_STICKY;

       }

    }

    3)對守護進程GuardService進行和2一樣的處理:

    /**

    * 守護進程 雙進程通訊

    *

    * @author LiGuangMin

    * @time Created by 2018/8/17 11:27

    */

    publicclassGuardService extendsService {

       privatefinalstaticString TAG = GuardService.class.getSimpleName();

       privateServiceConnection mServiceConnection = newServiceConnection() {

           @Override

           publicvoidonServiceConnected(ComponentName componentName, IBinder iBinder) {

               Logger.d(TAG, "GuardService:建立鏈接");

               booleanisServiceRunning = ServiceAliveUtils.isServiceAlice();

               if(!isServiceRunning) {

                   Intent i = newIntent(GuardService.this, DownloadService.class);

                   startService(i);

               }

           }


           @Override

           publicvoidonServiceDisconnected(ComponentName componentName) {

               // 斷開鏈接

               startService(newIntent(GuardService.this, StepService.class));

               // 重新綁定

               bindService(newIntent(GuardService.this, StepService.class), mServiceConnection, Context.BIND_IMPORTANT);

           }

       };


       @Nullable

       @Override

       publicIBinder onBind(Intent intent) {

           returnnewKeepAliveConnection.Stub() {

           };

       }


       @Override

       publicintonStartCommand(Intent intent, intflags, intstartId) {

           startForeground(1, newNotification());

           // 綁定建立鏈接

           bindService(newIntent(this, StepService.class), mServiceConnection, Context.BIND_IMPORTANT);

           returnSTART_STICKY;

       }

    }

    4)在Activity中在啟動需要保活的DownloadService服務后然后啟動保活的雙進程:

    public class MainActivity extends AppCompatActivity {

       privateTextView mShowTimeTv;

       privateDownloadService.DownloadBinder mDownloadBinder;

       privateServiceConnection mServiceConnection = newServiceConnection() {

           @Override

           publicvoidonServiceConnected(ComponentName name, IBinder service) {

               mDownloadBinder = (DownloadService.DownloadBinder) service;

               mDownloadBinder.setOnTimeChangeListener(newDownloadService.OnTimeChangeListener() {

                   @Override

                   publicvoidshowTime(finalString time) {

                       runOnUiThread(newRunnable() {

                           @Override

                           publicvoidrun() {

                               mShowTimeTv.setText(time);

                           }

                       });

                   }

               });

           }


           @Override

           publicvoidonServiceDisconnected(ComponentName name) {

           }

       };


       @Override

       protectedvoidonCreate(Bundle savedInstanceState) {

           super.onCreate(savedInstanceState);

           setContentView(R.layout.activity_main);


           Intent intent = newIntent(this, DownloadService.class);

           startService(intent);

           bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);

           //雙守護線程,優先級不一樣

           startAllServices();

       }


       @Override

       publicvoidonContentChanged() {

           super.onContentChanged();

           mShowTimeTv = findViewById(R.id.tv_show_time);

       }


       @Override

       protectedvoidonDestroy() {

           super.onDestroy();

           unbindService(mServiceConnection);

       }


       /**

        * 開啟所有守護Service

        */

       privatevoidstartAllServices() {

           startService(newIntent(this, StepService.class));

           startService(newIntent(this, GuardService.class));

       }

    }

    5.2 黑科技代碼實現2:監聽到鎖屏廣播后使用“1”像素Activity提升優先級

    “1”像素保活這么流氓的手段,傳說是某IM大廠用過的方案 ...

    本方法的具體代碼實現主要由以下6步組成。

    1)該Activity的View只要設置為1像素然后設置在Window對象上即可。在Activity的onDestroy周期中進行保活服務的存活判斷從而喚醒服務。”1像素”Activity如下:

    public class SinglePixelActivity extends AppCompatActivity {

       private static final String TAG = SinglePixelActivity.class.getSimpleName();


       @Override

       protected void onCreate(@NullableBundle savedInstanceState) {

           super.onCreate(savedInstanceState);

           Window mWindow = getWindow();

           mWindow.setGravity(Gravity.LEFT | Gravity.TOP);

           WindowManager.LayoutParams attrParams = mWindow.getAttributes();

           attrParams.x = 0;

           attrParams.y = 0;

           attrParams.height = 1;

           attrParams.width = 1;

           mWindow.setAttributes(attrParams);

           ScreenManager.getInstance(this).setSingleActivity(this);

       }


       @Override

       protectedvoidonDestroy() {

           if(!SystemUtils.isAppAlive(this, Constant.PACKAGE_NAME)) {

               Intent intentAlive = newIntent(this, DownloadService.class);

               startService(intentAlive);

           }

           super.onDestroy();

       }

    }

    2)對廣播進行監聽,封裝為一個ScreenReceiverUtil類,進行鎖屏解鎖的廣播動態注冊監聽:

    public class ScreenReceiverUtil {

       privateContext mContext;

       privateSreenBroadcastReceiver mScreenReceiver;

       privateSreenStateListener mStateReceiverListener;


       publicScreenReceiverUtil(Context mContext) {

           this.mContext = mContext;

       }


       publicvoidsetScreenReceiverListener(SreenStateListener mStateReceiverListener) {

           this.mStateReceiverListener = mStateReceiverListener;

           // 動態啟動廣播接收器

           this.mScreenReceiver = newSreenBroadcastReceiver();

           IntentFilter filter = newIntentFilter();

           filter.addAction(Intent.ACTION_SCREEN_ON);

           filter.addAction(Intent.ACTION_SCREEN_OFF);

           filter.addAction(Intent.ACTION_USER_PRESENT);

           mContext.registerReceiver(mScreenReceiver, filter);

       }


       publicvoidstopScreenReceiverListener() {

           mContext.unregisterReceiver(mScreenReceiver);

       }


       /**

        * 監聽sreen狀態對外回調接口

        */

       publicinterfaceSreenStateListener {

           voidonSreenOn();


           voidonSreenOff();


           voidonUserPresent();

       }


       publicclassSreenBroadcastReceiver extendsBroadcastReceiver {

           @Override

           publicvoidonReceive(Context context, Intent intent) {

               String action = intent.getAction();

               if(mStateReceiverListener == null) {

                   return;

               }

               if(Intent.ACTION_SCREEN_ON.equals(action)) { // 開屏

                   mStateReceiverListener.onSreenOn();

               } elseif(Intent.ACTION_SCREEN_OFF.equals(action)) { // 鎖屏

                   mStateReceiverListener.onSreenOff();

               } elseif(Intent.ACTION_USER_PRESENT.equals(action)) { // 解鎖

                   mStateReceiverListener.onUserPresent();

               }

           }

       }

    }

    3)對1像素Activity進行防止內存泄露的處理,新建一個ScreenManager類:

    public class ScreenManager {

       privatestaticfinalString TAG = ScreenManager.class.getSimpleName();

       privatestaticScreenManager sInstance;

       privateContext mContext;

       privateWeakReference<Activity> mActivity;


       privateScreenManager(Context mContext) {

           this.mContext = mContext;

       }


       publicstaticScreenManager getInstance(Context context) {

           if(sInstance == null) {

               sInstance = newScreenManager(context);

           }

           returnsInstance;

       }


       /** 獲得SinglePixelActivity的引用

        * @param activity

        */

       publicvoidsetSingleActivity(Activity activity) {

           mActivity = newWeakReference<>(activity);

       }


       /**

        * 啟動SinglePixelActivity

        */

       publicvoidstartActivity() {

           Intent intent = newIntent(mContext, SinglePixelActivity.class);

           intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

           mContext.startActivity(intent);

       }


       /**

        * 結束SinglePixelActivity

        */

       publicvoidfinishActivity() {

           if(mActivity != null) {

               Activity activity = mActivity.get();

               if(activity != null) {

                   activity.finish();

               }

           }

       }

    }

    4)對1像素的Style進行特殊處理,在style文件中新建一個SingleActivityStyle:

    <stylename="SingleActivityStyle"parent="android:Theme.Holo.Light.NoActionBar">

           <itemname="android:windowBackground">@android:color/transparent</item>

           <itemname="android:windowFrame">@null</item>

           <itemname="android:windowNoTitle">true</item>

           <itemname="android:windowIsFloating">true</item>

           <itemname="android:windowContentOverlay">@null</item>

           <itemname="android:backgroundDimEnabled">false</item>

           <itemname="android:windowAnimationStyle">@null</item>

           <itemname="android:windowDisablePreview">true</item>

           <itemname="android:windowNoDisplay">false</item>

    5)讓SinglePixelActivity使用singleInstance啟動模式,在manifest文件中:

    <activity

               android:name=".activity.SinglePixelActivity"

               android:configChanges="keyboardHidden|orientation|screenSize|navigation|keyboard"

               android:excludeFromRecents="true"

               android:finishOnTaskLaunch="false"

               android:launchMode="singleInstance"

               android:theme="@style/SingleActivityStyle"/>

    6)在保活服務類DownloadService中對監聽的廣播進行注冊和對SinglePixelActivity進行控制:

    public class DownloadService extends Service {

       publicstaticfinalintNOTICE_ID = 100;

       privatestaticfinalString TAG = DownloadService.class.getSimpleName();

       privateDownloadBinder mDownloadBinder;

       privateNotificationCompat.Builder mBuilderProgress;

       privateNotificationManager mNotificationManager;


       privateScreenReceiverUtil mScreenListener;

       privateScreenManager mScreenManager;

       privateTimer mRunTimer;


       privateintmTimeSec;

       privateintmTimeMin;

       privateintmTimeHour;


       privateScreenReceiverUtil.SreenStateListener mScreenListenerer = newScreenReceiverUtil.SreenStateListener() {

           @Override

           publicvoidonSreenOn() {

               mScreenManager.finishActivity();

               Logger.d(TAG, "關閉了1像素Activity");

           }


           @Override

           publicvoidonSreenOff() {

               mScreenManager.startActivity();

               Logger.d(TAG, "打開了1像素Activity");

           }


           @Override

           publicvoidonUserPresent() {

           }

       };

       privateOnTimeChangeListener mOnTimeChangeListener;


       @Override

       publicvoidonCreate() {

           super.onCreate();


    //        注冊鎖屏廣播監聽器

           mScreenListener = newScreenReceiverUtil(this);

           mScreenManager = ScreenManager.getInstance(this);

           mScreenListener.setScreenReceiverListener(mScreenListenerer);


           mDownloadBinder = newDownloadBinder();

           mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

       }


       @Override

       publicintonStartCommand(Intent intent, intflags, intstartId) {

           Logger.d(TAG, "onStartCommand");

           startRunTimer();

           returnSTART_STICKY;

       }


       @Nullable

       @Override

       publicIBinder onBind(Intent intent) {


           returnmDownloadBinder;

       }


       @Override

       publicbooleanonUnbind(Intent intent) {

           Logger.d(TAG, "onUnbind");

           returnsuper.onUnbind(intent);

       }


       @Override

       publicvoidonDestroy() {

           super.onDestroy();

           NotificationManager mManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

           if(mManager == null) {

               return;

           }

           mManager.cancel(NOTICE_ID);

           stopRunTimer();

    //        mScreenListener.stopScreenReceiverListener();

       }


       privatevoidstartRunTimer() {

           TimerTask mTask = newTimerTask() {

               @Override

               publicvoidrun() {

                   mTimeSec++;

                   if(mTimeSec == 60) {

                       mTimeSec = 0;

                       mTimeMin++;

                   }

                   if(mTimeMin == 60) {

                       mTimeMin = 0;

                       mTimeHour++;

                   }

                   if(mTimeHour == 24) {

                       mTimeSec = 0;

                       mTimeMin = 0;

                       mTimeHour = 0;

                   }

                   String time = "時間為:"+ mTimeHour + " : "+ mTimeMin + " : "+ mTimeSec;

                   if(mOnTimeChangeListener != null) {

                       mOnTimeChangeListener.showTime(time);

                   }

                   Logger.d(TAG, time);

               }

           };

           mRunTimer = newTimer();

           // 每隔1s更新一下時間

           mRunTimer.schedule(mTask, 1000, 1000);

       }


       privatevoidstopRunTimer() {

           if(mRunTimer != null) {

               mRunTimer.cancel();

               mRunTimer = null;

           }

           mTimeSec = 0;

           mTimeMin = 0;

           mTimeHour = 0;

           Logger.d(TAG, "時間為:"+ mTimeHour + " : "+ mTimeMin + " : "+ mTimeSec);

       }


       publicinterfaceOnTimeChangeListener {

           voidshowTime(String time);

       }


       publicclassDownloadBinder extendsBinder {

           publicvoidsetOnTimeChangeListener(OnTimeChangeListener onTimeChangeListener) {

               mOnTimeChangeListener = onTimeChangeListener;

           }

       }

    }

    6.3 黑科技代碼實現3:在后臺播放音樂

    后臺播放音樂這種保活方法,親身經歷過:

    記得當時用的是某運動記步APP,它為了保活就是這么干的。之所以被我發現,是因為在我的Android手機上,每次打開這個APP居然總能莫名其妙聽到若有若無的環境噪音樣的聲音,尤其安靜的場所下更明顯。我個人估計這個APP里用的保活音頻文件,很可能就是程序員在簡陋的條件下隨手自已錄制的,雖然也是不得以為之,但做法確實是有點粗糙。

    好了,回到正題,本方案的具體代碼實現主要是以下3步。

    1)準備一段無聲的音頻,新建一個播放音樂的Service類,將播放模式改為無限循環播放。在其onDestroy方法中對自己重新啟動:

    public class PlayerMusicService extends Service {

       privatefinalstaticString TAG = PlayerMusicService.class.getSimpleName();

       privateMediaPlayer mMediaPlayer;


       @Nullable

       @Override

       publicIBinder onBind(Intent intent) {

           returnnull;

       }


       @Override

       publicvoidonCreate() {

           super.onCreate();

           Logger.d(TAG, TAG + "---->onCreate,啟動服務");

           mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.silent);

           mMediaPlayer.setLooping(true);

       }


       @Override

       publicintonStartCommand(Intent intent, intflags, intstartId) {

           newThread(newRunnable() {

               @Override

               publicvoidrun() {

                   startPlayMusic();

               }

           }).start();

           returnSTART_STICKY;

       }


       privatevoidstartPlayMusic() {

           if(mMediaPlayer != null) {

               Logger.d(TAG, "啟動后臺播放音樂");

               mMediaPlayer.start();

           }

       }


       privatevoidstopPlayMusic() {

           if(mMediaPlayer != null) {

               Logger.d(TAG, "關閉后臺播放音樂");

               mMediaPlayer.stop();

           }

       }


       @Override

       publicvoidonDestroy() {

           super.onDestroy();

           stopPlayMusic();

           Logger.d(TAG, TAG + "---->onCreate,停止服務");

           // 重啟自己

           Intent intent = newIntent(getApplicationContext(), PlayerMusicService.class);

           startService(intent);

       }

    }

    2)在保活的DownloadServie服務類的onCreate方法中對PlayerMusicService進行啟動:

    Intent intent = newIntent(this, PlayerMusicService.class);

    startService(intent);

    3)在Manifest文件中進行注冊:

    <service

               android:name=".service.PlayerMusicService"

               android:enabled="true"

               android:exported="true"

               android:process=":music_service"/>

    6.4 黑科技代碼實現4:使用JobScheduler喚醒Service

    本方案代碼實現由以下3步組成。

    1)新建一個繼承自JobService的ScheduleService類,在其onStartJob回調中對DownloadService進行存活的判斷來重啟:

    public class ScheduleService extends JobService {

       privatestaticfinalString TAG = ScheduleService.class.getSimpleName();


       @Override

       publicbooleanonStartJob(JobParameters params) {


           booleanisServiceRunning = ServiceAliveUtils.isServiceAlice();

           if(!isServiceRunning) {

               Intent i = newIntent(this, DownloadService.class);

               startService(i);

               Logger.d(TAG, "ScheduleService啟動了DownloadService");

           }

           jobFinished(params, false);

           returnfalse;

       }


       @Override

       publicbooleanonStopJob(JobParameters params) {

           returnfalse;

       }

    }

    2)在DownloadService服務類中進行JobScheduler的注冊和使用:

    /**

        * 使用JobScheduler進行保活

        */

       private void useJobServiceForKeepAlive() {

           JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);

           if(jobScheduler == null) {

               return;

           }

           jobScheduler.cancelAll();

           JobInfo.Builder builder =

               newJobInfo.Builder(1024, newComponentName(getPackageName(), ScheduleService.class.getName()));

           //周期設置為了2s

           builder.setPeriodic(1000* 2);

           builder.setPersisted(true);

           builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);

           intschedule = jobScheduler.schedule(builder.build());

           if(schedule <= 0) {

               Logger.w(TAG, "schedule error!");

           }

       }

    3)在manifest文件中進行權限設置:

    <service

               android:name=".service.ScheduleService"

               android:enabled="true"

               android:exported="true"

        android:permission="android.permission.BIND_JOB_SERVICE"/>

    7、總結一下,以上方案在當前主流手機上的運行效果

    【1】雙進程守護方案(基于onStartCommand() return START_STICKY):

    1)原生5.0、5.1:原生任務欄滑動清理app,Service會被殺掉,然后被拉起,接著一直存活;

    2)金立F100(5.1):一鍵清理直接殺掉整個app,包括雙守護進程。不手動清理情況下,經測試能鎖屏存活至少40分鐘;

    3)華為暢享5x(6.0):一鍵清理直接殺掉整個app,包括雙守護進程。不手動清理下,鎖屏只存活10s。結論:雙進程守護方案失效;

    4)美圖m8s(7.1.1):一鍵清理直接殺掉整個app,包括雙守護進程。不清理情況下,鎖屏會有被殺過程(9分鐘左右被殺),之后重新復活,之后不斷被干掉然后又重新復活。結論:雙守護進程可在后臺不斷拉起Service;

    5)原生7.0:任務欄清除APP后,Service存活。使用此方案后Service照樣存活;

    6)LG V30+(7.1.2):不加雙進程守護的時候,一鍵清理無法殺掉服務。加了此方案之后也不能殺掉服務,鎖屏存活(測試觀察大于50分鐘);

    7)小米8(8.1):一鍵清理直接干掉app并且包括雙守護進程。不清理情況下,不加守護進程方案與加守護進程方案Service會一直存活,12分鐘左右closed。結論:此方案沒有起作用。

    ▲ 結論:除了華為此方案無效以及未更改底層的廠商不起作用外(START_STICKY字段就可以保持Service不被殺)。此方案可以與其他方案混合使用。

    【2】監聽鎖屏廣播打開1像素Activity(基于onStartCommand() return START_STICKY):

    1)原生5.0、5.1:鎖屏后3s服務被干掉然后重啟(START_STICKY字段起作用);

    2)華為暢享5x(6.0):鎖屏只存活4s。結論:方案失效;

    3)美圖m8s(7.1.1):同原生5.0;

    4)原生7.0:同美圖m8s;

    5)LG V30+(7.1.2):鎖屏后情況跟不加情況一致,服務一致保持運行,結論:此方案不起作用;

    6)小米8(8.1):關屏過2s之后app全部被干掉。結論:此方案沒有起作用。

    ▲ 結論:此方案無效果。

    【3】故意在后臺播放無聲的音樂(基于onStartCommand() return START_STICKY):

    1)原生5.0、5.1:鎖屏后3s服務被干掉然后重啟(START_STICKY字段起作用);

    2)華為暢享5x(6.0):一鍵清理后服務依然存活,需要單獨清理才可殺掉服務,鎖屏8分鐘后依然存活。結論:此方案適用;

    3)美圖m8s(7.1.1):同5.0;

    4)原生7.0:任務管理器中關閉APP后服務被干掉,大概過3s會重新復活(同僅START_STICKY字段模式)。結論:看不出此方案有沒有其作用;

    5)LG V30+(7.1.2):使用此方案前后效果一致。結論:此方案不起作用;

    6)小米8(8.1):一鍵清理可以殺掉服務。鎖屏后保活超過20分鐘。

    ▲ 結論:成功對華為手機保活。小米8下也成功突破20分鐘。

    【4】使用JobScheduler喚醒Service(基于onStartCommand() return START_STICKY):

    1)原生5.0、5.1:任務管理器中干掉APP,服務會在周期時間后重新啟動。結論:此方案起作用;

    2)華為暢享5x(6.0):一鍵清理直接殺掉APP,過12s左右會自動重啟服務,JobScheduler起作用;

    3)美圖m8s(7.1.1):一鍵清理直接殺掉APP,無法自動重啟;

    4)原生7.0:同美圖m8s(7.1.1);

    5)小米8(8.1):同美圖m8s(7.1.1)。

    ▲ 結論:只對5.0,5.1、6.0起作用。

    【5】混合使用的效果,并且在通知欄彈出通知:

    1)原生5.0、5.1:任務管理器中干掉APP,服務會在周期時間后重新啟動。鎖屏超過11分鐘存活;

    2)華為暢享5x(6.0):一鍵清理后服務依然存活,需要單獨清理才可殺掉服務。結論:方案適用;

    3)美圖m8s(7.1.1):一鍵清理APP會被殺掉。正常情況下鎖屏后服務依然存活;

    4)原生7.0:任務管理器中關閉APP后服務被干掉,過2s會重新復活;

    5)小米8(8.1):一鍵清理可以殺掉服務,鎖屏下后臺保活時間超過38分鐘;

    6)榮耀10(8.0):一鍵清理殺掉服務,鎖屏下后臺保活時間超過23分鐘。

    ▲ 結論:高版本情況下可以使用彈出通知欄、雙進程、無聲音樂提高后臺服務的保活概率。

    8、補充:ServiceAliveUtils 類代碼如下

    public class ServiceAliveUtils {

       publicstaticbooleanisServiceAlice() {

           booleanisServiceRunning = false;

           ActivityManager manager =

               (ActivityManager) MyApplication.getMyApplication().getSystemService(Context.ACTIVITY_SERVICE);

           if(manager == null) {

               returntrue;

           }

           for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {

               if("demo.lgm.com.keepalivedemo.service.DownloadService".equals(service.service.getClassName())) {

                   isServiceRunning = true;

               }

           }

           returnisServiceRunning;

       }

    }

    9、寫在最后

    Android P(即Android 9)已于2018年8月7日的正式發布,此版本的Android省電策略等限制,對于APP的后臺保活來說將更為困難。預計2019年Android P將會成為Android設備的主流系統,到那時才是真正噩夢的開始。

    關于Android P在保活方面的問題,請詳細閱讀《Android P正式版即將到來:后臺應用保活、消息推送的真正噩夢》。

    附錄:更多有關IM/推送的心跳保活處理的文章

    應用保活終極總結(一):Android6.0以下的雙進程守護保活實踐

    應用保活終極總結(二):Android6.0及以上的保活實踐(進程防殺篇)

    應用保活終極總結(三):Android6.0及以上的保活實踐(被殺復活篇)

    Android進程保活詳解:一篇文章解決你的所有疑問

    Android端消息推送總結:實現原理、心跳保活、遇到的問題等

    深入的聊聊Android消息推送這件小事

    為何基于TCP協議的移動端IM仍然需要心跳保活機制?

    微信團隊原創分享:Android版微信后臺保活實戰分享(進程保活篇)

    微信團隊原創分享:Android版微信后臺保活實戰分享(網絡保活篇)

    移動端IM實踐:實現Android版微信的智能心跳機制

    移動端IM實踐:WhatsApp、Line、微信的心跳策略分析

    Android P正式版即將到來:后臺應用保活、消息推送的真正噩夢

    全面盤點當前Android后臺保活方案的真實運行效果(截止2019年前)

    >> 更多同類文章 ……

    (本文同步發布于:http://www.52im.net/thread-2176-1-1.html



    作者:Jack Jiang (點擊作者姓名進入Github)
    出處:http://www.52im.net/space-uid-1.html
    交流:歡迎加入即時通訊開發交流群 215891622
    討論:http://www.52im.net/
    Jack Jiang同時是【原創Java Swing外觀工程BeautyEye】【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
    本博文 歡迎轉載,轉載請注明出處(也可前往 我的52im.net 找到我)。


    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    Jack Jiang的 Mail: jb2011@163.com, 聯系QQ: 413980957, 微信: hellojackjiang
    主站蜘蛛池模板: 亚洲精品国产精品乱码视色| 99久久精品免费视频| 免费一级毛片在线播放放视频| 亚洲色大成WWW亚洲女子| 亚洲中文字幕AV在天堂| 亚洲制服丝袜一区二区三区| 91在线亚洲精品专区| 亚洲视频一区在线| 亚洲日韩中文字幕| 亚洲国产成人久久三区| 亚洲一区二区三区高清视频| 亚洲最新中文字幕| 亚洲精品福利你懂| 亚洲综合av一区二区三区不卡| 国产亚洲精品影视在线| 亚洲欧好州第一的日产suv| 亚洲国产精品日韩av不卡在线| 亚洲精品无码专区| 风间由美在线亚洲一区| 性生大片视频免费观看一级| 一区二区三区免费精品视频| 久久久精品视频免费观看 | 亚洲国产精品一区二区第一页免 | 中文在线免费观看| 日本黄色动图免费在线观看| 久久精品毛片免费观看| 黄色片在线免费观看| 免费黄色网址入口| 亚洲国产精品无码久久久久久曰| 亚洲开心婷婷中文字幕| 久久亚洲AV无码精品色午夜 | 亚洲综合伊人久久综合| 亚洲成人动漫在线| 亚洲乱码一区二区三区国产精品| 亚洲日韩一中文字暮| 永久免费无码网站在线观看个| 91免费国产视频| 99久久99久久精品免费观看 | 人妻巨大乳hd免费看| 日本亚洲欧洲免费天堂午夜看片女人员| 67pao强力打造高清免费|