2015年2月27日 星期五

[Quick Note] Android JobScheduler

Android API 21 新加入了 JobScheduler API 以及他的朋友們,目的就如字面上的意思,可以允許開發者排定一個定期工作,取代原有的 Alarm Manager + Broadcast Receiver 方式。

我第一眼看到 API 是覺得還蠻新奇的啦
setPersisted():開機後可以繼續活著
setRequiredNetworkType(), setRequiresCharging(), setRequiresDeviceIdle()喔喔!這個不錯,終於可以設定條件了嗎?!
雖說可以依照電量或機子狀態做選擇,但效能部分呢?會不會只是 Alarm Manager 的再包裝版呢?

先來看看常見的 Alarm Manager + Broadcast Receiver 方法:
  1. App 必須要開一個 Service,持續運行在背景
  2. Service 要註冊一個 Alarm Broadcast Receiver,接收 Alarm Service 的broadcast
  3. 向 Alarm Service 請求定期發送 broadcast
缺點一當然就是彈性不夠,不像 JobScheduler 可以設定條件,但我認為最致命的就是你必須開一個 Service 持續在背景運行。以使用者的角度,有一個東西在背景佔記憶體當然不怎麼高興,以開發者的角度,最糟的情況是如果機子記憶體很小,你的 Service 很容易因為 Low Memory 被殺掉了。

那 JobScheduler 怎麼做呢?Reference 這樣講過:
When the criteria declared are met, the system will execute this job on your application's JobService.
基本上我們邏輯的部份還是要繼承 JobService 呀,至於 JobScheduler 的 schedule(JobInfo) 吃的 JobInfo 事實上只是個加上資訊的包裝罷了,因為我們在調用 JobInfo.Builder(int, ComponentName) 時,還是要傳入 ComponentName 這個需要 JobService 建構的參數呀。
稍微翻了一下原始碼,終於在 android/server/job/JobServiceContext.java(看來他的邏輯還是簡單地劃出一個執行的 context) 找到了實際執行的函式。
142    /**
143     * Give a job to this context for execution. Callers must first check {@link #isAvailable()}
144     * to make sure this is a valid context.
145     * @param job The status of the job that we are going to run.
146     * @return True if the job is valid and is running. False if the job cannot be executed.
147     */
148    boolean executeRunnableJob(JobStatus job) {
149        synchronized (mLock) {
150            if (!mAvailable) {
151                Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
152                return false;
153            }
154
155            mRunningJob = job;
156            mParams = new JobParameters(this, job.getJobId(), job.getExtras(),
157                    !job.isConstraintsSatisfied());
158            mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
159
160            mVerb = VERB_BINDING;
161            scheduleOpTimeOut();
162            final Intent intent = new Intent().setComponent(job.getServiceComponent());
163            boolean binding = mContext.bindServiceAsUser(intent, this,
164                    Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
165                    new UserHandle(job.getUserId()));
166            if (!binding) {
167                if (DEBUG) {
168                    Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
169                }
170                mRunningJob = null;
171                mParams = null;
172                mExecutionStartTimeElapsed = 0L;
173                removeOpTimeOut();
174                return false;
175            }
176            try {
177                mBatteryStats.noteJobStart(job.getName(), job.getUid());
178            } catch (RemoteException e) {
179                // Whatever.
180            }
181            mAvailable = false;
182            return true;
183        }
184    }
第162~165行就是大部份的答案了:JobScheduler 還是會生出一個 Service 的,但這個 Service 很特殊,也就是 JobService,有 onStartJob(), onStopJob(),不會一直在後台運行的,我個人感覺,JobService 比較像定義一個 task,在對的時間點執行,執行完 task 就結束,讓它比傳統需要一直開 Service 然後等 alarm 的方法更加省資源,也比較直觀。
    那這樣每一個工作就要 spawn 一個 process 的做法,代價是不是有點高呀?畢竟創造進程不是一件小事。
    我也有想過說要不要讓開發者可以把輕量級的工作丟到一個進程,在一個進程裡統一執行。
    恩...我是認為由於 Android App 的模型裡,App 進程已經跟 zygote 共用很多 address space 資源了,因為不管是 activity 還是 service 都是從 zygote fork 過來的,所以會比較像創一個 thread 而不是 process,代價比較輕一些。至於統一執行的部分,實作上會用像是 v8 isolate 的概念,在同一個進程裡區分不同工作的 context,但我覺得不會這樣做是因為 android 的 sandBox system 是用獨立出 process + uid / pid 等方式來做隔離,嘛...遵照一下現有的安全模型還是比較統一啊。


沒有留言:

張貼留言