平台
RK3288 + Android 7.1
问题
在测试Thread.sleep过程中发现, 当App进入后台后, 服务中的Thread.sleep会有不同程度的精确度丢失.
测试sleep 2ms, 当置于后台时, 实际延迟达到 [10 - 40] ms
相关测试代码:
|-- AndroidManifest.xml
<application
android:allowBackup="true"
android:icon="@mipmap/ic_factory_test"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:directBootAware="true">
<activity android:name="MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<service android:name="NormalService"/>
</application>
|-- layout/main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:id="@+id/btStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/start"/>
</LinearLayout>
|-- MainActivity.java
public class MainActivity extends Activity implements View.OnClickListener {
boolean serviceStarted = false;
Button btStart;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.service_test);
btStart = (Button)findViewById(R.id.btStart);
btStart.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if(view.getId() == R.id.btStart){
if(serviceStarted){
btStart.setText(R.string.start);
stopService(new Intent(this, NormalService.class));
serviceStarted = false;
}else{
btStart.setText(R.string.stop);
startService(new Intent(this, NormalService.class));
serviceStarted = true;
}
}
}
}
|-- NormalService.java
public class NormalService extends Service {
final String TAG = "NormalService";
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
testSleep();
return super.onStartCommand(intent, flags, startId);
}
void testSleep(){
new Thread(TAG){
@Override
public void run() {
try{
sleep(5000);
for(int i = 0; i < 30; i ++){
sleep(2);
Global.d(TAG, "testSleep");
}
}catch(Exception e){}
}
}.start();
}
}
测试过程
正常
- 打开Activity
- 点击按键启动服务.
- 等待线程执行并打印结果.
2019-05-09 13:55:42.295 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.298 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.301 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.305 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.308 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.312 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.315 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.318 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.322 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.325 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.328 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.332 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.336 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.339 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.342 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.346 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.349 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.352 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.356 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.359 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.362 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.366 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.369 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.371 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.374 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.377 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.381 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.383 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.386 D/NormalService: ALog > testSleep
2019-05-09 13:55:42.389 D/NormalService: ALog > testSleep
延迟
- 打开Activity
- 点击按键启动服务.
- 点击HOME 键退出到桌面.
- 等待线程执行并打印结果.
2019-05-09 13:53:49.313 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.354 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.396 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.440 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.459 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.501 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.544 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.560 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.572 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.583 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.596 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.606 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.618 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.629 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.641 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.653 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.664 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.676 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.723 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.746 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.758 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.769 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.781 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.793 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.805 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.816 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.828 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.839 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.851 D/NormalService: ALog > testSleep
2019-05-09 13:53:49.862 D/NormalService: ALog > testSleep
解决方法
以上问题是后台进程的CPU调度优先级降低导致.
关于CPU调度请参考:
随笔之Android平台上的进程调度探讨-https://blog.csdn.net/innost/article/details/6940136
[RK3288][Android6.0] CPU频率调度策略小结-https://blog.csdn.net/kris_fei/article/details/78016996
解决方法可参考:
Android Foreground Service (前台服务)-http://www.cnblogs.com/renhui/p/8575299.html
关键代码:
// 参数一:唯一的通知标识;参数二:通知消息。
startForeground(110, notification);// 开始前台服务
附源码相关
|-- frameworks/base/core/java/android/app/Service.java
public final void startForeground(int id, Notification notification) {
try {
mActivityManager.setServiceForeground(
new ComponentName(this, mClassName), mToken, id,
notification, 0);
} catch (RemoteException ex) {
}
}
|-- frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
@Override
public void setServiceForeground(ComponentName className, IBinder token,
int id, Notification notification, int flags) {
synchronized(this) {
mServices.setServiceForegroundLocked(className, token, id, notification, flags);
}
}
|-- frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
public void setServiceForegroundLocked(ComponentName className, IBinder token,
int id, Notification notification, int flags) {
final int userId = UserHandle.getCallingUserId();
final long origId = Binder.clearCallingIdentity();
try {
ServiceRecord r = findServiceLocked(className, token, userId);
if (r != null) {
if (id != 0) {
if (notification == null) {
throw new IllegalArgumentException("null notification");
}
if (r.foregroundId != id) {
cancelForegroudNotificationLocked(r);
r.foregroundId = id;
}
notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
r.foregroundNoti = notification;
r.isForeground = true;
r.postNotification();
if (r.app != null) {
updateServiceForegroundLocked(r.app, true);
}
getServiceMap(r.userId).ensureNotStartingBackground(r);
mAm.notifyPackageUse(r.serviceInfo.packageName,
PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE);
} else {
if (r.isForeground) {
r.isForeground = false;
if (r.app != null) {
mAm.updateLruProcessLocked(r.app, false, null);
updateServiceForegroundLocked(r.app, true);
}
}
if ((flags & Service.STOP_FOREGROUND_REMOVE) != 0) {
cancelForegroudNotificationLocked(r);
r.foregroundId = 0;
r.foregroundNoti = null;
} else if (r.appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
r.stripForegroundServiceFlagFromNotification();
if ((flags & Service.STOP_FOREGROUND_DETACH) != 0) {
r.foregroundId = 0;
r.foregroundNoti = null;
}
}
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
private void updateServiceForegroundLocked(ProcessRecord proc, boolean oomAdj) {
boolean anyForeground = false;
for (int i=proc.services.size()-1; i>=0; i--) {
ServiceRecord sr = proc.services.valueAt(i);
if (sr.isForeground) {
anyForeground = true;
break;
}
}
mAm.updateProcessForegroundLocked(proc, anyForeground, oomAdj);
}
|-- frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
boolean oomAdj) {
if (isForeground != proc.foregroundServices) {
proc.foregroundServices = isForeground;
ArrayList<ProcessRecord> curProcs = mForegroundPackages.get(proc.info.packageName,
proc.info.uid);
if (isForeground) {
if (curProcs == null) {
curProcs = new ArrayList<ProcessRecord>();
mForegroundPackages.put(proc.info.packageName, proc.info.uid, curProcs);
}
if (!curProcs.contains(proc)) {
curProcs.add(proc);
mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_FOREGROUND_START,
proc.info.packageName, proc.info.uid);
}
} else {
if (curProcs != null) {
if (curProcs.remove(proc)) {
mBatteryStatsService.noteEvent(
BatteryStats.HistoryItem.EVENT_FOREGROUND_FINISH,
proc.info.packageName, proc.info.uid);
if (curProcs.size() <= 0) {
mForegroundPackages.remove(proc.info.packageName, proc.info.uid);
}
}
}
}
if (oomAdj) {
updateOomAdjLocked();
}
}
}