Jack Jiang

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

          本文原作者“minminaya”,作者網(wǎng)站:minminaya.cn,為了提升文章品質(zhì),即時通訊網(wǎng)對內(nèi)容作了幅修訂和改動,感謝原作者。

          1、引言

          對于IM應(yīng)用和消息推送服務(wù)的開發(fā)者來說,在Android機(jī)型上的后臺保活是個相當(dāng)頭疼的問題。

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

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

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

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

          正因?yàn)锳ndroid系統(tǒng)版本的差異,也導(dǎo)致了各種保活黑科技的運(yùn)行效果大相徑庭,所以本文正好借此機(jī)會,盤點(diǎn)一下當(dāng)前主流(截止2019年前)的保活黑科技在市面上各版本Android手機(jī)上的運(yùn)行效果,希望能給大家提供一些客觀的參考。

          學(xué)習(xí)交流:

          - 即時通訊/推送技術(shù)開發(fā)交流4群:101279154 [推薦]

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

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

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

          * 本節(jié)內(nèi)容摘錄自即時通訊網(wǎng)整理的《Android P正式版即將到來:后臺應(yīng)用保活、消息推送的真正噩夢》一文

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

          以消息推送為例,當(dāng)APP處于后臺或關(guān)閉時,消息推送對于某些應(yīng)用來說非常有用,比如:

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

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

          3)SNS社交應(yīng)用:轉(zhuǎn)發(fā)/關(guān)注/贊等通知,典型代表有:微博、知乎;

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

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

          .... ....

          在上述的各種應(yīng)用中,尤其對于用戶接觸最多、最平常的IM聊天應(yīng)用或新聞資訊來說,保活和消息推送簡直事關(guān)APP的“生死”,消息推送這種能力已經(jīng)被越來越多的APP作為基礎(chǔ)能力之一,因?yàn)橐苿踊ヂ?lián)網(wǎng)時代下,用戶的“全時在線”能力非常誘人和強(qiáng)大,能隨時隨地即時地將各種重要信息推送給用戶,無疑是非常有意義的。

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

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

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

          3、相關(guān)文章

          應(yīng)用保活終極總結(jié)(一):Android6.0以下的雙進(jìn)程守護(hù)保活實(shí)踐

          應(yīng)用保活終極總結(jié)(二):Android6.0及以上的保活實(shí)踐(進(jìn)程防殺篇)

          應(yīng)用保活終極總結(jié)(三):Android6.0及以上的保活實(shí)踐(被殺復(fù)活篇)

          Android進(jìn)程保活詳解:一篇文章解決你的所有疑問

          Android端消息推送總結(jié):實(shí)現(xiàn)原理、心跳保活、遇到的問題等

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

          微信團(tuán)隊(duì)原創(chuàng)分享:Android版微信后臺保活實(shí)戰(zhàn)分享(進(jìn)程保活篇)

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

          4、常見的Android端保活黑科技方案盤點(diǎn)

          主要黑科技方案有:

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

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

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

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

          針對上述方案,具體的實(shí)現(xiàn)思路,通常是這樣的:

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

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

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

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

          5)創(chuàng)建子進(jìn)程:通過android:process屬性來為Service創(chuàng)建一個進(jìn)程;

          6)白名單:跳轉(zhuǎn)到系統(tǒng)白名單界面讓用戶自己添加app進(jìn)入白名單。

          5、匯總一下,主要的保活黑科技方案的具體代碼實(shí)現(xiàn)

          5.1 黑科技代碼實(shí)現(xiàn)1:雙進(jìn)程拉活方案的代碼實(shí)現(xiàn)

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

          關(guān)于本方案的具體實(shí)現(xiàn),即時通訊網(wǎng)的以下文章有更詳細(xì)的介紹,您也可以仔細(xì)研讀:

          應(yīng)用保活終極總結(jié)(一):Android6.0以下的雙進(jìn)程守護(hù)保活實(shí)踐

          應(yīng)用保活終極總結(jié)(二):Android6.0及以上的保活實(shí)踐(進(jìn)程防殺篇)

          應(yīng)用保活終極總結(jié)(三):Android6.0及以上的保活實(shí)踐(被殺復(fù)活篇)

          本方案的具體代碼實(shí)現(xiàn),主要由以下4步構(gòu)成。

          1)新建一個AIDL文件:

          KeepAliveConnection

          interfaceKeepAliveConnection  {

          }

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

          /**

          * 主進(jìn)程 雙進(jìn)程通訊

          *

          * @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)對守護(hù)進(jìn)程GuardService進(jìn)行和2一樣的處理:

          /**

          * 守護(hù)進(jìn)程 雙進(jìn)程通訊

          *

          * @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服務(wù)后然后啟動保活的雙進(jìn)程:

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

                 //雙守護(hù)線程,優(yōu)先級不一樣

                 startAllServices();

             }


             @Override

             publicvoidonContentChanged() {

                 super.onContentChanged();

                 mShowTimeTv = findViewById(R.id.tv_show_time);

             }


             @Override

             protectedvoidonDestroy() {

                 super.onDestroy();

                 unbindService(mServiceConnection);

             }


             /**

              * 開啟所有守護(hù)Service

              */

             privatevoidstartAllServices() {

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

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

             }

          }

          5.2 黑科技代碼實(shí)現(xiàn)2:監(jiān)聽到鎖屏廣播后使用“1”像素Activity提升優(yōu)先級

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

          本方法的具體代碼實(shí)現(xiàn)主要由以下6步組成。

          1)該Activity的View只要設(shè)置為1像素然后設(shè)置在Window對象上即可。在Activity的onDestroy周期中進(jìn)行保活服務(wù)的存活判斷從而喚醒服務(wù)。”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)對廣播進(jìn)行監(jiān)聽,封裝為一個ScreenReceiverUtil類,進(jìn)行鎖屏解鎖的廣播動態(tài)注冊監(jiān)聽:

          public class ScreenReceiverUtil {

             privateContext mContext;

             privateSreenBroadcastReceiver mScreenReceiver;

             privateSreenStateListener mStateReceiverListener;


             publicScreenReceiverUtil(Context mContext) {

                 this.mContext = mContext;

             }


             publicvoidsetScreenReceiverListener(SreenStateListener mStateReceiverListener) {

                 this.mStateReceiverListener = mStateReceiverListener;

                 // 動態(tài)啟動廣播接收器

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

             }


             /**

              * 監(jiān)聽sreen狀態(tài)對外回調(diào)接口

              */

             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進(jìn)行防止內(nèi)存泄露的處理,新建一個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);

             }


             /**

              * 結(jié)束SinglePixelActivity

              */

             publicvoidfinishActivity() {

                 if(mActivity != null) {

                     Activity activity = mActivity.get();

                     if(activity != null) {

                         activity.finish();

                     }

                 }

             }

          }

          4)對1像素的Style進(jìn)行特殊處理,在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)在保活服務(wù)類DownloadService中對監(jiān)聽的廣播進(jìn)行注冊和對SinglePixelActivity進(jìn)行控制:

          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, "關(guān)閉了1像素Activity");

                 }


                 @Override

                 publicvoidonSreenOff() {

                     mScreenManager.startActivity();

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

                 }


                 @Override

                 publicvoidonUserPresent() {

                 }

             };

             privateOnTimeChangeListener mOnTimeChangeListener;


             @Override

             publicvoidonCreate() {

                 super.onCreate();


          //        注冊鎖屏廣播監(jiān)聽器

                 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 黑科技代碼實(shí)現(xiàn)3:在后臺播放音樂

          后臺播放音樂這種保活方法,親身經(jīng)歷過:

          記得當(dāng)時用的是某運(yùn)動記步APP,它為了保活就是這么干的。之所以被我發(fā)現(xiàn),是因?yàn)樵谖业腁ndroid手機(jī)上,每次打開這個APP居然總能莫名其妙聽到若有若無的環(huán)境噪音樣的聲音,尤其安靜的場所下更明顯。我個人估計這個APP里用的保活音頻文件,很可能就是程序員在簡陋的條件下隨手自已錄制的,雖然也是不得以為之,但做法確實(shí)是有點(diǎn)粗糙。

          好了,回到正題,本方案的具體代碼實(shí)現(xiàn)主要是以下3步。

          1)準(zhǔn)備一段無聲的音頻,新建一個播放音樂的Service類,將播放模式改為無限循環(huán)播放。在其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,啟動服務(wù)");

                 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, "關(guān)閉后臺播放音樂");

                     mMediaPlayer.stop();

                 }

             }


             @Override

             publicvoidonDestroy() {

                 super.onDestroy();

                 stopPlayMusic();

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

                 // 重啟自己

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

                 startService(intent);

             }

          }

          2)在保活的DownloadServie服務(wù)類的onCreate方法中對PlayerMusicService進(jìn)行啟動:

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

          startService(intent);

          3)在Manifest文件中進(jìn)行注冊:

          <service

                     android:name=".service.PlayerMusicService"

                     android:enabled="true"

                     android:exported="true"

                     android:process=":music_service"/>

          6.4 黑科技代碼實(shí)現(xiàn)4:使用JobScheduler喚醒Service

          本方案代碼實(shí)現(xiàn)由以下3步組成。

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

          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服務(wù)類中進(jìn)行JobScheduler的注冊和使用:

          /**

              * 使用JobScheduler進(jìn)行保活

              */

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

                 //周期設(shè)置為了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文件中進(jìn)行權(quán)限設(shè)置:

          <service

                     android:name=".service.ScheduleService"

                     android:enabled="true"

                     android:exported="true"

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

          7、總結(jié)一下,以上方案在當(dāng)前主流手機(jī)上的運(yùn)行效果

          【1】雙進(jìn)程守護(hù)方案(基于onStartCommand() return START_STICKY):

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

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

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

          4)美圖m8s(7.1.1):一鍵清理直接殺掉整個app,包括雙守護(hù)進(jìn)程。不清理情況下,鎖屏?xí)斜粴⑦^程(9分鐘左右被殺),之后重新復(fù)活,之后不斷被干掉然后又重新復(fù)活。結(jié)論:雙守護(hù)進(jìn)程可在后臺不斷拉起Service;

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

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

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

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

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

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

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

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

          4)原生7.0:同美圖m8s;

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

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

          ▲ 結(jié)論:此方案無效果。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          ▲ 結(jié)論:只對5.0,5.1、6.0起作用。

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

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

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

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

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

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

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

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

          8、補(bǔ)充: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日的正式發(fā)布,此版本的Android省電策略等限制,對于APP的后臺保活來說將更為困難。預(yù)計2019年Android P將會成為Android設(shè)備的主流系統(tǒng),到那時才是真正噩夢的開始。

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

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

          應(yīng)用保活終極總結(jié)(一):Android6.0以下的雙進(jìn)程守護(hù)保活實(shí)踐

          應(yīng)用保活終極總結(jié)(二):Android6.0及以上的保活實(shí)踐(進(jìn)程防殺篇)

          應(yīng)用保活終極總結(jié)(三):Android6.0及以上的保活實(shí)踐(被殺復(fù)活篇)

          Android進(jìn)程保活詳解:一篇文章解決你的所有疑問

          Android端消息推送總結(jié):實(shí)現(xiàn)原理、心跳保活、遇到的問題等

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

          為何基于TCP協(xié)議的移動端IM仍然需要心跳保活機(jī)制?

          微信團(tuán)隊(duì)原創(chuàng)分享:Android版微信后臺保活實(shí)戰(zhàn)分享(進(jìn)程保活篇)

          微信團(tuán)隊(duì)原創(chuàng)分享:Android版微信后臺保活實(shí)戰(zhàn)分享(網(wǎng)絡(luò)保活篇)

          移動端IM實(shí)踐:實(shí)現(xiàn)Android版微信的智能心跳機(jī)制

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

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

          全面盤點(diǎn)當(dāng)前Android后臺保活方案的真實(shí)運(yùn)行效果(截止2019年前)

          >> 更多同類文章 ……

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



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


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


          網(wǎng)站導(dǎo)航:
           
          Jack Jiang的 Mail: jb2011@163.com, 聯(lián)系QQ: 413980957, 微信: hellojackjiang
          主站蜘蛛池模板: 依兰县| 德钦县| 昌江| 嫩江县| 普兰店市| 拉萨市| 莱州市| 乳山市| 易门县| 平乡县| 禹城市| 剑阁县| 潼关县| 棋牌| 文成县| 淳安县| 华安县| 绥芬河市| 信阳市| 德安县| 大连市| 司法| 台中县| 正安县| 淮安市| 灵武市| 文成县| 江孜县| 商丘市| 尼勒克县| 内乡县| 彝良县| 阿拉善盟| 泽库县| 曲阜市| 遂溪县| 安龙县| 鹤庆县| 子洲县| 玉树县| 太白县|