1. 程式人生 > >今天做到一道面試題:Android中程序的通訊方式

今天做到一道面試題:Android中程序的通訊方式

由於android系統中應用程式之間不能共享記憶體。因此,在不同應用程式之間互動資料(跨程序通訊)就稍微麻煩一些。在android SDK中提供了4種用於跨程序通訊的方式。這4種方式正好對應於android系統中4種應用程式元件:Activity、Content Provider、Broadcast和Service。

  其中Activity可以跨程序呼叫其他應用程式的Activity;
  Content Provider可以跨程序訪問其他應用程式中的資料(以Cursor物件形式返回),當然,也可以對其他應用程式的資料進行增、刪、改操 作;
  Broadcast可以向android系統中所有應用程式傳送廣播,而需要跨程序通訊的應用程式可以監聽這些廣播;
  Service

和Content Provider類似,也可以訪問其他應用程式中的資料,但不同的是,Content Provider返回的是Cursor物件,而Service返回的是Java物件,這種可以跨程序通訊的服務叫AIDL服務。

完整示例請參閱本文提供的原始碼。

方式一:訪問其他應用程式的Activity
Activity既可以在程序內(同一個應用程式)訪問,也可以跨程序訪問。如果想在同一個應用程式中訪問Activity,需要指定Context物件和Activity的Class物件,程式碼如下:

Intent intent = new  Intent(this , Test.class );  
startActivity(intent);  

Activity的跨程序訪問與程序內訪問略有不同。雖然它們都需要Intent物件,但跨程序訪問並不需要指定Context物件和Activity的 Class物件,而需要指定的是要訪問的Activity所對應的Action(一個字串)。有些Activity還需要指定一個Uri(通過 Intent構造方法的第2個引數指定)。

在android系統中有很多應用程式提供了可以跨程序訪問的Activity,例如,下面的程式碼可以直接呼叫撥打電話的Activity。

Intent callIntent = new  Intent(Intent.ACTION_CALL, Uri.parse("tel:12345678" );  
startActivity(callIntent);  

執行上面的程式碼後,系統會自動撥號,介面如圖1所示。

在呼叫撥號程式的程式碼中使用了一個Intent.ACTION_CALL常量,該常量的定義如下:

public  static  final  String ACTION_CALL = "android.intent.action.CALL" ;  

        這個常量是一個字串常量,也是我們在這節要介紹的跨程序呼叫Activity的關鍵。如果在應用程式中要共享某個Activity,需要為這個 Activity指定一個字串ID,也就是Action。也可以將這個Action看做這個Activity的key。在其他的應用程式中只要通過這個 Action就可以找到與Action對應的Activity,並通過startActivity方法來啟動這個Activity。

        下面先來看一下如何將應用程式的Activity共享出來,讀者可按如下幾步來共享Activity:
1.  在AndroidManifest.xml檔案中指定Action。指定Action要使用<action>標籤,並在該標籤的android:name屬性中指定Action
2.  在AndroidManifest.xml檔案中指定訪問協議。在指定Uri(Intent類的第2個引數)時需要訪問協議。訪問協議需要使 用<data>標籤的android:scheme屬性來指定。如果該屬性的值是“abc”,那麼Uri就應該是“abc://Uri的主體 部分”,也就是說,訪問協議是Uri的開頭部分。
3.  通過getIntent().getData().getHost()方法獲得協議後的Uri的主體部分。這個Host只是個稱謂,並不一定是主機名。讀者可以將其看成是任意的字串。
4.  從Bundle物件中獲得其他應用程式傳遞過來的資料。
5.  這一步當然是獲得資料後做進一步的處理了。至於如何處理這些資料,就得根據具體的需求決定了。

        下面來根據這些步驟共享一個Activity。首先建立一個android工程(ActionActivity),工程的主Activity是Main。在 本例中我們會共享這個Main類。首先開啟AndroidManifest.xml檔案,新增一個<activity>標籤,並重新定義了 Main的相應屬性。AndroidManifest.xml檔案的內容如下:

複製程式碼
<!--  重新配置Main  -->
<activity android:name=".Main" android:label="@string/app_name">
    <intent-filter>    
        <action android:name="net.blogjava.mobile.MYACTION" />
        <data android:scheme="info" />            
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
複製程式碼

      在配置AndroidManifest.xml時要注意,不能在同一個<activity>中配置多個動作,否則會覆蓋MAIN動作以使該程式無法正常啟動(雖然其他應用程式呼叫Main是正常的)。

      從上面的程式碼可以看出,<action>標籤的android:name屬性值是 net.blogjava.mobile.MYACTION,這就是Main自定義的動作。<data>標籤指定了Url的協議。如果指定 了<data>標籤的android:scheme屬性值(info),則在呼叫Main時需要使用如下的URL:

info://任意字串   

         一般<category>標籤的android:name屬性值可以設成android.intent.category.DEFAULT。

         下面來看看如何在Main類的onCreate方法中獲得其他應用程式傳遞過來的資料。

複製程式碼
package net.blogjava.mobile.actionactivity;
... ...
public class Main extends Activity implements OnClickListener
{
    private EditText editText;
    @Override
    public void onClick(View view)
    {
        //  單擊按鈕,會顯示文字框中的內容(以Toast資訊框形式顯示)
        Toast.makeText(this, editText.getText().toString(), Toast.LENGTH_LONG)
                .show();
    }
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
        editText = (EditText) findViewById(R.id.edittext);
        //  獲得其他應用程式傳遞過來的資料
        if (getIntent().getData() != null)
        {
            //  獲得Host,也就是info://後面的內容
            String host = getIntent().getData().getHost();
            Bundle bundle = getIntent().getExtras();
            //  其他的應用程式會傳遞過來一個value值,在該應用程式中需要獲得這個值
            String value = bundle.getString("value");
            //  將Host和Value組合在一下顯示在EditText元件中
            editText.setText(host + ":" + value);
            //  呼叫了按鈕的單擊事件,顯示Toast資訊提示框
            onClick(button);
        }
    }
}
複製程式碼

       從上面的程式可以看出,首先通過getIntent().getData()來判斷其他的應用程式是否傳遞了Uri(getData方法返回了一個Uri 物件)。如果執行該程式,Uri為null,因此,不會執行if語句裡面的程式碼。當其他的應用程式傳遞了Uri物件後,系統會執行if語句裡面的程式碼。當 執行ActionActivity後,在文字框中輸入“Running”,單擊“顯示文字框的內容”按鈕,會顯示如圖2所示的Toast提示資訊框。

      下面來看一下其他的應用程式是如何呼叫ActionActivity中的Main。新建一個android工程(InvokeActivity),並新增一個按鈕,按鈕的單擊事件方法程式碼如下:

複製程式碼
public void onClick(View view)
{
    //  需要使用Intent類的第2個引數指定Uri
    Intent intent = new Intent("net.blogjava.mobile.MYACTION", Uri
            .parse("info://呼叫其他應用程式的Activity"));
    //  設定value屬性值
    intent.putExtra("value", "呼叫成功");
    //  呼叫ActionActivity中的Main
    startActivity(intent);
}
複製程式碼

       在執行InvokeActivity之前,先要執行ActionActivity以便在android模擬器中安裝該程式。然後單擊InvokeActivity中的按鈕,就會顯示如圖3所示的效果。

   當然,也可以使用startActivityForResult方法來啟動其他應用程式的Activity,以便獲得Activity的返回值。例如,可以將ActionActivity中Main類的onClick程式碼修改為下面的形式。

複製程式碼
public void onClick(View view)
{
    Toast.makeText(this, editText.getText().toString(), Toast.LENGTH_LONG).show();
    Intent intent = new Intent();
    //  設定要返回的屬性值
    intent.putExtra("result", editText.getText().toString());
    //  設定返回碼和Intent物件
    setResult(2, intent);
    //  關閉Activity
    finish();
}
複製程式碼

 然後在InvokeActivity中使用下面的程式碼來呼叫Main。

intent = new Intent("net.blogjava.mobile.MYACTION", Uri
        .parse("info://呼叫其他應用程式的Activity"));
//  傳遞資料
intent.putExtra("value", "呼叫成功");
startActivityForResult(intent, 1);                //  1為請求碼

要想接收Activity返回的值,需要覆蓋onActivityResult事件方法,程式碼如下:

複製程式碼
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
    Toast.makeText(this, "返回值:" + data.getExtras().getString("result"),
            Toast.LENGTH_LONG).show();
}
複製程式碼

 當單擊InvokeActivity中的相應按鈕後,並且Main關閉後,會顯示如圖4所示的Toast資訊提示框。

從本節的介紹可以看出,跨程序訪問Activity(訪問其他應用程式中的Activity)主要是通過一個Action來完成的,如果要傳遞資料,還需 要指定一個Uri。當然,傳遞資料也可以通過Intent來完成。傳遞資料的過程可以是雙向的。如果要想從呼叫的Activity中返回資料,就需要使用 startActivityForResult方法來啟動Activity了。

方式二:Content Provider 
      Android應用程式可以使用檔案或SqlLite資料庫來儲存資料。Content Provider提供了一種在多個應用程式之間資料共享的方式(跨程序共享資料)。應用程式可以利用Content Provider完成下面的工作

1. 查詢資料
2. 修改資料
3. 新增資料
4. 刪除資料

        雖然Content Provider也可以在同一個應用程式中被訪問,但這麼做並沒有什麼意義。Content Provider存在的目的向其他應用程式共享資料和允許其他應用程式對資料進行增、刪、改操作。
Android系統本身提供了很多Content Provider,例如,音訊、視訊、聯絡人資訊等等。我們可以通過這些Content Provider獲得相關資訊的列表。這些列表資料將以Cursor物件返回。因此,從Content Provider返回的資料是二維表的形式。

      對於訪問Content Provider的程式,需要使用ContentResolver物件。該物件需要使用getContentResolver方法獲得,程式碼如下:

ContentResolver cr = getContentResolver();

 與Activity一樣,Content Provider也需要與一個URI對應。每一個Content Provider可以控制多個數據集,在這種情況下,每一個數據集會對應一個單獨的URI。所有的URI必須以“content://”開頭。
為了程式更容易維護,也為了簡化程式程式碼,一般將URI定義成一個常量。例如,下面的常量表示系統的聯絡人電話號碼。

android.provider.Contacts.Phones.CONTENT_URI 

下面來看一下編寫Content Provider的具體步驟。

1.  編寫一個繼承於android.content.ContentProvider的子類。該類是ContentProvider的核心類。在該類中會實現 query、insert、update及delete方法。實際上呼叫ContentResolver類的這4個方法就是呼叫 ContentProvider類中與之要對應的方法。在本文中只介紹query。至於insert、update、delete和query的用法類 似。也是通過Uri傳遞引數,然後在這些方法中接收這些引數,並做進一步地處理。
2.  在AndroidManifest.xml檔案中配置ContentProvider。要想唯一確定一個ContentProvider,需要指定這個 ContentProvider的URI,除此之外,還需要指定URI所對應的ContentProvider類。這有些象Servlet的定義,除了要 指定Servlet對應的Web地址,還要指定這個地址所對應的Servlet類。
現在來看一下Uri的具體格式,先看一下如圖5所示的URI。

下面對圖5所示的URI的4個部分做一下解釋。

A:Content Provider URI的固定字首,也就是說,所有的URI必須以content://開頭。
B:URI中最重要的部分。該部分是Content Provider的唯一標識。對於第三方應用程式來說,該部分最後使用完整的類名(包名+類名),以確保URI的唯一性。該部分需要在 AndroidManifest.xml檔案中<provider>標籤中定義,程式碼如下:

<provider name=".TransportationProvider" authorities="com.example.transportationprovider"
          . . .  >

C:這部分是URI的路徑(path)。表示URI中各種被請求的資料。這部分是可選的, 如果Content Provider僅僅提供一種請求的資料,那麼這部分可以省略。如果Content Provider要提供多種請求資料。就需要新增多個路徑,甚至是子路徑。例如,“land/bus”、“land/train”、“sea/ship” 就指定了3種可能提供的資料。
D:這部分也是可選的。如果要傳遞一個值給Content Provider,可以通過這部分傳遞。當然,如果不需要傳值,這部分也可以省略,省略後的URI如下所示:

content://com.example.transportationprovider/trains

 本例利用了《基於 android SDK1.5的英文電子詞典的實現》一文中實現的電子詞典程式。通過ContentProvider,將電子詞典的查詞功能共享成Cursor物件。這樣 其他的應用程式就可以通過ContentProvider來查詞英文單詞了。關於英文詞典的具體實現細節,讀者可以通過如下的地址檢視《基於android SDK1.5的英文電子詞典的實現》一文。

http://www.ophonesdn.com/article/show/111

在電子詞典程式中需要一個DictionaryContentProvider類,該類是ContentProvider的子類。在該類中實現了 query方法,並根據不同的URI來返回不同的結果。讓我們先看一下DictionaryContentProvider類,然後再對這些程式碼做一些解 釋。

複製程式碼
... ...
public class DictionaryContentProvider extends ContentProvider
{
    private static UriMatcher uriMatcher;
    private static final String AUTHORITY = "net.blogjava.mobile.dictionarycontentprovider";
    private static final int SINGLE_WORD = 1;
    private static final int PREFIX_WORDS = 2;
    public static final String DATABASE_PATH = android.os.Environment
    .getExternalStorageDirectory().getAbsolutePath()
    + "/dictionary";
    public static final String DATABASE_FILENAME = "dictionary.db";
    private SQLiteDatabase database;
    static
    {
        //  新增訪問ContentProvider的Uri
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY, "single", SINGLE_WORD);
        uriMatcher.addURI(AUTHORITY, "prefix/*", PREFIX_WORDS);
    }
    //  該方法在Activity的onCreate方法之前呼叫
    @Override
    public boolean onCreate()
    {
        database = openDatabase();
        return true;
    }
    //  在本例中只實現了query方法,其他的方法(insert、update和delete)與query方法的實現
    //  類似
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder)
    {
        Cursor cursor = null;
        switch (uriMatcher.match(uri))
        {
            case SINGLE_WORD:
                //  查詢指定的單詞
                cursor = database.query("t_words", projection, selection,
                        selectionArgs, null, null, sortOrder);
                break;
            case PREFIX_WORDS:
                String word = uri.getPathSegments().get(1);
                //  查詢以指定字串開頭的單詞集合
                cursor = database
                        .rawQuery(
                                "select english as _id, chinese from t_words where english like ?",
                                new String[]
                                { word + "%" });
                break;

            default:
                throw new IllegalArgumentException("<" + uri + ">格式不正確.");
        }
        return cursor;
    }
    ... ...
}
複製程式碼

關於DictionaryContentProvider類的程式碼需要做如下的解釋。


1.  在DictionaryContentProvider類的開頭定義的AUTHORITY是訪問ContentProvider的URI的前半部分。
2.  訪問ContentProvider的URI的後半部分由uriMatcher.addURI(...)方法指定。該方法的第1個引數就是 AUTHORITY(Uri的前半部分),第2個引數是Uri的後半部分,第3個引數是與第2個引數值對應的程式碼。當其他的應用程式通過Uri訪問 ContentProvider時。系統解析Uri後,將addURI方法的第2個引數值轉換成與之對應的程式碼(第3個引數值)。
3.  addURI的第2個引數值可以使用萬用字元。例如,prefix/*中的*表示所有字元。prefix/abc、prefix/xxx都會匹配成功。
4.  訪問ContentProvider的URI是addURI的第1個和第2個引數值的元件,例如,按著DictionaryContentProvider中設定的兩個URI,可以分別匹配下面的兩個URI。

content://net.blogjava.mobile.dictionarycontentprovider/single
content://net.blogjava.mobile.dictionarycontentprovider/prefix/wo

要注意的是,訪問ContentProvider的URI必須以“content://”開頭。


5.  在query方法中建議使用SQLiteDatabase物件的query方法查詢。因為query方法的引數正好和DictionaryContentProvider類中的query方法的引數對應,這樣使用起來比較方便。
6.  由於安裝了ContentProvider的應用程式會先呼叫ContentProvider的onCreate方法(該方法會在Activity的 onCreate方法之前呼叫),因此,只需要將開啟或複製資料庫的方法(openDatabase)放在 DictionaryContentProvider類中,並在onCreate方法中呼叫即可。
7.  在DictionaryContentProvider類中只實現了query方法。在該方法中判斷了其他應用程式傳送的是哪一個Uri。並進行相應的處理。這兩個Uri一個是查詢指定單詞的,另外一個是查詢以某個字串開頭的所有單詞的(用於顯示單詞列表)。
下面在AndroidManifest.xml檔案中配置DictionaryContentProvider類。

<provider android:name="DictionaryContentProvider"
            android:authorities="net.blogjava.mobile.dictionarycontentprovider" />  

 OK,現在來看看應用程式如何呼叫ContentProvider。呼叫ContentProvider的關鍵是使用 getContentResolver方法來獲得一個ContentResolver物件,並通過ContentResolver物件的query方法來 訪問ContentProvider。

        首先來定義兩個訪問ContentProvider的常量。

public final String DICTIONARY_SINGLE_WORD_URI = "content://net.blogjava.mobile.dictionarycontentprovider/single";
public final String DICTIONARY_PREFIX_WORD_URI = "content://net.blogjava.mobile.dictionarycontentprovider/prefix";

然後在查詢按鈕的單擊事件中編寫如下的程式碼來查詢單詞。

複製程式碼
public void onClick(View view)
{
    Uri uri = Uri.parse(DICTIONARY_SINGLE_WORD_URI);
    //  通過ContentProvider查詢單詞,並返回Cursor物件,然後的操作就和直接從資料中獲得
    //  Cursor物件後的操作是一樣的了
    Cursor cursor = getContentResolver().query(uri, null, "english=?",
            new String[]{ actvWord.getText().toString() }, null);
    String result = "未找到該單詞.";
    if (cursor.getCount() > 0)
    {
        cursor.moveToFirst();
        result = cursor.getString(cursor.getColumnIndex("chinese"));
    }
    new AlertDialog.Builder(this).setTitle("查詢結果").setMessage(result)
            .setPositiveButton("關閉", null).show();

}
複製程式碼

下面是顯示單詞列表的程式碼。

複製程式碼
public void afterTextChanged(Editable s)
{
    if ("".equals(s.toString()))
        return;
    Uri uri = Uri.parse(DICTIONARY_PREFIX_WORD_URI + "/" + s.toString());
    //  從ContentProvider中獲得以某個字串開頭的所有單詞的Cursor物件
    Cursor cursor = getContentResolver().query(uri, null, null, null, null);
    DictionaryAdapter dictionaryAdapter = new DictionaryAdapter(this,
            cursor, true);
    actvWord.setAdapter(dictionaryAdapter);
}
複製程式碼

 現在來執行本例,會看到如圖6所示的介面。當查詢單詞時會顯示如圖7所示的單詞列表,查詢出結果後,會顯示如圖8所示的介面。

方式三:廣播(Broadcast) 
      廣播是一種被動跨程序通訊的方式。當某個程式向系統傳送廣播時,其他的應用程式只能被動地接收廣播資料。這就象電臺進行廣播一樣,聽眾只能被動地收聽,而不能主動與電臺進行溝通。
在應用程式中傳送廣播比較簡單。只需要呼叫sendBroadcast方法即可。該方法需要一個Intent物件。通過Intent物件可以傳送需要廣播的資料。

     先建一個android工程:sendbroadcast。在XML佈局檔案中放兩個元件:EditText和Button,當單擊按鈕後,會彈出顯示 EditText元件中文字的對話方塊,關閉對話方塊後, 會使用sendBroadcast方法傳送訊息,並將EditText元件的文字通過Intent物件傳送出去。完整的程式碼如下:

複製程式碼
package net.blogjava.mobile.sendbroadcast;
... ...
public class Main extends Activity implements OnClickListener
{
    private EditText editText;
    @Override
    public void onClick(View view)
    {
        new AlertDialog.Builder(this).setMessage(editText.getText().toString())
                .setPositiveButton("確定", null).show();     
        //  通過Intent類的構造方法指定廣播的ID
        Intent intent = new Intent("net.blogjava.mobile.MYBROADCAST");
        //  將要廣播的資料新增到Intent物件中  
        intent.putExtra("text", editText.getText().toString());
        //  傳送廣播  
        sendBroadcast(intent);
    }
    ... ...
}
複製程式碼

傳送廣播並不需要在AndroidManifest.xml檔案中註冊,但接收廣播必須在AndroidManifest.xml檔案中註冊 receiver。下面來編寫一個接收廣播的應用程式。首先建立一個android工程:receiver。然後編寫一個MyReceiver類,該類是 BroadcastReceiver的子類,程式碼如下:

複製程式碼
package net.blogjava.mobile.receiver;
... ...
public class MyReceiver extends BroadcastReceiver
{
    //  當sendbroadcast傳送廣播時,系統會呼叫onReceive方法來接收廣播
    @Override
    public void onReceive(Context context, Intent intent)
{
    //  判斷是否為sendbroadcast傳送的廣播
        if ("net.blogjava.mobile.MYBROADCAST".equals(intent.getAction()))
        {
            Bundle bundle = intent.getExtras();
            if (bundle != null)
            {
                String text = bundle.getString("text");
                Toast.makeText(context, "成功接收廣播:" + text, Toast.LENGTH_LONG).show();
            }
        }
    }
}
複製程式碼

     當應用程式傳送廣播時,系統會呼叫onReceive方法來接收廣播,並通過intent.getAction()方法返回廣播的ID,也就是在傳送廣播時Intent構造方法指定的字串。然後就可以從Bundle物件中獲得相應的資料了。

     最後還需要在AndroidManifest.xml檔案中註冊receiver,程式碼如下:

複製程式碼
<!--  註冊receiver 
<receiver android:name="MyReceiver">
    <intent-filter>
        <action android:name="net.blogjava.mobile.MYBROADCAST" />
    </intent-filter>
</receiver>
複製程式碼

在註冊MyReceiver類時需要使用<receiver>標籤,android:name屬性指定MyReceiver類,<action>標籤的android:name指定了廣播的ID。

        首先執行receiver程式,然後就可以關閉receiver程式了。接收廣播並不依賴於程式的狀態。就算程式關閉了,仍然可以接收廣播。然後再啟動 sendbroadcast程式。並在文字框中輸入“android”,然後單擊按鈕,會彈出一個顯示文字框內容的對話方塊,如圖9所示。當關閉對話方塊後,會 顯示一個Toast資訊提示框,這個資訊框是由receiver程式彈出的。如圖10所示。

方式四:AIDL服務
       服務(Service)是android系統中非常重要的元件。Service可以脫離應用程式執行。也就是說,應用程式只起到一個啟動Service的作用。一但Service被啟動,就算應用程式關閉,Service仍然會在後臺執行。

       android系統中的Service主要有兩個作用:後臺執行和跨程序通訊。後臺執行就不用說了,當Service啟動後,就可以在Service物件中 執行相應的業務程式碼,而這一切使用者並不會察覺。而跨程序通訊是這一節的主題。如果想讓應用程式可以跨程序通訊,就要使用我們這節講的AIDL服 務,AIDL的全稱是Android Interface Definition Language,也就是說,AIDL實際上是一種介面定義語言。通過這種語言定義介面後,Eclipse外掛(ODT)會自動生成相應的Java程式碼接 口程式碼。下面來看一下編寫一個AIDL服務的基本步驟。

1.  在Eclipse工程的package目錄中建立一個副檔名為aidl的檔案。package目錄就是Java類所在的目錄。該檔案的語法類似於Java程式碼。aidl檔案中定義的是AIDL服務的介面。這個介面需要在呼叫AIDL服務的程式中訪問。
2.  如果aidl檔案的內容是正確的,Eclipse外掛會自動生成一個Java介面檔案(*.java)。
3.  建立一個服務類(Service的子類)。
4.  實現由aidl檔案生成的Java介面。
5.  在AndroidManifest.xml檔案中配置AIDL服務,尤其要注意的是,<action>標籤的android:name屬性值就是客戶端要引用該服務的ID,也就是Intent類構造方法的引數值。

      現在我們來編寫一個AIDL服務,首先建立一個android工程:aidlservice。在aidlservice工程中有一個Main類,在Main類所有的目錄建立一個IMyService.aidl檔案,內容如下:

package net.blogjava.mobile.aidlservice;
interface IMyService
{
    String getValue();    //  為AIDL服務的介面方法,呼叫AIDL服務的程式需要呼叫該方法
}

在儲存IMyService.aidl檔案後,ODT會在gen目錄下產生一個IMyService.java檔案,讀者可以不必管這個檔案中的內容,也 不需要修改該檔案的內容。這個檔案是由ODT自動維護的,只要修改了IMyService.aidl檔案的內容,IMyService.java檔案的內 容就會隨之改變。

        然後建立一個MyService類,該類是Service的子類,程式碼如下:

複製程式碼
package net.blogjava.mobile.aidlservice;
... ...
public class MyService extends Service
{
    //  IMyService.Stub類是根據IMyService.aidl檔案生成的類,該類中包含了介面方法(getValue)
    public class MyServiceImpl extends IMyService.Stub
    {
        @Override
        public String getValue() throws RemoteException
        {
            return "從AIDL服務獲得的值.";
        }
    }
    @Override
    public IBinder onBind(Intent intent)
{        
//  該方法必須返回MyServiceImpl類的物件例項
        return new MyServiceImpl();
    }
}
複製程式碼

最後需要在AndroidManifest.xml檔案中配置MyService類,程式碼如下:

複製程式碼
<!--  註冊服務 -->
<service android:name=".MyService">
    <intent-filter>
        <!--  指定呼叫AIDL服務的ID  -->
        <action android:name="net.blogjava.mobile.aidlservice.IMyService" />
    </intent-filter>
</service>
複製程式碼

  下面來看看如何呼叫這個AIDL服務。首先建立一個android工程:aidlclient。然後將aidlservice工程中自動生成的 IMyService.java檔案複製到aidlclient工程中。在呼叫AIDL服務之前需要先使用bindService方法繫結AIDL服務。 bindService方法需要一個ServiceConnection物件。ServiceConnection有一個 onServiceConnected方法,當成功繫結AIDL服務且,該方法被呼叫。並通過service引數返回AIDL服務物件。下面是呼叫 AIDL服務的完成程式碼。

複製程式碼
package net.blogjava.mobile.aidlclient;
... ...
public class Main extends Activity implements OnClickListener
{
private IMyService myService = null;
//  建立ServiceConnection物件
    private ServiceConnection serviceConnection = new ServiceConnection()
    {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
        {
            // 獲得AIDL服務物件
            myService = IMyService.Stub.asInterface(service);
            try
            {
                //  呼叫AIDL服務物件中的getValue方法,並以對話方塊中顯示該方法的返回值
                new AlertDialog.Builder(Main.this).setMessage(
                        myService.getValue()).setPositiveButton("確定", null)
                        .show();
            }
            catch (Exception e)
            {
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name)
        {
        }
    };
    @Override
    public void onClick(View view)
{
    //  繫結AIDL服務
        bindService(new Intent("net.blogjava.mobile.aidlservice.IMyService"),
                serviceConnection, Context.BIND_AUTO_CREATE);
    }
    ... ...
}
複製程式碼

在編寫AIDL服務和客戶端時要注意如下兩點:

1.  AIDL服務中的onBind方法必須返回AIDL介面物件(MyServiceImpl物件)。該物件也是onServiceConnected事件方法的第2個引數值。
2.  bindService方法的第1個引數是Intent物件,該物件構造方法的引數需要指定AIDL服務的ID,也就是在 AndroidManifest.xml檔案中<service>標籤的<action>子標籤的android:name屬性 的值。

 現在先執行aidlservice程式,以便安裝AIDL服務,然後執行aidlclient程式,並單擊按鈕,會顯示如圖11所示的對話方塊。對話方塊中的資訊就是AIDL服務介面中getValue方法的返回值。

總結
      本文介紹了4種跨程序通訊的方式:Activity、ContentProvider、Broadcast和AIDL Service。其中Activity可以跨程序呼叫其他應用程式的Activity;ContentProvider可以訪問其他應用程式返回的 Cursor物件;Broadcast採用的是被動接收的方法,也就是說,客戶端只能接收廣播資料,而不能向傳送廣播的程式傳送資訊。AIDL Service可以將程式中的某個介面公開,這樣在其他的應用程式中就可以象訪問本地物件一樣訪問AIDL服務物件了。這4種跨程序通訊的方式可以應用在 不同的場合,例如,在需要顯示視覺化的介面時可以用Activity,需要返回記錄集時可以用ContentProvider。至於在應用程式中具體要用 到哪一種或幾種方式進行跨程序通訊,讀者可以根據實際情況進行選擇。