如何快速學會android的四大基礎----Service篇


      很多人都以為,只要學過一點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()方法來做到這點。

      
       這就是既是Started Service又是Bound Service的整個生命周期。

       但我們有時候需要和遠程的進程進行通信,這時就需要使用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的基本內容大概就是這樣,具體的設計問題得到具體的情景才能知道,但萬變不離其宗,只要我們知道基礎,就算一時間解決不了復雜的問題,也可以有個思緒。
     

 

    

    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM