您现在的位置是:主页 > news > java做直播网站/seo排名优化价格
java做直播网站/seo排名优化价格
admin2025/6/19 13:15:10【news】
简介java做直播网站,seo排名优化价格,教育云平台网站建设,四平网站建设问题背景 升级应用程序 targetsdkversion 到8.0 后,发现下面这个问题: 在不升级前,APP退出,后台Service可以存活很长一段时间,但是在升级后,8.0以下版本手机还是可以存活一段很长时间,但是在8…
问题背景
升级应用程序 targetsdkversion 到8.0 后,发现下面这个问题:
在不升级前,APP退出,后台Service可以存活很长一段时间,但是在升级后,8.0以下版本手机还是可以存活一段很长时间,但是在8.0以上版本手机,app 在退出一分钟,后台Service就被杀死了。
杀死后有这么一条日志:
Stopping service due to app idle: u0a309 -1m19s437ms xxx.xxx.xxx/xxx.xxxx.xxxx
原因分析
1: 拿到这个日志,当时的想法是,service 被stopping , 原因是:app 处于空闲了,结合Android中 Service是通过Ams(ActivityManagerService)管理的,下面我们来分析下AMS
2: 一顿分析下来:在AMS中有个函数 updateOomAdjLocked, 该函数有一个 sendMessage操作,代码如下 :可以看到如果 应用程序处于后台,且不在白名单中,会延迟发送一个消息,这个延时消息与常量 BACKGROUND_SETTLE_TIME有关,它就是Service在后台存活的时间,并且默认情况下是 60s
// UID is now in the background (and not on the temp whitelist). Was it
// previously in the foreground (or on the temp whitelist)?
if (!ActivityManager.isProcStateBackground(uidRec.setProcState)|| uidRec.setWhitelist) {uidRec.lastBackgroundTime = nowElapsed;if (!mHandler.hasMessages(IDLE_UIDS_MSG)) {// Note: the background settle time is in elapsed realtime, while// the handler time base is uptime. All this means is that we may// stop background uids later than we had intended, but that only// happens because the device was sleeping so we are okay anyway.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,mConstants.BACKGROUND_SETTLE_TIME);}
}
public long BACKGROUND_SETTLE_TIME = DEFAULT_BACKGROUND_SETTLE_TIME;// 存活时间常量
private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;// 更新存活时间常量
private void updateConstants() {final String setting = Settings.Global.getString(mResolver,Settings.Global.ACTIVITY_MANAGER_CONSTANTS);synchronized (mService) {try {mParser.setString(setting);} catch (IllegalArgumentException e) {// Failed to parse the settings string, log this and move on// with defaults.Slog.e("ActivityManagerConstants", "Bad activity manager config settings", e);}MAX_CACHED_PROCESSES = mParser.getInt(KEY_MAX_CACHED_PROCESSES,DEFAULT_MAX_CACHED_PROCESSES);BACKGROUND_SETTLE_TIME = mParser.getLong(KEY_BACKGROUND_SETTLE_TIME,DEFAULT_BACKGROUND_SETTLE_TIME);...}
}
3:这个常量是被 隐藏的,而且由于是全局,我们不能直接修改这个常量值,我们现在重点放在,BACKGROUND_SETTLE_TIME 这条消息的处理机制
final void idleUids() {synchronized (this) {final int N = mActiveUids.size();if (N <= 0) {return;}final long nowElapsed = SystemClock.elapsedRealtime();final long maxBgTime = nowElapsed - mConstants.BACKGROUND_SETTLE_TIME;long nextTime = 0;if (mLocalPowerManager != null) {mLocalPowerManager.startUidChanges();}for (int i=N-1; i>=0; i--) {final UidRecord uidRec = mActiveUids.valueAt(i);final long bgTime = uidRec.lastBackgroundTime;if (bgTime > 0 && !uidRec.idle) {if (bgTime <= maxBgTime) {EventLogTags.writeAmUidIdle(uidRec.uid);uidRec.idle = true;uidRec.setIdle = true;doStopUidLocked(uidRec.uid, uidRec);} else {if (nextTime == 0 || nextTime > bgTime) {nextTime = bgTime;}}}}if (mLocalPowerManager != null) {mLocalPowerManager.finishUidChanges();}if (nextTime > 0) {mHandler.removeMessages(IDLE_UIDS_MSG);mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed);}}
}
化繁就简,抛去一些判断逻辑,我们具体看看停止服务实际是执行了 doStopUidLocked函数
final void doStopUidLocked(int uid, final UidRecord uidRec) {mServices.stopInBackgroundLocked(uid);enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
}
void stopInBackgroundLocked(int uid) {// Stop all services associated with this uid due to it going to the background// stopped state.ServiceMap services = mServiceMap.get(UserHandle.getUserId(uid));ArrayList<ServiceRecord> stopping = null;if (services != null) {for (int i=services.mServicesByName.size()-1; i>=0; i--) {ServiceRecord service = services.mServicesByName.valueAt(i);if (service.appInfo.uid == uid && service.startRequested) {if (mAm.getAppStartModeLocked(service.appInfo.uid, service.packageName,service.appInfo.targetSdkVersion, -1, false, false)!= ActivityManager.APP_START_MODE_NORMAL) {if (stopping == null) {stopping = new ArrayList<>();}String compName = service.name.flattenToShortString();EventLogTags.writeAmStopIdleService(service.appInfo.uid, compName);StringBuilder sb = new StringBuilder(64);sb.append("Stopping service due to app idle: ");UserHandle.formatUid(sb, service.appInfo.uid);sb.append(" ");TimeUtils.formatDuration(service.createTime- SystemClock.elapsedRealtime(), sb);sb.append(" ");sb.append(compName);Slog.w(TAG, sb.toString());stopping.add(service);}}}if (stopping != null) {for (int i=stopping.size()-1; i>=0; i--) {ServiceRecord service = stopping.get(i);service.delayed = false;services.ensureNotStartingBackgroundLocked(service);stopServiceLocked(service);}}}
}
这里源码可以看到,首先遍历Service ,经过一些条件判断,将满足条件的 service放入到 stopping列表中,然后遍历这个列表,停止service .
这里我们得到了一个小结论:8.0及以上版本手机中有一个机制,app退出后一分钟后会清理后台service(满足条件的),但是foreground service不会。所以在8.0上可以通过foreground service的形式提高存活。
从AMS分析,为什么targetsdkversion 8.0以上会杀死后台服务
我们来看下 AMS中的 getAppStartModeLocked函数
int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,int callingPid, boolean alwaysRestrict, boolean disabledOnly) {UidRecord uidRec = mActiveUids.get(uid);if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid + " pkg="+ packageName + " rec=" + uidRec + " always=" + alwaysRestrict + " idle="+ (uidRec != null ? uidRec.idle : false));if (uidRec == null || alwaysRestrict || uidRec.idle) {boolean ephemeral;if (uidRec == null) {ephemeral = getPackageManagerInternalLocked().isPackageEphemeral(UserHandle.getUserId(uid), packageName);} else {ephemeral = uidRec.ephemeral;}if (ephemeral) {// We are hard-core about ephemeral apps not running in the background.return ActivityManager.APP_START_MODE_DISABLED;} else {if (disabledOnly) {// The caller is only interested in whether app starts are completely// disabled for the given package (that is, it is an instant app). So// we don't need to go further, which is all just seeing if we should// apply a "delayed" mode for a regular app.return ActivityManager.APP_START_MODE_NORMAL;}final int startMode = (alwaysRestrict)? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk): appServicesRestrictedInBackgroundLocked(uid, packageName,packageTargetSdk);if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid+ " pkg=" + packageName + " startMode=" + startMode+ " onwhitelist=" + isOnDeviceIdleWhitelistLocked(uid));if (startMode == ActivityManager.APP_START_MODE_DELAYED) {// This is an old app that has been forced into a "compatible as possible"// mode of background check. To increase compatibility, we will allow other// foreground apps to cause its services to start.if (callingPid >= 0) {ProcessRecord proc;synchronized (mPidsSelfLocked) {proc = mPidsSelfLocked.get(callingPid);}if (proc != null &&!ActivityManager.isProcStateBackground(proc.curProcState)) {// Whoever is instigating this is in the foreground, so we will allow it// to go through.return ActivityManager.APP_START_MODE_NORMAL;}}}return startMode;}}return ActivityManager.APP_START_MODE_NORMAL;
}
这里也有很多判断,我们看首先判断是不是ephemeral apps,短暂应用?这个我没有找到更多的文档,只有一篇说chrome团队开发一款无需下载直接使用的,也不确定就是这里这个。不过我们看注释可以看到,ephemeral apps是完全不允许后台运行的,所以我们的app一定不是ephemeral apps。(这里以后有机会我们再仔细调查一下)继续,disabledOnly在前面的调用可以看到这个参数是false;继续,alwaysRestrict同样参数是false,所以这样startMode就是函数appServicesRestrictedInBackgroundLocked的返回值,这个函数如下:
int appServicesRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {// Persistent app?if (mPackageManagerInt.isPackagePersistent(packageName)) {if (DEBUG_BACKGROUND_CHECK) {Slog.i(TAG, "App " + uid + "/" + packageName+ " is persistent; not restricted in background");}return ActivityManager.APP_START_MODE_NORMAL;}// Non-persistent but background whitelisted?if (uidOnBackgroundWhitelist(uid)) {if (DEBUG_BACKGROUND_CHECK) {Slog.i(TAG, "App " + uid + "/" + packageName+ " on background whitelist; not restricted in background");}return ActivityManager.APP_START_MODE_NORMAL;}// Is this app on the battery whitelist?if (isOnDeviceIdleWhitelistLocked(uid)) {if (DEBUG_BACKGROUND_CHECK) {Slog.i(TAG, "App " + uid + "/" + packageName+ " on idle whitelist; not restricted in background");}return ActivityManager.APP_START_MODE_NORMAL;}// None of the service-policy criteria apply, so we apply the common criteriareturn appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);
}
1:这里有三个判断,是否Persistent app;是否在允许后台运行白名单;是否在省电(耗电)白名单。我们看都是返回ActivityManager.APP_START_MODE_NORMAL
2:回到开始的判断我们知道当不等于ActivityManager.APP_START_MODE_NORMAL时,才会将service放入stopping列表,所以这三种情况都不会停掉service
看到这里我们就知道了,如果上面三个条件都不满足的化,该应用程序就会加入到 stopping列表
int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {// Apps that target O+ are always subject to background checkif (packageTargetSdk >= Build.VERSION_CODES.O) {if (DEBUG_BACKGROUND_CHECK) {Slog.i(TAG, "App " + uid + "/" + packageName + " targets O+, restricted");}return ActivityManager.APP_START_MODE_DELAYED_RIGID;}// ...and legacy apps get an AppOp checkint appop = mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,uid, packageName);if (DEBUG_BACKGROUND_CHECK) {Slog.i(TAG, "Legacy app " + uid + "/" + packageName + " bg appop " + appop);}switch (appop) {case AppOpsManager.MODE_ALLOWED:return ActivityManager.APP_START_MODE_NORMAL;case AppOpsManager.MODE_IGNORED:return ActivityManager.APP_START_MODE_DELAYED;default:return ActivityManager.APP_START_MODE_DELAYED_RIGID;}
}
1:终于看到我们寻找的了,第一段代码就可以看到当targetsdkversion大于等于Build.VERSION_CODES.O = 26,即8.0时,返回
ActivityManager.APP_START_MODE_DELAYED_RIGID2:当targetsdkversion小于26,会检查app是否有后台运行的权限AppOpsManager.OP_RUN_IN_BACKGROUND,如果有权限则返回ActivityManager.APP_START_MODE_NORMAL,则根据前面的判断不会停止服务。
如何采取措施避免在 targetsdkversion 8.0系统以上杀死后台服务
那么我们从上面三个判断着手来规避这种情况
1:Persistent :实际上在Manifest中,我们可以为application设置android:persistent=”true”,但是前提是系统应用,也就是说我们第三方应用设置这个也没效果。关于Persistent app我们以后另开一篇文章细说。
2:是否允许后台允许:我们来看看 uidOnBackgroundWhitelist代码
private boolean uidOnBackgroundWhitelist(final int uid) {final int appId = UserHandle.getAppId(uid);final int[] whitelist = mBackgroundAppIdWhitelist;final int N = whitelist.length;for (int i = 0; i < N; i++) {if (appId == whitelist[i]) {return true;}}return false;
}
再来看看 mBackgroundAppIdWhitelist赋值,在backgroundWhitelistUid函数中:
第一行代码就明确表明了,只能系统应用使用这个方法,所以我们知道第三方应用无法使用这个白名单。
@Override
public void backgroundWhitelistUid(final int uid) {if (Binder.getCallingUid() != Process.SYSTEM_UID) {throw new SecurityException("Only the OS may call backgroundWhitelistUid()");}if (DEBUG_BACKGROUND_CHECK) {Slog.i(TAG, "Adding uid " + uid + " to bg uid whitelist");}synchronized (this) {final int N = mBackgroundAppIdWhitelist.length;int[] newList = new int[N+1];System.arraycopy(mBackgroundAppIdWhitelist, 0, newList, 0, N);newList[N] = UserHandle.getAppId(uid);mBackgroundAppIdWhitelist = newList;}
}
总结
这样我们基本上弄清楚8.0上service的存活机制了,按顺序经历下面几个判断
1、是否后台service,如果是foreground service则不停,否则继续
2、是否在临时白名单中,如果是则不停,否则继续
3、是否是ephemeral apps,如果是则停,否则继续
4、是否是Persistent app,如果是则不停,否则继续
5、是否在允许后台运行白名单,在则不停,否则继续
6、是否在省电白名单,在则不停,否则继续
7、是否targetsdkversion大于等于26,是则停,否则继续
8、如果targetsdkversion小于26,是否有OP_RUN_IN_BACKGROUND权限,有则不停,否则停
而在8.0上,service的停止则有一分钟的延迟。
那么如果升级了targetsdkversion,怎么才能让后台service存活?
这里有几个条件不需要考虑了,如:
临时白名单用于调试;Persistent app需要系统app;后台运行白名单也需要系统app
那么剩下就是:开启前台服务,省电白名单了
修改方案
方案一:开启前台服务
开启前台Service, 会在通知栏显示 Notification
if (!CallingStateListener.isCallingStateListener()) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {startForegroundService(Intent(this, CallingStateListener::class.java))} else {startService(Intent(this, CallingStateListener::class.java))}}//关闭监听电话状态服务if (CallingStateListener.isCallingStateListener()) {stopService(Intent(this, CallingStateListener::class.java))}
class CallingStateListener : Service() {private val TAG = "CallingStateListener"private var phoneStateListener: PhoneStateListener? = nullprivate var telephonyManager: TelephonyManager? = nullprivate val notificationId = "callingChannelId"private val notificationName = "callingChannelName"override fun onCreate() {super.onCreate()initCallingStateListener()val notificationManager =getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager//创建NotificationChannelif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {val channel = NotificationChannel(notificationId,notificationName,NotificationManager.IMPORTANCE_HIGH)notificationManager.createNotificationChannel(channel)}startForeground(1, getNotification())}override fun onBind(intent: Intent?): IBinder? {return null}private fun getNotification(): Notification {val builder = Notification.Builder(this).setSmallIcon(R.mipmap.ic_launcher).setContentTitle("通话服务").setContentText("服务正在运行")//设置Notification的ChannelID,否则不能正常显示if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {builder.setChannelId(notificationId)}return builder.build()}}
方案二:APP加入省电白名单
1: 首先使用:PowerManager.isIgnoringBatteryOptimizations判断是否有已经加入了
private boolean isIgnoringBatteryOptimizations(){if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {String packageName = getPackageName();PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);return pm.isIgnoringBatteryOptimizations(packageName);}return false;
}
2:如果未加入,则需要通 Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS拉起请求弹窗
private void gotoSettingIgnoringBatteryOptimizations() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {try {Intent intent = new Intent();String packageName = getPackageName();intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);intent.setData(Uri.parse("package:" + packageName));startActivityForResult(intent, REQUEST_IGNORE_BATTERY_CODE);} catch (Exception e) {e.printStackTrace();}}
}
3:添加权限
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
4:然后再 onActivityResult中处理结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if(resultCode == RESULT_OK){if (requestCode == REQUEST_IGNORE_BATTERY_CODE) {Log.d("Hello World!","开启省电模式成功");}}else if (resultCode == RESULT_CANCELED) {if (requestCode == REQUEST_IGNORE_BATTERY_CODE) {Toast.makeText(this, "请用户开启忽略电池优化~", Toast.LENGTH_LONG).show();}}
}