Android應(yīng)用?;顚?shí)踐詳解
最近在做的項(xiàng)目中需要app在后臺(tái)常駐,用于實(shí)時(shí)上傳一些健康信息數(shù)據(jù),便于后臺(tái)實(shí)時(shí)查看用戶的健康狀況。自從Android7.0以上后臺(tái)常駐實(shí)現(xiàn)越來越難,尤其是8.0及以上。關(guān)于?;畹奈恼卤缺冉允?,但是效果并不理想,關(guān)于?;畹姆椒ㄒ簿统Uf的哪幾種,重點(diǎn)在于怎么組合運(yùn)用。最終實(shí)現(xiàn)效果為:用戶不主動(dòng)強(qiáng)制殺死的話,能夠一直存活(小米,華為,vivo,oppo,三星)。其中三星s8,華為nova2s用戶強(qiáng)制殺死也能存活。
項(xiàng)目結(jié)構(gòu)
常見的?;罘桨?/strong>
關(guān)于Android應(yīng)用?;畹奈恼潞芏啵@里不再闡述,可自行百度。重點(diǎn)在于運(yùn)用這樣方案來實(shí)現(xiàn)?;罟δ堋?/p>
代碼實(shí)現(xiàn)
1.監(jiān)聽鎖屏廣播,開啟1個(gè)像素的Activity。
在鎖屏的時(shí)候啟動(dòng)一個(gè)1個(gè)像素的Activity,當(dāng)用戶解鎖以后將這個(gè)Activity結(jié)束掉。
定義一個(gè)1像素的Activity,在該Activity中動(dòng)態(tài)注冊(cè)自定義的廣播。
class OnePixelActivity : AppCompatActivity() { private lateinit var br: BroadcastReceiver override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //設(shè)定一像素的activity val window = window window.setGravity(Gravity.LEFT or Gravity.TOP) val params = window.attributes params.x = 0 params.y = 0 params.height = 1 params.width = 1 window.attributes = params //在一像素activity里注冊(cè)廣播接受者 接受到廣播結(jié)束掉一像素 br = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { finish() } } registerReceiver(br, IntentFilter("finish activity")) checkScreenOn() } override fun onResume() { super.onResume() checkScreenOn() } override fun onDestroy() { try { //銷毀的時(shí)候解鎖廣播 unregisterReceiver(br) } catch (e: IllegalArgumentException) { } super.onDestroy() } /** * 檢查屏幕是否點(diǎn)亮 */ private fun checkScreenOn() { val pm = this@OnePixelActivity.getSystemService(Context.POWER_SERVICE) as PowerManager val isScreenOn = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { pm.isInteractive } else { pm.isScreenOn } if (isScreenOn) { finish() } } }
2.雙進(jìn)程守護(hù)
定義一個(gè)本地服務(wù),在該服務(wù)中播放無聲音樂,并綁定遠(yuǎn)程服務(wù)。
class LocalService : Service() { private var mediaPlayer: MediaPlayer? = null private var mBilder: MyBilder? = null override fun onCreate() { super.onCreate() if (mBilder == null) { mBilder = MyBilder() } } override fun onBind(intent: Intent): IBinder? { return mBilder } override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { //播放無聲音樂 if (mediaPlayer == null) { mediaPlayer = MediaPlayer.create(this, R.raw.novioce) //聲音設(shè)置為0 mediaPlayer?.setVolume(0f, 0f) mediaPlayer?.isLooping = true//循環(huán)播放 play() } //啟用前臺(tái)服務(wù),提升優(yōu)先級(jí) if (KeepLive.foregroundNotification != null) { val intent2 = Intent(applicationContext, NotificationClickReceiver::class.java) intent2.action = NotificationClickReceiver.CLICK_NOTIFICATION val notification = NotificationUtils.createNotification(this, KeepLive.foregroundNotification!!.getTitle(), KeepLive.foregroundNotification!!.getDescription(), KeepLive.foregroundNotification!!.getIconRes(), intent2) startForeground(13691, notification) } //綁定守護(hù)進(jìn)程 try { val intent3 = Intent(this, RemoteService::class.java) this.bindService(intent3, connection, Context.BIND_ABOVE_CLIENT) } catch (e: Exception) { } //隱藏服務(wù)通知 try { if (Build.VERSION.SDK_INT < 25) { startService(Intent(this, HideForegroundService::class.java)) } } catch (e: Exception) { } if (KeepLive.keepLiveService != null) { KeepLive.keepLiveService!!.onWorking() } return Service.START_STICKY } private fun play() { if (mediaPlayer != null && !mediaPlayer!!.isPlaying) { mediaPlayer?.start() } } private inner class MyBilder : GuardAidl.Stub() { @Throws(RemoteException::class) override fun wakeUp(title: String, discription: String, iconRes: Int) { } } private val connection = object : ServiceConnection { override fun onServiceDisconnected(name: ComponentName) { val remoteService = Intent(this@LocalService, RemoteService::class.java) this@LocalService.startService(remoteService) val intent = Intent(this@LocalService, RemoteService::class.java) this@LocalService.bindService(intent, this, Context.BIND_ABOVE_CLIENT) } override fun onServiceConnected(name: ComponentName, service: IBinder) { try { if (mBilder != null && KeepLive.foregroundNotification != null) { val guardAidl = GuardAidl.Stub.asInterface(service) guardAidl.wakeUp(KeepLive.foregroundNotification?.getTitle(), KeepLive.foregroundNotification?.getDescription(), KeepLive.foregroundNotification!!.getIconRes()) } } catch (e: RemoteException) { e.printStackTrace() } } } override fun onDestroy() { super.onDestroy() unbindService(connection) if (KeepLive.keepLiveService != null) { KeepLive.keepLiveService?.onStop() } } }
定義一個(gè)遠(yuǎn)程服務(wù),綁定本地服務(wù)。
class RemoteService : Service() { private var mBilder: MyBilder? = null override fun onCreate() { super.onCreate() if (mBilder == null) { mBilder = MyBilder() } } override fun onBind(intent: Intent): IBinder? { return mBilder } override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { try { this.bindService(Intent(this@RemoteService, LocalService::class.java), connection, Context.BIND_ABOVE_CLIENT) } catch (e: Exception) { } return Service.START_STICKY } override fun onDestroy() { super.onDestroy() unbindService(connection) } private inner class MyBilder : GuardAidl.Stub() { @Throws(RemoteException::class) override fun wakeUp(title: String, discription: String, iconRes: Int) { if (Build.VERSION.SDK_INT < 25) { val intent = Intent(applicationContext, NotificationClickReceiver::class.java) intent.action = NotificationClickReceiver.CLICK_NOTIFICATION val notification = NotificationUtils.createNotification(this@RemoteService, title, discription, iconRes, intent) this@RemoteService.startForeground(13691, notification) } } } private val connection = object : ServiceConnection { override fun onServiceDisconnected(name: ComponentName) { val remoteService = Intent(this@RemoteService, LocalService::class.java) this@RemoteService.startService(remoteService) this@RemoteService.bindService(Intent(this@RemoteService, LocalService::class.java), this, Context.BIND_ABOVE_CLIENT) } override fun onServiceConnected(name: ComponentName, service: IBinder) {} } } /** * 通知欄點(diǎn)擊廣播接受者 */ class NotificationClickReceiver : BroadcastReceiver() { companion object { const val CLICK_NOTIFICATION = "CLICK_NOTIFICATION" } override fun onReceive(context: Context, intent: Intent) { if (intent.action == NotificationClickReceiver.CLICK_NOTIFICATION) { if (KeepLive.foregroundNotification != null) { if (KeepLive.foregroundNotification!!.getForegroundNotificationClickListener() != null) { KeepLive.foregroundNotification!!.getForegroundNotificationClickListener()?.foregroundNotificationClick(context, intent) } } } } }
3.JobScheduler
JobScheduler和JobService是安卓在api 21中增加的接口,用于在某些指定條件下執(zhí)行后臺(tái)任務(wù)。
定義一個(gè)JobService,開啟本地服務(wù)和遠(yuǎn)程服務(wù)
@SuppressWarnings(value = ["unchecked", "deprecation"]) @RequiresApi(Build.VERSION_CODES.LOLLIPOP) class JobHandlerService : JobService() { private var mJobScheduler: JobScheduler? = null override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { var startId = startId startService(this) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mJobScheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler val builder = JobInfo.Builder(startId++, ComponentName(packageName, JobHandlerService::class.java.name)) if (Build.VERSION.SDK_INT >= 24) { builder.setMinimumLatency(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS) //執(zhí)行的最小延遲時(shí)間 builder.setOverrideDeadline(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS) //執(zhí)行的最長(zhǎng)延時(shí)時(shí)間 builder.setMinimumLatency(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS) builder.setBackoffCriteria(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS, JobInfo.BACKOFF_POLICY_LINEAR)//線性重試方案 } else { builder.setPeriodic(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS) } builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) builder.setRequiresCharging(true) // 當(dāng)插入充電器,執(zhí)行該任務(wù) mJobScheduler?.schedule(builder.build()) } return Service.START_STICKY } private fun startService(context: Context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (KeepLive.foregroundNotification != null) { val intent = Intent(applicationContext, NotificationClickReceiver::class.java) intent.action = NotificationClickReceiver.CLICK_NOTIFICATION val notification = NotificationUtils.createNotification(this, KeepLive.foregroundNotification!!.getTitle(), KeepLive.foregroundNotification!!.getDescription(), KeepLive.foregroundNotification!!.getIconRes(), intent) startForeground(13691, notification) } } //啟動(dòng)本地服務(wù) val localIntent = Intent(context, LocalService::class.java) //啟動(dòng)守護(hù)進(jìn)程 val guardIntent = Intent(context, RemoteService::class.java) startService(localIntent) startService(guardIntent) } override fun onStartJob(jobParameters: JobParameters): Boolean { if (!isServiceRunning(applicationContext, "com.xiyang51.keeplive.service.LocalService") || !isServiceRunning(applicationContext, "$packageName:remote")) { startService(this) } return false } override fun onStopJob(jobParameters: JobParameters): Boolean { if (!isServiceRunning(applicationContext, "com.xiyang51.keeplive.service.LocalService") || !isServiceRunning(applicationContext, "$packageName:remote")) { startService(this) } return false } private fun isServiceRunning(ctx: Context, className: String): Boolean { var isRunning = false val activityManager = ctx .getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager val servicesList = activityManager .getRunningServices(Integer.MAX_VALUE) val l = servicesList.iterator() while (l.hasNext()) { val si = l.next() if (className == si.service.className) { isRunning = true } } return isRunning } }
4.播放無聲音樂
這里使用的是有聲的mp3文件,只是在代碼中把聲音設(shè)置成了0;如果使用真正的無聲的音樂文件,在oppo手機(jī)上按下返回鍵會(huì)被立刻殺死,并且在三星手機(jī),華為nova2s強(qiáng)制殺死也會(huì)被殺死,所有使用了有聲的文件。
5.提高Service優(yōu)先級(jí)
在 onStartCommand()
方法中開啟一個(gè)通知,提高進(jìn)程的優(yōu)先級(jí)。注意:從Android 8.0(API級(jí)別26)開始,所有通知必須要分配一個(gè)渠道,對(duì)于每個(gè)渠道,可以單獨(dú)設(shè)置視覺和聽覺行為。然后用戶可以在設(shè)置中修改這些設(shè)置,根據(jù)應(yīng)用程序來決定哪些通知可以顯示或者隱藏。
定義一個(gè)通知工具類,兼容8.0
class NotificationUtils(context: Context) : ContextWrapper(context) { private var manager: NotificationManager? = null private var id: String = context.packageName + "51" private var name: String = context.packageName private var context: Context = context private var channel: NotificationChannel? = null companion object { @SuppressLint("StaticFieldLeak") private var notificationUtils: NotificationUtils? = null fun createNotification(context: Context, title: String, content: String, icon: Int, intent: Intent): Notification? { if (notificationUtils == null) { notificationUtils = NotificationUtils(context) } var notification: Notification? = null notification = if (Build.VERSION.SDK_INT >= 26) { notificationUtils?.createNotificationChannel() notificationUtils?.getChannelNotification(title, content, icon, intent)?.build() } else { notificationUtils?.getNotification_25(title, content, icon, intent)?.build() } return notification } } @RequiresApi(api = Build.VERSION_CODES.O) fun createNotificationChannel() { if (channel == null) { channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_MIN) channel?.enableLights(false) channel?.enableVibration(false) channel?.vibrationPattern = longArrayOf(0) channel?.setSound(null, null) getManager().createNotificationChannel(channel) } } private fun getManager(): NotificationManager { if (manager == null) { manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } return manager!! } @RequiresApi(api = Build.VERSION_CODES.O) fun getChannelNotification(title: String, content: String, icon: Int, intent: Intent): Notification.Builder { //PendingIntent.FLAG_UPDATE_CURRENT 這個(gè)類型才能傳值 val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) return Notification.Builder(context, id) .setContentTitle(title) .setContentText(content) .setSmallIcon(icon) .setAutoCancel(true) .setContentIntent(pendingIntent) } fun getNotification_25(title: String, content: String, icon: Int, intent: Intent): NotificationCompat.Builder { val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) return NotificationCompat.Builder(context, id) .setContentTitle(title) .setContentText(content) .setSmallIcon(icon) .setAutoCancel(true) .setVibrate(longArrayOf(0)) .setSound(null) .setLights(0, 0, 0) .setContentIntent(pendingIntent) } }
使用
將?;畹墓δ芊庋b成了一個(gè)單獨(dú)的庫(kù),依賴該庫(kù)即可。
app中使用:
KeepLive.startWork(this, KeepLive.RunMode.ROGUE, ForegroundNotification("Title", "message", R.mipmap.ic_launcher, object : ForegroundNotificationClickListener { override fun foregroundNotificationClick(context: Context, intent: Intent) { //點(diǎn)擊通知回調(diào) } }), object : KeepLiveService { override fun onStop() { //可能調(diào)用多次,跟onWorking匹配調(diào)用 } override fun onWorking() { //一直存活,可能調(diào)用多次 } })
清單文件配置:
<!--權(quán)限配置--> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.GET_TASKS" /> <uses-permission android:name="android.permission.REORDER_TASKS" /> <!--保活相關(guān)配置--> <receiver android:name="com.xiyang51.keeplive.receiver.NotificationClickReceiver" /> <activity android:name="com.xiyang51.keeplive.activity.OnePixelActivity" /> <service android:name="com.xiyang51.keeplive.service.LocalService" /> <service android:name="com.xiyang51.keeplive.service.HideForegroundService" /> <service android:name="com.xiyang51.keeplive.service.JobHandlerService" android:permission="android.permission.BIND_JOB_SERVICE" /> <service android:name="com.xiyang51.keeplive.service.RemoteService" android:process=":remote" />
代碼地址 github
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android 中Crash時(shí)如何獲取異常信息詳解及實(shí)例
這篇文章主要介紹了Android 中Crash時(shí)如何獲取異常信息詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-02-02android webview 簡(jiǎn)單瀏覽器實(shí)現(xiàn)代碼
android webview 簡(jiǎn)單瀏覽器實(shí)現(xiàn)代碼,需要的朋友可以參考一下2013-05-05Android手勢(shì)ImageView三部曲 第一部
這篇文章主要為大家詳細(xì)介紹了Android手勢(shì)ImageView三部曲的第一部,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03Android實(shí)現(xiàn)Service下載文件,Notification顯示下載進(jìn)度的示例
本篇文章主要介紹了Android實(shí)現(xiàn)Service下載文件,Notification顯示下載進(jìn)度,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-01-01android中okhttp實(shí)現(xiàn)斷點(diǎn)上傳示例
本篇文章主要介紹了android中okhttp實(shí)現(xiàn)斷點(diǎn)上傳示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02Android的WebView與H5前端JS代碼交互的實(shí)例代碼
本篇文章主要介紹了Android的WebView與H5前端JS代碼交互的實(shí)例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-07-07Flutter下Android Studio配置gradle的方法
這篇文章主要介紹了Flutter下Android Studio配置gradle的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04Android編程實(shí)現(xiàn)切換imageView的方法分析
這篇文章主要介紹了Android編程實(shí)現(xiàn)切換imageView的方法,結(jié)合具體實(shí)例形式分析了切換imageView的相關(guān)設(shè)置技巧與注意事項(xiàng),需要的朋友可以參考下2017-09-09