天天看點

Android Service完全解析,關于服務你所需知道的一切

轉載自:http://blog.csdn.net/guolin_blog/article/details/11952435

http://blog.csdn.net/guolin_blog/article/details/9797169

相信大多數朋友對Service這個名詞都不會陌生,沒錯,一個老練的Android程式員如果連Service都沒聽說過的話,那确實也太遜了。Service作為Android四大元件之一,在每一個應用程式中都扮演着非常重要的角色。它主要用于在背景處理一些耗時的邏輯,或者去執行某些需要長期運作的任務。必要的時候我們甚至可以在程式退出的情況下,讓Service在背景繼續保持運作狀态。

不過,雖然Service幾乎被每一個Android程式員所熟知,但并不是每個人都已經将Service的各個知識點都掌握得非常透徹。那麼今天我就将帶着大家對Service進行一次全面、深入的探究,希望每個人在讀完本篇文章後都能對Service有更深一層的了解。

Service的基本用法

關于Service最基本的用法自然就是如何啟動一個Service了,啟動Service的方法和啟動Activity很類似,都需要借助Intent來實作,下面我們就通過一個具體的例子來看一下。

建立一個Android項目,項目名就叫ServiceTest,這裡我選擇使用4.0的API。

然後建立一個MyService繼承自Service,并重寫父類的onCreate()、onStartCommand()和onDestroy()方法,如下所示:

public class MyService extends Service {  

        public static final String TAG = "MyService";  

        @Override  
        public void onCreate() {  
            super.onCreate();  
            Log.d(TAG, "onCreate() executed");  
        }  

        @Override  
        public int onStartCommand(Intent intent, int flags, int startId) {  
            Log.d(TAG, "onStartCommand() executed");  
            return super.onStartCommand(intent, flags, startId);  
        }  

        @Override  
        public void onDestroy() {  
            super.onDestroy();  
            Log.d(TAG, "onDestroy() executed");  
        }  

        @Override  
        public IBinder onBind(Intent intent) {  
            return null;  
        }  

    }  
           

可以看到,我們隻是在onCreate()、onStartCommand()和onDestroy()方法中分别列印了一句話,并沒有進行其它任何的操作。

然後打開或建立activity_main.xml作為程式的主布局檔案,代碼如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent"  
        android:orientation="vertical" >  

        <Button  
            android:id="@+id/start_service"  
            android:layout_width="match_parent"  
            android:layout_height="wrap_content"  
            android:text="Start Service" />  

        <Button  
            android:id="@+id/stop_service"  
            android:layout_width="match_parent"  
            android:layout_height="wrap_content"  
            android:text="Stop Service" />  

    </LinearLayout>  
           

我們在布局檔案中加入了兩個按鈕,一個用于啟動Service,一個用于停止Service。

然後打開或建立MainActivity作為程式的主Activity,在裡面加入啟動Service和停止Service的邏輯,代碼如下所示:

public class MainActivity extends Activity implements OnClickListener {  

        private Button startService;  

        private Button stopService;  

        @Override  
        protected void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.activity_main);  
            startService = (Button) findViewById(R.id.start_service);  
            stopService = (Button) findViewById(R.id.stop_service);  
            startService.setOnClickListener(this);  
            stopService.setOnClickListener(this);  
        }  

        @Override  
        public void onClick(View v) {  
            switch (v.getId()) {  
            case R.id.start_service:  
                Intent startIntent = new Intent(this, MyService.class);  
                startService(startIntent);  
                break;  
            case R.id.stop_service:  
                Intent stopIntent = new Intent(this, MyService.class);  
                stopService(stopIntent);  
                break;  
            default:  
                break;  
            }  
        }  

    }  
           

可以看到,在Start Service按鈕的點選事件裡,我們建構出了一個Intent對象,并調用startService()方法來啟動MyService。然後在Stop Serivce按鈕的點選事件裡,我們同樣建構出了一個Intent對象,并調用stopService()方法來停止MyService。代碼的邏輯非常簡單,相信不需要我再多做解釋了吧。

另外需要注意,項目中的每一個Service都必須在AndroidManifest.xml中注冊才行,是以還需要編輯AndroidManifest.xml檔案,代碼如下所示:

<?xml version="1.0" encoding="utf-8"?>  
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
        package="com.example.servicetest"  
        android:versionCode="1"  
        android:versionName="1.0" >  

        <uses-sdk  
            android:minSdkVersion="14"  
            android:targetSdkVersion="17" />  

        <application  
            android:allowBackup="true"  
            android:icon="@drawable/ic_launcher"  
            android:label="@string/app_name"  
            android:theme="@style/AppTheme" >  

        ……  

            <service android:name="com.example.servicetest.MyService" >  
            </service>  
        </application>  

    </manifest>  
           

這樣的話,一個簡單的帶有Service功能的程式就寫好了,現在我們将程式運作起來,并點選一下Start Service按鈕,可以看到LogCat的列印日志如下:

也就是說,當啟動一個Service的時候,會調用該Service中的onCreate()和onStartCommand()方法。

那麼如果我再點選一次Start Service按鈕呢?這個時候的列印日志如下:

可以看到,這次隻有onStartCommand()方法執行了,onCreate()方法并沒有執行,為什麼會這樣呢?這是由于onCreate()方法隻會在Service第一次被建立的時候調用,如果目前Service已經被建立過了,不管怎樣調用startService()方法,onCreate()方法都不會再執行。是以你可以再多點選幾次Start Service按鈕試一次,每次都隻會有onStartCommand()方法中的列印日志。

我們還可以到手機的應用程式管理界面來檢查一下MyService是不是正在運作,如下圖所示:

恩,MyService确實是正在運作的,即使它的内部并沒有執行任何的邏輯。

回到ServiceTest程式,然後點選一下Stop Service按鈕就可以将MyService停止掉了。

Service和Activity通信

上面我們學習了Service的基本用法,啟動Service之後,就可以在onCreate()或onStartCommand()方法裡去執行一些具體的邏輯了。不過這樣的話Service和Activity的關系并不大,隻是Activity通知了Service一下:“你可以啟動了。”然後Service就去忙自己的事情了。那麼有沒有什麼辦法能讓它們倆的關聯更多一些呢?比如說在Activity中可以指定讓Service去執行什麼任務。當然可以,隻需要讓Activity和Service建立關聯就好了。

觀察MyService中的代碼,你會發現一直有一個onBind()方法我們都沒有使用到,這個方法其實就是用于和Activity建立關聯的,修改MyService中的代碼,如下所示:

public class MyService extends Service {  

        public static final String TAG = "MyService";  

        private MyBinder mBinder = new MyBinder();  

        @Override  
        public void onCreate() {  
            super.onCreate();  
            Log.d(TAG, "onCreate() executed");  
        }  

        @Override  
        public int onStartCommand(Intent intent, int flags, int startId) {  
            Log.d(TAG, "onStartCommand() executed");  
            return super.onStartCommand(intent, flags, startId);  
        }  

        @Override  
        public void onDestroy() {  
            super.onDestroy();  
            Log.d(TAG, "onDestroy() executed");  
        }  

        @Override  
        public IBinder onBind(Intent intent) {  
            return mBinder;  
        }  

        class MyBinder extends Binder {  

            public void startDownload() {  
                Log.d("TAG", "startDownload() executed");  
                // 執行具體的下載下傳任務  
            }  

        }  

    }  
           

這裡我們新增了一個MyBinder類繼承自Binder類,然後在MyBinder中添加了一個startDownload()方法用于在背景執行下載下傳任務,當然這裡并不是真正地去下載下傳某個東西,隻是做個測試,是以startDownload()方法隻是列印了一行日志。

然後修改activity_main.xml中的代碼,在布局檔案中添加用于綁定Service和取消綁定Service的按鈕:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent"  
        android:orientation="vertical" >  

        <Button  
            android:id="@+id/start_service"  
            android:layout_width="match_parent"  
            android:layout_height="wrap_content"  
            android:text="Start Service" />  

        <Button  
            android:id="@+id/stop_service"  
            android:layout_width="match_parent"  
            android:layout_height="wrap_content"  
            android:text="Stop Service" />  

        <Button  
            android:id="@+id/bind_service"  
            android:layout_width="match_parent"  
            android:layout_height="wrap_content"  
            android:text="Bind Service" />  

        <Button   
            android:id="@+id/unbind_service"  
            android:layout_width="match_parent"  
            android:layout_height="wrap_content"  
            android:text="Unbind Service"  
            />  

    </LinearLayout>  
           

接下來再修改MainActivity中的代碼,讓MainActivity和MyService之間建立關聯,代碼如下所示:

public class MainActivity extends Activity implements OnClickListener {  

        private Button startService;  

        private Button stopService;  

        private Button bindService;  

        private Button unbindService;  

        private MyService.MyBinder myBinder;  

        private ServiceConnection connection = new ServiceConnection() {  

            @Override  
            public void onServiceDisconnected(ComponentName name) {  
            }  

            @Override  
            public void onServiceConnected(ComponentName name, IBinder service) {  
                myBinder = (MyService.MyBinder) service;  
                myBinder.startDownload();  
            }  
        };  

        @Override  
        protected void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.activity_main);  
            startService = (Button) findViewById(R.id.start_service);  
            stopService = (Button) findViewById(R.id.stop_service);  
            bindService = (Button) findViewById(R.id.bind_service);  
            unbindService = (Button) findViewById(R.id.unbind_service);  
            startService.setOnClickListener(this);  
            stopService.setOnClickListener(this);  
            bindService.setOnClickListener(this);  
            unbindService.setOnClickListener(this);  
        }  

        @Override  
        public void onClick(View v) {  
            switch (v.getId()) {  
            case R.id.start_service:  
                Intent startIntent = new Intent(this, MyService.class);  
                startService(startIntent);  
                break;  
            case R.id.stop_service:  
                Intent stopIntent = new Intent(this, MyService.class);  
                stopService(stopIntent);  
                break;  
            case R.id.bind_service:  
                Intent bindIntent = new Intent(this, MyService.class);  
                bindService(bindIntent, connection, BIND_AUTO_CREATE);  
                break;  
            case R.id.unbind_service:  
                unbindService(connection);  
                break;  
            default:  
                break;  
            }  
        }  

    }  
           

可以看到,這裡我們首先建立了一個ServiceConnection的匿名類,在裡面重寫了onServiceConnected()方法和onServiceDisconnected()方法,這兩個方法分别會在Activity與Service建立關聯和解除關聯的時候調用。在onServiceConnected()方法中,我們又通過向下轉型得到了MyBinder的執行個體,有了這個執行個體,Activity和Service之間的關系就變得非常緊密了。現在我們可以在Activity中根據具體的場景來調用MyBinder中的任何public方法,即實作了Activity指揮Service幹什麼Service就去幹什麼的功能。

當然,現在Activity和Service其實還沒關聯起來了呢,這個功能是在Bind Service按鈕的點選事件裡完成的。可以看到,這裡我們仍然是建構出了一個Intent對象,然後調用bindService()方法将Activity和Service進行綁定。bindService()方法接收三個參數,第一個參數就是剛剛建構出的Intent對象,第二個參數是前面建立出的ServiceConnection的執行個體,第三個參數是一個标志位,這裡傳入BIND_AUTO_CREATE表示在Activity和Service建立關聯後自動建立Service,這會使得MyService中的onCreate()方法得到執行,但onStartCommand()方法不會執行。

然後如何我們想解除Activity和Service之間的關聯怎麼辦呢?調用一下unbindService()方法就可以了,這也是Unbind Service按鈕的點選事件裡實作的邏輯。

現在讓我們重新運作一下程式吧,在MainActivity中點選一下Bind Service按鈕,LogCat裡的列印日志如下圖所示:

另外需要注意,任何一個Service在整個應用程式範圍内都是通用的,即MyService不僅可以和MainActivity建立關聯,還可以和任何一個Activity建立關聯,而且在建立關聯時它們都可以擷取到相同的MyBinder執行個體。

如何銷毀Service

在Service的基本用法這一部分,我們介紹了銷毀Service最簡單的一種情況,點選Start Service按鈕啟動Service,再點選Stop Service按鈕停止Service,這樣MyService就被銷毀了,可以看到列印日志如下所示:

那麼如果我們是點選的Bind Service按鈕呢?由于在綁定Service的時候指定的标志位是BIND_AUTO_CREATE,說明點選Bind Service按鈕的時候Service也會被建立,這時應該怎麼銷毀Service呢?其實也很簡單,點選一下Unbind Service按鈕,将Activity和Service的關聯解除就可以了。

先點選一下Bind Service按鈕,再點選一下Unbind Service按鈕,列印日志如下所示:

以上這兩種銷毀的方式都很好了解。那麼如果我們既點選了Start Service按鈕,又點選了Bind Service按鈕會怎麼樣呢?這個時候你會發現,不管你是單獨點選Stop Service按鈕還是Unbind Service按鈕,Service都不會被銷毀,必要将兩個按鈕都點選一下,Service才會被銷毀。也就是說,點選Stop Service按鈕隻會讓Service停止,點選Unbind Service按鈕隻會讓Service和Activity解除關聯,一個Service必須要在既沒有和任何Activity關聯又處理停止狀态的時候才會被銷毀。

為了證明一下,我們在Stop Service和Unbind Service按鈕的點選事件裡面加入一行列印日志:

public void onClick(View v) {  
        switch (v.getId()) {  
        case R.id.start_service:  
            Intent startIntent = new Intent(this, MyService.class);  
            startService(startIntent);  
            break;  
        case R.id.stop_service:  
            Log.d("MyService", "click Stop Service button");  
            Intent stopIntent = new Intent(this, MyService.class);  
            stopService(stopIntent);  
            break;  
        case R.id.bind_service:  
            Intent bindIntent = new Intent(this, MyService.class);  
            bindService(bindIntent, connection, BIND_AUTO_CREATE);  
            break;  
        case R.id.unbind_service:  
            Log.d("MyService", "click Unbind Service button");  
            unbindService(connection);  
            break;  
        default:  
            break;  
        }  
    }  
           

然後重新運作程式,先點選一下Start Service按鈕,再點選一下Bind Service按鈕,這樣就将Service啟動起來,并和Activity建立了關聯。然後點選Stop Service按鈕後Service并不會銷毀,再點選一下Unbind Service按鈕,Service就會銷毀了,列印日志如下所示:

我們應該始終記得在Service的onDestroy()方法裡去清理掉那些不再使用的資源,防止在Service被銷毀後還會有一些不再使用的對象仍占用着記憶體。

Service和Thread的關系

不少Android初學者都可能會有這樣的疑惑,Service和Thread到底有什麼關系呢?什麼時候應該用Service,什麼時候又應該用Thread?答案可能會有點讓你吃驚,因為Service和Thread之間沒有任何關系!

之是以有不少人會把它們聯系起來,主要就是因為Service的背景概念。Thread我們大家都知道,是用于開啟一個子線程,在這裡去執行一些耗時操作就不會阻塞主線程的運作。而Service我們最初了解的時候,總會覺得它是用來處理一些背景任務的,一些比較耗時的操作也可以放在這裡運作,這就會讓人産生混淆了。但是,如果我告訴你Service其實是運作在主線程裡的,你還會覺得它和Thread有什麼關系嗎?讓我們看一下這個殘酷的事實吧。

在MainActivity的onCreate()方法裡加入一行列印目前線程id的語句:

然後在MyService的onCreate()方法裡也加入一行列印目前線程id的語句:

現在重新運作一下程式,并點選Start Service按鈕,會看到如下列印日志:

可以看到,它們的線程id完全是一樣的,由此證明了Service确實是運作在主線程裡的,也就是說如果你在Service裡編寫了非常耗時的代碼,程式必定會出現ANR的。

你可能會驚呼,這不是坑爹麼!?那我要Service又有何用呢?其實大家不要把背景和子線程聯系在一起就行了,這是兩個完全不同的概念。Android的背景就是指,它的運作是完全不依賴UI的。即使Activity被銷毀,或者程式被關閉,隻要程序還在,Service就可以繼續運作。比如說一些應用程式,始終需要與伺服器之間始終保持着心跳連接配接,就可以使用Service來實作。你可能又會問,前面不是剛剛驗證過Service是運作在主線程裡的麼?在這裡一直執行着心跳連接配接,難道就不會阻塞主線程的運作嗎?當然會,但是我們可以在Service中再建立一個子線程,然後在這裡去處理耗時邏輯就沒問題了。

額,既然在Service裡也要建立一個子線程,那為什麼不直接在Activity裡建立呢?這是因為Activity很難對Thread進行控制,當Activity被銷毀之後,就沒有任何其它的辦法可以再重新擷取到之前建立的子線程的執行個體。而且在一個Activity中建立的子線程,另一個Activity無法對其進行操作。但是Service就不同了,所有的Activity都可以與Service進行關聯,然後可以很友善地操作其中的方法,即使Activity被銷毀了,之後隻要重新與Service建立關聯,就又能夠擷取到原有的Service中Binder的執行個體。是以,使用Service來處理背景任務,Activity就可以放心地finish,完全不需要擔心無法對背景任務進行控制的情況。

一個比較标準的Service就可以寫成:

@Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                // 開始執行背景任務  
            }  
        }).start();  
        return super.onStartCommand(intent, flags, startId);  
    }  

    class MyBinder extends Binder {  

        public void startDownload() {  
            new Thread(new Runnable() {  
                @Override  
                public void run() {  
                    // 執行具體的下載下傳任務  
                }  
            }).start();  
        }  

    }  
           

建立前台Service

Service幾乎都是在背景運作的,一直以來它都是默默地做着辛苦的工作。但是Service的系統優先級還是比較低的,當系統出現記憶體不足情況時,就有可能會回收掉正在背景運作的Service。如果你希望Service可以一直保持運作狀态,而不會由于系統記憶體不足的原因導緻被回收,就可以考慮使用前台Service。前台Service和普通Service最大的差別就在于,它會一直有一個正在運作的圖示在系統的狀态欄顯示,下拉狀态欄後可以看到更加詳細的資訊,非常類似于通知的效果。當然有時候你也可能不僅僅是為了防止Service被回收才使用前台Service,有些項目由于特殊的需求會要求必須使用前台Service,比如說墨迹天氣,它的Service在背景更新天氣資料的同時,還會在系統狀态欄一直顯示目前天氣的資訊,如下圖所示:

那麼我們就來看一下如何才能建立一個前台Service吧,其實并不複雜,修改MyService中的代碼,如下所示:

public class MyService extends Service {  

        public static final String TAG = "MyService";  

        private MyBinder mBinder = new MyBinder();  

        @Override  
        public void onCreate() {  
            super.onCreate();  
            Notification notification = new Notification(R.drawable.ic_launcher,  
                    "有通知到來", System.currentTimeMillis());  
            Intent notificationIntent = new Intent(this, MainActivity.class);  
            PendingIntent pendingIntent = PendingIntent.getActivity(this, ,  
                    notificationIntent, );  
            notification.setLatestEventInfo(this, "這是通知的标題", "這是通知的内容",  
                    pendingIntent);  
            startForeground(, notification);  
            Log.d(TAG, "onCreate() executed");  
        }  

        .........  

    }  
           

這裡隻是修改了MyService中onCreate()方法的代碼。可以看到,我們首先建立了一個Notification對象,然後調用了它的setLatestEventInfo()方法來為通知初始化布局和資料,并在這裡設定了點選通知後就打開MainActivity。然後調用startForeground()方法就可以讓MyService變成一個前台Service,并會将通知的圖檔顯示出來。

現在重新運作一下程式,并點選Start Service或Bind Service按鈕,MyService就會以前台Service的模式啟動了,并且在系統狀态欄會彈出一個通欄圖示,下拉狀态欄後可以看到通知的詳細内容,如下圖所示。

好了,由于篇幅的原因,本篇文章就先寫到這裡。目前我們已經把關于Service的很多重要知識點都梳理完了,下一篇文章會承接這篇文章,介紹Android Service中剩下的一個非常重要且複雜的知識點 —— 遠端Service的使用,感興趣的朋友請繼續閱讀 Android Service完全解析,關于服務你所需知道的一切(下) 。

在上一篇文章中,我們學習了Android Service相關的許多重要内容,包括Service的基本用法、Service和Activity進行通信、Service的銷毀方式、Service與Thread的關系、以及如何建立前台Service。以上所提到的這些知識點,基本上涵蓋了大部分日常開發工作當中可能使用到的Service技術。不過關于Service其實還有一個更加高端的使用技巧沒有介紹,即遠端Service的用法。使用遠端Service甚至可以實作Android跨程序通信的功能,下面就讓我們具體地學習一下。

如果你還沒有看過前面一篇文章,建議先去閱讀一下 Android Service完全解析,關于服務你所需知道的一切(上) ,因為本篇文章中涉及到的代碼是在上篇文章的基礎上進行修改的。

在上篇文章中我們知道了,Service其實是運作在主線程裡的,如果直接在Service中處理一些耗時的邏輯,就會導緻程式ANR。

讓我們來做個實驗驗證一下吧,修改上一篇文章中建立的ServiceTest項目,在MyService的onCreate()方法中讓線程睡眠60秒,如下所示:

public class MyService extends Service {  

        ......  

        @Override  
        public void onCreate() {  
            super.onCreate();  
            Log.d(TAG, "onCreate() executed");  
            try {  
                Thread.sleep();  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  

        ......  

    }  
           

重新運作後,點選一下Start Service按鈕或Bind Service按鈕,程式就會阻塞住并無法進行任何其它操作,過一段時間後就會彈出ANR的提示框,如下圖所示。

之前我們提到過,應該在Service中開啟線程去執行耗時任務,這樣就可以有效地避免ANR的出現。

那麼本篇文章的主題是介紹遠端Service的用法,如果将MyService轉換成一個遠端Service,還會不會有ANR的情況呢?讓我們來動手嘗試一下吧。

将一個普通的Service轉換成遠端Service其實非常簡單,隻需要在注冊Service的時候将它的android:process屬性指定成:remote就可以了,代碼如下所示:

<?xml version="1.0" encoding="utf-8"?>  
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
        package="com.example.servicetest"  
        android:versionCode="1"  
        android:versionName="1.0" >  

        ......  

        <service  
            android:name="com.example.servicetest.MyService"  
            android:process=":remote" >  
        </service>  

    </manifest>  
           

現在重新運作程式,并點選一下Start Service按鈕,你會看到控制台立刻列印了onCreate() executed的資訊,而且主界面并沒有阻塞住,也不會出現ANR。大概過了一分鐘後,又會看到onStartCommand() executed列印了出來。

為什麼将MyService轉換成遠端Service後就不會導緻程式ANR了呢?這是由于,使用了遠端Service後,MyService已經在另外一個程序當中運作了,是以隻會阻塞該程序中的主線程,并不會影響到目前的應用程式。

為了證明一下MyService現在确實已經運作在另外一個程序當中了,我們分别在MainActivity的onCreate()方法和MyService的onCreate()方法裡加入一行日志,列印出各自所在的程序id,如下所示:

再次重新運作程式,然後點選一下Start Service按鈕,列印結果如下圖所示:

可以看到,不僅僅是程序id不同了,就連應用程式包名也不一樣了,MyService中列印的那條日志,包名後面還跟上了:remote辨別。

那既然遠端Service這麼好用,幹脆以後我們把所有的Service都轉換成遠端Service吧,還省得再開啟線程了。其實不然,遠端Service非但不好用,甚至可以稱得上是較為難用。一般情況下如果可以不使用遠端Service,就盡量不要使用它。

下面就來看一下它的弊端吧,首先将MyService的onCreate()方法中讓線程睡眠的代碼去除掉,然後重新運作程式,并點選一下Bind Service按鈕,你會發現程式崩潰了!為什麼點選Start Service按鈕程式就不會崩潰,而點選Bind Service按鈕就會崩潰呢?這是由于在Bind Service按鈕的點選事件裡面我們會讓MainActivity和MyService建立關聯,但是目前MyService已經是一個遠端Service了,Activity和Service運作在兩個不同的程序當中,這時就不能再使用傳統的建立關聯的方式,程式也就崩潰了。

那麼如何才能讓Activity與一個遠端Service建立關聯呢?這就要使用AIDL來進行跨程序通信了(IPC)。

AIDL(Android Interface Definition Language)是Android接口定義語言的意思,它可以用于讓某個Service與多個應用程式元件之間進行跨程序通信,進而可以實作多個應用程式共享同一個Service的功能。

下面我們就來一步步地看一下AIDL的用法到底是怎樣的。首先需要建立一個AIDL檔案,在這個檔案中定義好Activity需要與Service進行通信的方法。建立MyAIDLService.aidl檔案,代碼如下所示:

package com.example.servicetest;  
    interface MyAIDLService {  
        int plus(int a, int b);  
        String toUpperCase(String str);  
    }  
           

點選儲存之後,gen目錄下就會生成一個對應的Java檔案,如下圖所示:

然後修改MyService中的代碼,在裡面實作我們剛剛定義好的MyAIDLService接口,如下所示:

public class MyService extends Service {  

        ......  

        @Override  
        public IBinder onBind(Intent intent) {  
            return mBinder;  
        }  

        MyAIDLService.Stub mBinder = new Stub() {  

            @Override  
            public String toUpperCase(String str) throws RemoteException {  
                if (str != null) {  
                    return str.toUpperCase();  
                }  
                return null;  
            }  

            @Override  
            public int plus(int a, int b) throws RemoteException {  
                return a + b;  
            }  
        };  

    }  
           

這裡先是對MyAIDLService.Stub進行了實作,重寫裡了toUpperCase()和plus()這兩個方法。這兩個方法的作用分别是将一個字元串全部轉換成大寫格式,以及将兩個傳入的整數進行相加。然後在onBind()方法中将MyAIDLService.Stub的實作傳回。這裡為什麼可以這樣寫呢?因為Stub其實就是Binder的子類,是以在onBind()方法中可以直接傳回Stub的實作。

接下來修改MainActivity中的代碼,如下所示:

public class MainActivity extends Activity implements OnClickListener {  

        private Button startService;  

        private Button stopService;  

        private Button bindService;  

        private Button unbindService;  

        private MyAIDLService myAIDLService;  

        private ServiceConnection connection = new ServiceConnection() {  

            @Override  
            public void onServiceDisconnected(ComponentName name) {  
            }  

            @Override  
            public void onServiceConnected(ComponentName name, IBinder service) {  
                myAIDLService = MyAIDLService.Stub.asInterface(service);  
                try {  
                    int result = myAIDLService.plus(, );  
                    String upperStr = myAIDLService.toUpperCase("hello world");  
                    Log.d("TAG", "result is " + result);  
                    Log.d("TAG", "upperStr is " + upperStr);  
                } catch (RemoteException e) {  
                    e.printStackTrace();  
                }  
            }  
        };  

        ......  

    }  
           

我們隻是修改了ServiceConnection中的代碼。可以看到,這裡首先使用了MyAIDLService.Stub.asInterface()方法将傳入的IBinder對象傳換成了MyAIDLService對象,接下來就可以調用在MyAIDLService.aidl檔案中定義的所有接口了。這裡我們先是調用了plus()方法,并傳入了3和5作為參數,然後又調用了toUpperCase()方法,并傳入hello world字元串作為參數,最後将調用方法的傳回結果列印出來。

現在重新運作程式,并點選一下Bind Service按鈕,可以看到列印日志如下所示:

由此可見,我們确實已經成功實作跨程序通信了,在一個程序中通路到了另外一個程序中的方法。

不過你也可以看出,目前的跨程序通信其實并沒有什麼實質上的作用,因為這隻是在一個Activity裡調用了同一個應用程式的Service裡的方法。而跨程序通信的真正意義是為了讓一個應用程式去通路另一個應用程式中的Service,以實作共享Service的功能。那麼下面我們自然要學習一下,如何才能在其它的應用程式中調用到MyService裡的方法。

在上一篇文章中我們已經知道,如果想要讓Activity與Service之間建立關聯,需要調用bindService()方法,并将Intent作為參數傳遞進去,在Intent裡指定好要綁定的Service,示例代碼如下:

Intent bindIntent = new Intent(this, MyService.class);  
    bindService(bindIntent, connection, BIND_AUTO_CREATE);
           

這裡在建構Intent的時候是使用MyService.class來指定要綁定哪一個Service的,但是在另一個應用程式中去綁定Service的時候并沒有MyService這個類,這時就必須使用到隐式Intent了。現在修改AndroidManifest.xml中的代碼,給MyService加上一個action,如下所示:

<?xml version="1.0" encoding="utf-8"?>  
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
        package="com.example.servicetest"  
        android:versionCode="1"  
        android:versionName="1.0" >  

        ......  

        <service  
            android:name="com.example.servicetest.MyService"  
            android:process=":remote" >  
            <intent-filter>  
                <action android:name="com.example.servicetest.MyAIDLService"/>  
            </intent-filter>  
        </service>  

    </manifest> 
           

這就說明,MyService可以響應帶有com.example.servicetest.MyAIDLService這個action的Intent。

現在重新運作一下程式,這樣就把遠端Service端的工作全部完成了。

然後建立一個新的Android項目,起名為ClientTest,我們就嘗試在這個程式中遠端調用MyService中的方法。

ClientTest中的Activity如果想要和MyService建立關聯其實也不難,首先需要将MyAIDLService.aidl檔案從ServiceTest項目中拷貝過來,注意要将原有的包路徑一起拷貝過來,完成後項目的結構如下圖所示:

然後打開或建立activity_main.xml,在布局檔案中也加入一個Bind Service按鈕:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent"  
        android:orientation="vertical"  
         >  

       <Button   
           android:id="@+id/bind_service"  
           android:layout_width="match_parent"  
           android:layout_height="wrap_content"  
           android:text="Bind Service"  
           />  

    </LinearLayout>  
           

接下來打開或建立MainActivity,在其中加入和MyService建立關聯的代碼,如下所示:

public class MainActivity extends Activity {  

        private MyAIDLService myAIDLService;  

        private ServiceConnection connection = new ServiceConnection() {  

            @Override  
            public void onServiceDisconnected(ComponentName name) {  
            }  

            @Override  
            public void onServiceConnected(ComponentName name, IBinder service) {  
                myAIDLService = MyAIDLService.Stub.asInterface(service);  
                try {  
                    int result = myAIDLService.plus(, );  
                    String upperStr = myAIDLService.toUpperCase("comes from ClientTest");  
                    Log.d("TAG", "result is " + result);  
                    Log.d("TAG", "upperStr is " + upperStr);  
                } catch (RemoteException e) {  
                    e.printStackTrace();  
                }  
            }  
        };  

        @Override  
        protected void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.activity_main);  
            Button bindService = (Button) findViewById(R.id.bind_service);  
            bindService.setOnClickListener(new OnClickListener() {  
                @Override  
                public void onClick(View v) {  
                    Intent intent = new Intent("com.example.servicetest.MyAIDLService");  
                    bindService(intent, connection, BIND_AUTO_CREATE);  
                }  
            });  
        }  

    }  
           

這部分代碼大家一定會非常眼熟吧?沒錯,這和在ServiceTest的MainActivity中的代碼幾乎是完全相同的,隻是在讓Activity和Service建立關聯的時候我們使用了隐式Intent,将Intent的action指定成了com.example.servicetest.MyAIDLService。

在目前Activity和MyService建立關聯之後,我們仍然是調用了plus()和toUpperCase()這兩個方法,遠端的MyService會對傳入的參數進行處理并傳回結果,然後将結果列印出來。

這樣的話,ClientTest中的代碼也就全部完成了,現在運作一下這個項目,然後點選Bind Service按鈕,此時就會去和遠端的MyService建立關聯,觀察LogCat中的列印資訊如下所示:

不用我說,大家都已經看出,我們的跨程序通信功能已經完美實作了。

不過還有一點需要說明的是,由于這是在不同的程序之間傳遞資料,Android對這類資料的格式支援是非常有限的,基本上隻能傳遞Java的基本資料類型、字元串、List或Map等。那麼如果我想傳遞一個自定義的類該怎麼辦呢?這就必須要讓這個類去實作Parcelable接口,并且要給這個類也定義一個同名的AIDL檔案。這部分内容并不複雜,而且和Service關系不大,是以就不再詳細進行講解了,感興趣的朋友可以自己去查閱一下相關的資料。

好了,結合上下兩篇,這就是關于Service你所需知道的一切。