Android Service 是與 Activity 同位階的元件,不同於 Activity 的地方在於 Service 是在背景執行,沒有 UI 可以操作,所以不像 Activity 會有 onPause(...) 或 onResume(...) 的情況,Service 只有 onCreate(...)、onDestroy(...) 與負責工作的 onStartCommand(...),因此當 Activity 被關掉時,可以將未完成的工作交給 Service 執行。
Android Service 的生命週期獨立於 Activity,就是說,關閉 Activity 不會一併關閉 Service。
Android Service 可以分成 Local 與 Remote 兩種,Local Service 只能由同一 App 存取,而 Remote Service 則可以由同一 Android 裡的所有 App 存取。
非常重要的一點是,Android Service 預設是在 Main thread 裡執行,所以 Service 不可以執行費時的工作,否則 UI 會停住,因此 Service 若要執行費時的工作,得另起 thread 去處理;跑在 Main Thread 的 Service 因此可以透過 Activity 來修改 UI,而另起 Thread 後就不行了。
Local Service 適合用在寄送 Email 這類費時且不可因 Activity 關閉而停止的工作,而 Remote Service 則適合負責多個 App 都需要的功能,也就是 code reuse 的概念。
Local Service 與 Remote Service 都是透過實做 Service,差別在於 Remote Service 得實做 onBind(...),而 Local Service 不用,因為 Local Service 是透過 Context.startService(...) 手動啟動,而 Remote Service 則是由系統呼叫 onBind(...) 來自動啟動,但是千萬不可以將一個 Service 同時實做 Local Service 與 Remote Service,生或週期不同會錯亂。
Local Service
Local Service 透過手動呼叫 Context.startService(...) 啟動,然後呼叫 Context.stopService(...) 或者 Service 本身呼叫 stopSelf() 來停止。
Local Service 只會有一個 instance,所以第一次啟動時,會產生 instance 並呼叫 onStartCommand(...),第二次以後呼叫,就直接呼叫 onStartCommand(...),不會再產生新的 instance。
main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <EditText android:id="@+id/et" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:onClick="startLocalService" android:text="開始" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:onClick="stopLocalService" android:text="停止" /> </LinearLayout>LocalService
public class LocalService extends Service { private static final String TAG = "LocalService"; public static final String KEY = "key"; private ThreadGroup threads; private NotificationManager nMgr; private int threadId = 1; @Override public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate...只會執行一次"); // 方便在 onDestroy 一次殺掉所有 Thread this.threads = new ThreadGroup("LocalService"); // Service 沒有 UI 可以互動,只能透過 Notification this.nMgr = (NotificationManager) this.getSystemService(NOTIFICATION_SERVICE); this.notify(0, "Local Service 執行中..."); } @Override public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); String key = intent.getExtras().getString(LocalService.KEY); this.notify(this.threadId, "Local Service " + key + "開始"); Log.d(TAG, "onStartCommand...可以多次呼叫 - " + key); Log.d(TAG, "啟動 Thread " + key); new Thread(this.threads, new CounterThread(this, this.threadId, key), "LocalService").start(); this.threadId++; return START_STICKY; } @Override public void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy..."); Log.d(TAG, "停止所有 Local Service 啟動的 Thread"); this.threads.interrupt(); Log.d(TAG, "清空所有通知訊息"); this.nMgr.cancelAll(); Log.d(TAG, "onDestroyed"); } public void notify(int id, String msg) { Notification n = new Notification(R.drawable.ic_launcher, msg, System.currentTimeMillis()); // 設為不可清除,以防被清掉就回不來了 n.flags = Notification.FLAG_NO_CLEAR; PendingIntent pi = PendingIntent.getActivity(this, 1, new Intent(this, LocalServiceActivity.class), 0); n.setLatestEventInfo(this, "Local Service 通知", msg, pi); // 使用同一 id 來覆蓋(更新)前一訊息,所以永遠只會有一個通知訊息 this.nMgr.notify(id, n); } @Override public IBinder onBind(Intent intent) { Log.d(TAG, "Remote Service 專用,Local Service 不用"); return null; } }CounterThread
public class CounterThread implements Runnable { private static final String TAG = "CounterThread"; private LocalService service; private int threadId; private String key; private int cnt; public CounterThread(LocalService service, int threadId, String key) { super(); this.service = service; this.threadId = threadId; this.key = key; Log.d(TAG, "建立 Thread - " + this.key); } @Override public void run() { while (this.cnt < 5) { this.cnt++; Log.d(TAG, "Thread " + key + " - " + this.cnt); this.service.notify(this.threadId, "Thread " + key + " - " + this.cnt); try { Thread.sleep(3 * 1000); } catch (InterruptedException e) { Log.e(TAG, "Thread " + key + " 被中斷了!"); this.service.notify(this.threadId, "Thread " + key + " 被中斷了!"); return; } } Log.d(TAG, "Thread " + key + " 完成了"); this.service.notify(this.threadId, "Thread " + key + " 完成了"); return; } }LocalServiceActivity
public class LocalServiceActivity extends Activity { private static final String TAG = "LocalServiceActivity"; private EditText et; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.et = (EditText) this.findViewById(R.id.et); } public void startLocalService(View v) { String key = this.et.getText().toString(); Log.d(TAG, "開始 " + key + " ..."); Intent it = new Intent(this, LocalService.class); it.putExtra(LocalService.KEY, key); this.startService(it); } public void stopLocalService(View v) { Log.d(TAG, "全部收工"); boolean ok = this.stopService(new Intent(this, LocalService.class)); if (ok) { Log.d(TAG, "順利收工"); } else { Log.e(TAG, "收工不順"); } } @Override public void onDestroy() { super.onDestroy(); // 很重要,一定要有,不然 Thread 會跑到暴肝 this.stopLocalService(null); } }AndroidManifest.xml
<application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:label="@string/app_name" android:name=".LocalServiceActivity" android:launchMode="singleTop"> <intent-filter > <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name="LocalService"></service> </application>
沒有留言:
張貼留言