很多人都以為,只要學過一點java就可以馬上寫android應用了,這種想法的產生非常自然,因為現在網上有那么多的android開源實例,只要跟着來,也能夠自己寫一個播放器。但是,只有去寫一個真正投入使用的android應用的人才會明白,一個完整的android應用的誕生並不是一件簡單的事情,就算是一個播放器,考慮到在線音源,無損音源等等其他東西,也會變得很復雜,單是界面這塊,就已經讓人崩潰了:android有那么多的版本,要做到各個版本的界面都是一樣的,需要一點功夫,如果單純依賴基本組件,到時每個版本的界面都會不統一的。更可怕的是,橫屏和豎屏時的界面怎么辦?各個組件之間的布局呢?。。。界面是一件頭疼的問題,如何保證這些界面能夠流暢的切換呢?界面和Activity之間的數據交換呢?MVP模式就像MVC一樣,也是需要我們去研究的東西。
一句話:應用可不僅僅只是能夠用就行,還要有人用,用得好,並且是沒有任何問題的在用。
再復雜的東西,也是由小東西一點點搭建起來。所以,打好基礎非常重要。
android中有四大組件:Activity,Service,BroadcastReceiver和ContentProvider。其中Service級別上跟Activity其實差不多,只是Service只能在后台運行,適合那些不需要界面的操作,像是播放音樂或者監聽動作等,因為它的名字就已經提示了:它就是一個服務。
Service同樣也是運行在主線程中,所以不能用它來做耗時的請求或者動作,否則就會阻塞住主線程。如果真的要這么做,可以跟Activity一樣的做法:新開一個線程。
Service根據啟動方式分為兩類:Started和Bound。其中,Started()是通過startService()來啟動,主要用於程序內部使用的Service,而Bound是通過bindService()來啟動,允許多個應用程序共享同一個Service。
Service的生命周期不像Activity那樣復雜,當我們通過Context.startService()啟動Service的時候,系統就會調用Service的onCreate(),接着就是onStartCommand(Intent, int, int),過去這個方法是onStart(),但現在onStart()已經不被鼓勵使用了,onStartCommand()里面的代碼就是我們Service所要執行的操作,接着就是onDestroy(),關閉Service。
我們也可以通過Context.bindService()來啟動,系統同樣會調用Service的onCreate(),接着並不是onStartCommand()而是onBind(),它會將多個客戶端綁定到同一個服務中。如果我們想要停止Service,必須先對客戶端解綁,也就是調用onUnbind(),然后就是onDestroy()。

這就是Service的大概生命周期。
既然啟動Service有兩種方式,那么我們應該選擇哪一個呢?如果單單只是為了使用Service,兩種方式都可以,但正如我們上面所看到的,Bound啟動的Service可以允許多個應用程序綁定到Service,所以,如果該Service是多個程序共享的,必須使用Bound來啟動Service。
我們還可以將Service聲明為應用程序的私有Service,這樣就可以阻止其他應用獲取該服務。
就像Activity,當我們在應用程序中使用自定義的Service的時候,我們必須在manifest中聲明該Service。就像這樣:
<application> <service android:name=".MyService"/> </application>
就像Activity一樣,我們可以定義Service的Intent Filters,這樣其他應用程序就可以通過Intent來使用該Service。如果沒有定義Intent Filters,默認下是私有的,無法訪問,當然,我們也可以顯示的指定該訪問權限:android:exported=false。
使用Service的時候必須注意,Service是在主線程中運行的,所以任何阻塞或者耗時操作都必須在Service中新開一個Thread。
接下來我們就來創建一個簡單的Service:MyService,用於在后台播放MP3。這是介紹Service時經常用到的例子。
public class MyService extends Service { MediaPlayer mediaPlayer = null; @Override public IBinder onBind(Intent arg0) { return null; } @Override public void onCreate() { if (mediaPlayer == null) { mediaPlayer = MediaPlayer.create(this, uri); // uri 為要播放的歌曲的路徑 super.onCreate(); } } @Override public int onStartCommand(Intent intent, int flags, int startId) { mediaPlayer.start(); return START_STICKY; } @Override public void onDestroy() { mediaPlayer.stop(); super.onDestroy(); } }
由於我們是通過Started啟動Service,所以onBind()返回的是NULL。
其實,onStartCommand()中的代碼是我們Service主要的操作,它會在每次Service運行的時候被調用,而onCreate()是當Service第一次被創建的時候才會被調用,當Service已經在運行的時候,不會調用該方法,可以將一些初始化的操作放在這里。
定義好Service后,我們就可以在Activity中啟動該Service:
Intent intent = new Intent(this, MyService.class); startService(intent);
這就像是在Activity中跳轉到另一個Activity一樣。
看上去Service就好像是在Activity中新開一個Thread一樣,事實上,這兩者也是挺像的,那么,我們是如何確定我們需要的是Thread還是Service?
如果是當用戶使用我們的應用程序,像是點擊按鈕的時候,才執行一個在主線程外面的操作,我們需要的就是一個Thread而不是Service。
還是我們上面的例子,如果我們想要播放音樂是在我們應用程序運行的時候,那么我們可以再onCreate()中新建一個Thread,然后在onStartCommand()中開始該Thread,接着是在onStop()中停止該Thread。這樣的做法非常自然,是一般的新手都會想到的,但是android鼓勵我們使用AsyncTask或者HandlerThread來處理這個過程。關於這個話題,我們還是放在其他文章里講吧,畢竟這是一個不小的話題。
當我們開啟了一個Service,我們就有義務去決定什么時候關閉該Service。可以通過stopSelf()讓Service自己關閉自己或者調用stopService()來關閉該Service。
當內存不夠的時候,系統也會自動關閉Service。這時系統是如何選擇哪些Service應該被關閉呢?如果一個Service被綁定到Activity並且得到用戶的焦點,那么它就很少可能會被關閉,但如果它是被聲明運行在前台,它就永遠也不可能會被關閉。
知道這個事實對我們有什么用呢?當然是考慮如果內存足夠的時候我們如何重啟Service。
解決這個問題的關鍵就在於onStartCommand()的返回值。
onStartCommand()指定要求返回一個整數值,這個整數值描述的就是系統應該以何種方式來重啟該Service,一共有下面三種方式:
1.START_NOT_STICKY:系統在關閉Service后,不需要重新創建該Service,除非我們傳遞Intent要求創建Service。這是避免不必要的Service運行的最安全的方式,因為我們可以自己指定什么時候重新啟動該Service。
2.START_STICKY:要求重新創建Service並且系統會通過一個空的Intent來調用onStartCommand()除非我們傳遞Intent。我們看到,上面的例子就使用了該返回值,這樣當音樂播放被停止后,重新啟動的時候就會重新播放音樂。
3.START_REDELIVER_INTENT:這種方式會通過關閉前的Intent來調用onStartCommand()。它非常適合像是下載文件這類的操作,這樣當重新啟動的時候,就會繼續之前的下載而不會丟失之前的下載進度。
關閉Service有兩種方式:stopSelf()和stopService(),它們是有區別的,而且區別非常大。如果我們在一個應用程序中開啟了多個Service,那么我們就不能貿然的使用stopService()來關閉Service,因為前一個Service的關閉可能會影響到后面Service的使用。這時我們就需要使用stopSelf(int startId)來決定關閉哪個Service。
關閉Service是非常重要的,這對於減少內存消耗和電量消耗來說,都是一件好事。
Service是在后台運行的,但是有時候我們需要向用戶發送一些提示,像是下載完成之類的,這時就可以通過Toast或者Status Bar了。
我們上面提到,Service可以運行在前台,這點就非常奇怪了:Service不是后台運行的嗎?其實不然,有時候我們也想要清楚的知道Service的進度,像是下載文件的進度或者歌曲的播放進度。這種Service一般都會在android手機上顯示"正在進行的"的提示框里可以看到(就是我們手機上可以拉下來的那個框)。
這種Service一般都是通過Status Bar來提示進度,只有Service完成工作后才會消失:
Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text), System.currentTimeMillis()); Intent notificationIntent = new Intent(this, MyService.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); notification.setLatestEventInfo(this, getText(R.string.notification_title), getText(R.string.notification_message), pendingIntent); startForeground(ONGOING_NOTIFICATION_ID, notification);
關鍵的方法就是startForeground(),它需要兩個參數:唯一標識notification的id(不能為0)以及Status Bar的Notification。
當然,我們可以通過stopForeground()方法來停止Service在前台的顯示,該方法並不會停止該Service。
要想使用Started Service,我們除了繼承自Service之外,還可以繼承自另一個類:IntentService。
IntentService是Service的子類。我們一般都是一個Intent就開啟一個Service,但是IntentService是專門有一個線程來處理所有的開啟請求,每次處理一個。這是我們需要在應用程序中開啟多個Service但又想避免多線程的最佳選擇。
public class MyService extends IntentService { MediaPlayer mediaPlayer = null; public MyService() { super("MyService"); } @Override protected void onHandleIntent(Intent intent) { if (mediaPlayer == null) { mediaPlayer = MediaPlayer.create(this, uri); }// uri 為要播放的歌曲的路徑 mediaPlayer.start(); } }
和繼承自Service不一樣,我們需要一個構造器,該構造器的主要作用就是為工作線程命名。
我們僅需要覆寫onHandleIntent()一個方法,因為該方法會為我們處理所有的Intent,並且會在處理完畢后關閉該Service。
IntentService就像是一個封裝好的Service,方便我們處理多個Intent的情況,但如果我們想要覆寫其他方法,也是可以的,但要確保每個方法最后都有調用super的實現。
接下來我們要講的就是Bound Service。
要想創建Bound Service,我們就必須定義一個IBinder,它用於說明客戶端是如何和服務通信的。Bound Service是一個非常大的話題,因為它涉及到本地服務還有遠程服務。我們先從簡單的本地服務開始:
public class LocalService extends Service { // Binder given to clients private final IBinder mBinder = new LocalBinder(); // Random number generator private final Random mGenerator = new Random(); /** * Class used for the client Binder. Because we know this service always * runs in the same process as its clients, we don't need to deal with IPC. */ public class LocalBinder extends Binder { LocalService getService() { // Return this instance of LocalService so clients can call public methods return LocalService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } /** method for clients */ public int getRandomNumber() { return mGenerator.nextInt(100); } }
這個例子非常簡單,就是為每個綁定到該服務的客戶產生一個0-100的隨機數。
我們首先必須提供一個IBinder的實現類,該類返回的是我們Service的一個實例,這是為了方便客戶調用Service的公共方法,接着我們在onBind()方法中返回這個IBinder的實現類。
這種做法適合Service只在應用程序內部共享,我們可以這樣使用這個Service:
public class BindingActivity extends Activity { LocalService mService; boolean mBound = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override protected void onStart() { super.onStart(); // Bind to LocalService Intent intent = new Intent(this, LocalService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); // Unbind from the service if (mBound) { unbindService(mConnection); mBound = false; } } /** Called when a button is clicked (the button in the layout file attaches to * this method with the android:onClick attribute) */ public void onButtonClick(View v) { if (mBound) { // Call a method from the LocalService. // However, if this call were something that might hang, then this request should // occur in a separate thread to avoid slowing down the activity performance. int num = mService.getRandomNumber(); Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show(); } } /** Defines callbacks for service binding, passed to bindService() */ private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { // We've bound to LocalService, cast the IBinder and get LocalService instance LocalBinder binder = (LocalBinder) service; mService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { mBound = false; } }; }
我們通過bindService()將Activity和Service綁定到一起,接着必須定義一個ServiceConnection,為的就是通過綁定的IBinder的getService()方法來獲得Service,然后我們再調用Service的getRandomNumber()。使用完后,我們需要通過unbindService()來解除綁定。
這里充分利用了回調的價值。
這種方式僅僅適合客戶和服務都在同一個應用程序和一個進程內,像是音樂程序的后台播放。
結合我們上面有關Started Service的討論,我們知道,可以用兩種方式來開啟服務,StartedService完全可以變成Bound Service,這樣我們就不用顯式的關閉服務,當沒有任何客戶和該Service綁定的時候,系統會自動的關閉該Service。但如果我們在一個Started Service中也同樣使用了onBind()呢?像是這樣的情況:我們在一個音樂播放程序中利用Started Service開啟音樂播放,然后用戶離開程序后,音樂會在后台播放,當用戶重新返回到程序中時,Activity可以綁定到Service上以便對音樂進行控制。我們可以使用onRebind()方法來做到這點。

但我們有時候需要和遠程的進程進行通信,這時就需要使用Messenger,這是為了實現進程間通信但又不想使用AIDL的唯一方式。
我們還是先來個簡單例子:
public class MessengerService extends Service { /** Command to the service to display a message */ static final int MSG_SAY_HELLO = 1; /** * Handler of incoming messages from clients. */ class IncomingHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_SAY_HELLO: Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show(); break; default: super.handleMessage(msg); } } } /** * Target we publish for clients to send messages to IncomingHandler. */ final Messenger mMessenger = new Messenger(new IncomingHandler()); /** * When binding to the service, we return an interface to our messenger * for sending messages to the service. */ @Override public IBinder onBind(Intent intent) { Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show(); return mMessenger.getBinder(); } }
可以看到,我們必須先在Service里面實現一個Handler,然后將這個Handler傳遞給Messenger,然后在onBind()中返回的是該Messenger的getBinder()的返回值。
public class ActivityMessenger extends Activity { /** Messenger for communicating with the service. */ Messenger mService = null; /** Flag indicating whether we have called bind on the service. */ boolean mBound; /** * Class for interacting with the main interface of the service. */ private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // This is called when the connection with the service has been // established, giving us the object we can use to // interact with the service. We are communicating with the // service using a Messenger, so here we get a client-side // representation of that from the raw IBinder object. mService = new Messenger(service); mBound = true; } public void onServiceDisconnected(ComponentName className) { // This is called when the connection with the service has been // unexpectedly disconnected -- that is, its process crashed. mService = null; mBound = false; } }; public void sayHello(View v) { if (!mBound) return; // Create and send a message to the service, using a supported 'what' value Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0); try { mService.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override protected void onStart() { super.onStart(); // Bind to the service bindService(new Intent(this, MessengerService.class), mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); // Unbind from the service if (mBound) { unbindService(mConnection); mBound = false; } } }
Activity所要做的就是闖將一個Messenger,該Messenger擁有Service返回的IBinder,接着就是發送消息給Service,然后Service中的Handler根據消息進行處理。我們可以看到,Messenger之所以能夠實現進程間的通信,靠的還是Handler。
如果是為實現進程間的通信,我們可以使用Android Interface Definition Language(AIDL)。這個是個大話題,它體現的是一種編程思想,就是將對象分解成操作系統能夠理解和執行的基元,像是上面的Messenger,實際上也是建立在AIDL的基礎上。Messenger實際上是在一個單獨的線程里創建客戶的請求隊列,所以Service每次只能接收到一個請求。但是,如果我們想要我們的Service能夠處理多個請求,那么我們可以直接使用AIDL,也就是說,我們的Service就會成為多線程,相應的線程安全問題也隨之而來。這真的是一件麻煩事!
要使用AIDL,我們必須建立.aidl文件,然后在這個文件中定義我們的接口,Android SDK工具會使用該文件去創建一個abstract class去實現這個接口,並且處理IPC,我們唯一要做的就是繼承這個abstract class。
這就是遠程代理模式的運用。
關於AIDL,這里不會涉及到太多東西,因為大部分的程序是不鼓勵使用的,畢竟它是多線程的,很容易出現問題,而且如果我們的程序出現多線程,基本上可以認定,我們的程序設計是有問題的。
Service的基本內容大概就是這樣,具體的設計問題得到具體的情景才能知道,但萬變不離其宗,只要我們知道基礎,就算一時間解決不了復雜的問題,也可以有個思緒。