android Service中Thread.sleep不精確

平臺

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

測試過程

正常

  1. 打開Activity
  2. 點擊按鍵啓動服務.
  3. 等待線程執行並打印結果.
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

延遲

  1. 打開Activity
  2. 點擊按鍵啓動服務.
  3. 點擊HOME 鍵退出到桌面.
  4. 等待線程執行並打印結果.
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();
            }
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章