一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

android中跨進(jìn)程通訊的4種方式

 杰出天下 2012-02-10

由于android系統(tǒng)中應(yīng)用程序之間不能共享內(nèi)存。因此,在不同應(yīng)用程序之間交互數(shù)據(jù)(跨進(jìn)程通訊)就稍微麻煩一些。在android SDK中提供了4種用于跨進(jìn)程通訊的方式。這4種方式正好對(duì)應(yīng)于android系統(tǒng)中4種應(yīng)用程序組件:Activity、Content Provider、Broadcast和Service。其中Activity可以跨進(jìn)程調(diào)用其他應(yīng)用程序的Activity;Content Provider可以跨進(jìn)程訪問其他應(yīng)用程序中的數(shù)據(jù)(以Cursor對(duì)象形式返回),當(dāng)然,也可以對(duì)其他應(yīng)用程序的數(shù)據(jù)進(jìn)行增、刪、改操作;Broadcast可以向android系統(tǒng)中所有應(yīng)用程序發(fā)送廣播,而需要跨進(jìn)程通訊的應(yīng)用程序可以監(jiān)聽這些廣播;Service和Content Provider類似,也可以訪問其他應(yīng)用程序中的數(shù)據(jù),但不同的是,Content Provider返回的是Cursor對(duì)象,而Service返回的是Java對(duì)象,這種可以跨進(jìn)程通訊的服務(wù)叫AIDL服務(wù)。
完整示例請(qǐng)參閱本文提供的源代碼。

方式一:訪問其他應(yīng)用程序的Activity
Activity既可以在進(jìn)程內(nèi)(同一個(gè)應(yīng)用程序)訪問,也可以跨進(jìn)程訪問。如果想在同一個(gè)應(yīng)用程序中訪問Activity,需要指定Context對(duì)象和Activity的Class對(duì)象,代碼如下:


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

      Activity的跨進(jìn)程訪問與進(jìn)程內(nèi)訪問略有不同。雖然它們都需要Intent對(duì)象,但跨進(jìn)程訪問并不需要指定Context對(duì)象和Activity的 Class對(duì)象,而需要指定的是要訪問的Activity所對(duì)應(yīng)的Action(一個(gè)字符串)。有些Activity還需要指定一個(gè)Uri(通過 Intent構(gòu)造方法的第2個(gè)參數(shù)指定)。


         在android系統(tǒng)中有很多應(yīng)用程序提供了可以跨進(jìn)程訪問的Activity,例如,下面的代碼可以直接調(diào)用撥打電話的Activity。


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

       執(zhí)行上面的代碼后,系統(tǒng)會(huì)自動(dòng)撥號(hào),界面如圖1所示。

        在調(diào)用撥號(hào)程序的代碼中使用了一個(gè)Intent.ACTION_CALL常量,該常量的定義如下:


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

        這個(gè)常量是一個(gè)字符串常量,也是我們?cè)谶@節(jié)要介紹的跨進(jìn)程調(diào)用Activity的關(guān)鍵。如果在應(yīng)用程序中要共享某個(gè)Activity,需要為這個(gè) Activity指定一個(gè)字符串ID,也就是Action。也可以將這個(gè)Action看做這個(gè)Activity的key。在其他的應(yīng)用程序中只要通過這個(gè) Action就可以找到與Action對(duì)應(yīng)的Activity,并通過startActivity方法來啟動(dòng)這個(gè)Activity。

        下面先來看一下如何將應(yīng)用程序的Activity共享出來,讀者可按如下幾步來共享Activity:
1.  在AndroidManifest.xml文件中指定Action。指定Action要使用<action>標(biāo)簽,并在該標(biāo)簽的android:name屬性中指定Action
2.  在AndroidManifest.xml文件中指定訪問協(xié)議。在指定Uri(Intent類的第2個(gè)參數(shù))時(shí)需要訪問協(xié)議。訪問協(xié)議需要使用<data>標(biāo)簽的android:scheme屬性來指定。如果該屬性的值是“abc”,那么Uri就應(yīng)該是“abc://Uri的主體部分”,也就是說,訪問協(xié)議是Uri的開頭部分。
3.  通過getIntent().getData().getHost()方法獲得協(xié)議后的Uri的主體部分。這個(gè)Host只是個(gè)稱謂,并不一定是主機(jī)名。讀者可以將其看成是任意的字符串。
4.  從Bundle對(duì)象中獲得其他應(yīng)用程序傳遞過來的數(shù)據(jù)。
5.  這一步當(dāng)然是獲得數(shù)據(jù)后做進(jìn)一步的處理了。至于如何處理這些數(shù)據(jù),就得根據(jù)具體的需求決定了。

        下面來根據(jù)這些步驟共享一個(gè)Activity。首先建立一個(gè)android工程(ActionActivity),工程的主Activity是Main。在本例中我們會(huì)共享這個(gè)Main類。首先打開AndroidManifest.xml文件,添加一個(gè)<activity>標(biāo)簽,并重新定義了 Main的相應(yīng)屬性。AndroidManifest.xml文件的內(nèi)容如下:

  1. <!--  重新配置Main  -->  
  2. <activity android:name=".Main"  android:label="@string/app_name" >  
  3.     <intent-filter>      
  4.         <action android:name="net.blogjava.mobile.MYACTION"  />  
  5.         <data android:scheme="info"  />              
  6.         <category android:name="android.intent.category.DEFAULT"  />  
  7.     </intent-filter>  
  8. </activity>  
  1. <!-- 重新配置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時(shí)要注意,不能在同一個(gè)<activity>中配置多個(gè)動(dòng)作,否則會(huì)覆蓋MAIN動(dòng)作以使該程序無法正常啟動(dòng)(雖然其他應(yīng)用程序調(diào)用Main是正常的)。

         從上面的代碼可以看出,<action>標(biāo)簽的android:name屬性值是 net.blogjava.mobile.MYACTION,這就是Main自定義的動(dòng)作。<data>標(biāo)簽指定了Url的協(xié)議。如果指定了<data>標(biāo)簽的android:scheme屬性值(info),則在調(diào)用Main時(shí)需要使用如下的URL:


  1. info://任意字符串   
  1. info://任意字符串  

         一般<category>標(biāo)簽的android:name屬性值可以設(shè)成android.intent.category.DEFAULT。

         下面來看看如何在Main類的onCreate方法中獲得其他應(yīng)用程序傳遞過來的數(shù)據(jù)。


  1. package  net.blogjava.mobile.actionactivity;  
  2. ... ...  
  3. public  class  Main extends  Activity implements  OnClickListener  
  4. {  
  5.     private  EditText editText;  
  6.     @Override   
  7.     public  void  onClick(View view)  
  8.     {  
  9.         //  單擊按鈕,會(huì)顯示文本框中的內(nèi)容(以Toast信息框形式顯示)   
  10.         Toast.makeText(this , editText.getText().toString(), Toast.LENGTH_LONG)  
  11.                 .show();  
  12.     }  
  13.     @Override   
  14.     public  void  onCreate(Bundle savedInstanceState)  
  15.     {  
  16.         super .onCreate(savedInstanceState);  
  17.         setContentView(R.layout.main);  
  18.         Button button = (Button) findViewById(R.id.button);  
  19.         button.setOnClickListener(this );  
  20.         editText = (EditText) findViewById(R.id.edittext);  
  21.         //  獲得其他應(yīng)用程序傳遞過來的數(shù)據(jù)   
  22.         if  (getIntent().getData() != null )  
  23.         {  
  24.             //  獲得Host,也就是info://后面的內(nèi)容   
  25.             String host = getIntent().getData().getHost();  
  26.             Bundle bundle = getIntent().getExtras();  
  27.             //  其他的應(yīng)用程序會(huì)傳遞過來一個(gè)value值,在該應(yīng)用程序中需要獲得這個(gè)值   
  28.             String value = bundle.getString("value" );  
  29.             //  將Host和Value組合在一下顯示在EditText組件中   
  30.             editText.setText(host + ":"  + value);  
  31.             //  調(diào)用了按鈕的單擊事件,顯示Toast信息提示框   
  32.             onClick(button);  
  33.         }  
  34.     }  
  35. }  
  1. package net.blogjava.mobile.actionactivity; ... ... public class Main extends Activity implements OnClickListener { private EditText editText; @Override public void onClick(View view) { // 單擊按鈕,會(huì)顯示文本框中的內(nèi)容(以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); // 獲得其他應(yīng)用程序傳遞過來的數(shù)據(jù) if (getIntent().getData() != null) { // 獲得Host,也就是info://后面的內(nèi)容 String host = getIntent().getData().getHost(); Bundle bundle = getIntent().getExtras(); // 其他的應(yīng)用程序會(huì)傳遞過來一個(gè)value值,在該應(yīng)用程序中需要獲得這個(gè)值 String value = bundle.getString("value"); // 將Host和Value組合在一下顯示在EditText組件中 editText.setText(host + ":" + value); // 調(diào)用了按鈕的單擊事件,顯示Toast信息提示框 onClick(button); } } }  

        從上面的程序可以看出,首先通過getIntent().getData()來判斷其他的應(yīng)用程序是否傳遞了Uri(getData方法返回了一個(gè)Uri 對(duì)象)。如果運(yùn)行該程序,Uri為null,因此,不會(huì)執(zhí)行if語句里面的代碼。當(dāng)其他的應(yīng)用程序傳遞了Uri對(duì)象后,系統(tǒng)會(huì)執(zhí)行if語句里面的代碼。當(dāng)運(yùn)行ActionActivity后,在文本框中輸入“Running”,單擊“顯示文本框的內(nèi)容”按鈕,會(huì)顯示如圖2所示的Toast提示信息框。

           下面來看一下其他的應(yīng)用程序是如何調(diào)用ActionActivity中的Main。新建一個(gè)android工程(InvokeActivity),并添加一個(gè)按鈕,按鈕的單擊事件方法代碼如下:


  1. public  void  onClick(View view)  
  2. {  
  3.     //  需要使用Intent類的第2個(gè)參數(shù)指定Uri   
  4.     Intent intent = new  Intent("net.blogjava.mobile.MYACTION" , Uri  
  5.             .parse("info://調(diào)用其他應(yīng)用程序的Activity" ));  
  6.     //  設(shè)置value屬性值   
  7.     intent.putExtra("value""調(diào)用成功" );  
  8.     //  調(diào)用ActionActivity中的Main   
  9.     startActivity(intent);  
  10. }  
  1. public void onClick(View view) { // 需要使用Intent類的第2個(gè)參數(shù)指定Uri Intent intent = new Intent("net.blogjava.mobile.MYACTION", Uri .parse("info://調(diào)用其他應(yīng)用程序的Activity")); // 設(shè)置value屬性值 intent.putExtra("value", "調(diào)用成功"); // 調(diào)用ActionActivity中的Main startActivity(intent); }  

       在運(yùn)行InvokeActivity之前,先要運(yùn)行ActionActivity以便在android模擬器中安裝該程序。然后單擊InvokeActivity中的按鈕,就會(huì)顯示如圖3所示的效果。

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

 


  1. public  void  onClick(View view)  
  2. {  
  3.     Toast.makeText(this , editText.getText().toString(), Toast.LENGTH_LONG).show();  
  4.     Intent intent = new  Intent();  
  5.     //  設(shè)置要返回的屬性值   
  6.     intent.putExtra("result" , editText.getText().toString());  
  7.     //  設(shè)置返回碼和Intent對(duì)象   
  8.     setResult(2 , intent);  
  9.     //  關(guān)閉Activity   
  10.     finish();  
  11. }  
  1. public void onClick(View view) { Toast.makeText(this, editText.getText().toString(), Toast.LENGTH_LONG).show(); Intent intent = new Intent(); // 設(shè)置要返回的屬性值 intent.putExtra("result", editText.getText().toString()); // 設(shè)置返回碼和Intent對(duì)象 setResult(2, intent); // 關(guān)閉Activity finish(); }  

     然后在InvokeActivity中使用下面的代碼來調(diào)用Main。


  1. intent = new  Intent("net.blogjava.mobile.MYACTION" , Uri  
  2.         .parse("info://調(diào)用其他應(yīng)用程序的Activity" ));  
  3. //  傳遞數(shù)據(jù)   
  4. intent.putExtra("value""調(diào)用成功" );  
  5. startActivityForResult(intent, 1 );              //  1為請(qǐng)求碼   
  1. intent = new Intent("net.blogjava.mobile.MYACTION", Uri .parse("info://調(diào)用其他應(yīng)用程序的Activity")); // 傳遞數(shù)據(jù) intent.putExtra("value""調(diào)用成功"); startActivityForResult(intent, 1); // 1為請(qǐng)求碼  

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


  1. @Override   
  2. protected  void  onActivityResult(int  requestCode, int  resultCode, Intent data)  
  3. {  
  4.     Toast.makeText(this"返回值:"  + data.getExtras().getString("result" ),  
  5.             Toast.LENGTH_LONG).show();  
  6. }  
  1. @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { Toast.makeText(this"返回值:" + data.getExtras().getString("result"), Toast.LENGTH_LONG).show(); }  

     當(dāng)單擊InvokeActivity中的相應(yīng)按鈕后,并且Main關(guān)閉后,會(huì)顯示如圖4所示的Toast信息提示框。

      從本節(jié)的介紹可以看出,跨進(jìn)程訪問Activity(訪問其他應(yīng)用程序中的Activity)主要是通過一個(gè)Action來完成的,如果要傳遞數(shù)據(jù),還需要指定一個(gè)Uri。當(dāng)然,傳遞數(shù)據(jù)也可以通過Intent來完成。傳遞數(shù)據(jù)的過程可以是雙向的。如果要想從調(diào)用的Activity中返回?cái)?shù)據(jù),就需要使用 startActivityForResult方法來啟動(dòng)Activity了。

方式二:Content Provider
      Android應(yīng)用程序可以使用文件或SqlLite數(shù)據(jù)庫(kù)來存儲(chǔ)數(shù)據(jù)。Content Provider提供了一種在多個(gè)應(yīng)用程序之間數(shù)據(jù)共享的方式(跨進(jìn)程共享數(shù)據(jù))。應(yīng)用程序可以利用Content Provider完成下面的工作

1. 查詢數(shù)據(jù)
2. 修改數(shù)據(jù)
3. 添加數(shù)據(jù)
4. 刪除數(shù)據(jù)

        雖然Content Provider也可以在同一個(gè)應(yīng)用程序中被訪問,但這么做并沒有什么意義。Content Provider存在的目的向其他應(yīng)用程序共享數(shù)據(jù)和允許其他應(yīng)用程序?qū)?shù)據(jù)進(jìn)行增、刪、改操作。
Android系統(tǒng)本身提供了很多Content Provider,例如,音頻、視頻、聯(lián)系人信息等等。我們可以通過這些Content Provider獲得相關(guān)信息的列表。這些列表數(shù)據(jù)將以Cursor對(duì)象返回。因此,從Content Provider返回的數(shù)據(jù)是二維表的形式。

      對(duì)于訪問Content Provider的程序,需要使用ContentResolver對(duì)象。該對(duì)象需要使用getContentResolver方法獲得,代碼如下:


  1. ContentResolver cr = getContentResolver();  
  1. ContentResolver cr = getContentResolver();  

       與Activity一樣,Content Provider也需要與一個(gè)URI對(duì)應(yīng)。每一個(gè)Content Provider可以控制多個(gè)數(shù)據(jù)集,在這種情況下,每一個(gè)數(shù)據(jù)集會(huì)對(duì)應(yīng)一個(gè)單獨(dú)的URI。所有的URI必須以“content://”開頭。
為了程序更容易維護(hù),也為了簡(jiǎn)化程序代碼,一般將URI定義成一個(gè)常量。例如,下面的常量表示系統(tǒng)的聯(lián)系人電話號(hào)碼。


  1. android.provider.Contacts.Phones.CONTENT_URI   
  1. android.provider.Contacts.Phones.CONTENT_URI  

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


1.  編寫一個(gè)繼承于android.content.ContentProvider的子類。該類是ContentProvider的核心類。在該類中會(huì)實(shí)現(xiàn) query、insert、update及delete方法。實(shí)際上調(diào)用ContentResolver類的這4個(gè)方法就是調(diào)用 ContentProvider類中與之要對(duì)應(yīng)的方法。在本文中只介紹query。至于insert、update、delete和query的用法類似。也是通過Uri傳遞參數(shù),然后在這些方法中接收這些參數(shù),并做進(jìn)一步地處理。
2.  在AndroidManifest.xml文件中配置ContentProvider。要想唯一確定一個(gè)ContentProvider,需要指定這個(gè) ContentProvider的URI,除此之外,還需要指定URI所對(duì)應(yīng)的ContentProvider類。這有些象Servlet的定義,除了要指定Servlet對(duì)應(yīng)的Web地址,還要指定這個(gè)地址所對(duì)應(yīng)的Servlet類。
現(xiàn)在來看一下Uri的具體格式,先看一下如圖5所示的URI。

          下面對(duì)圖5所示的URI的4個(gè)部分做一下解釋。

A:Content Provider URI的固定前綴,也就是說,所有的URI必須以content://開頭。
B:URI中最重要的部分。該部分是Content Provider的唯一標(biāo)識(shí)。對(duì)于第三方應(yīng)用程序來說,該部分最后使用完整的類名(包名+類名),以確保URI的唯一性。該部分需要在 AndroidManifest.xml文件中<provider>標(biāo)簽中定義,代碼如下:


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

C:這部分是URI的路徑(path)。表示URI中各種被請(qǐng)求的數(shù)據(jù)。這部分是可選的,如果Content Provider僅僅提供一種請(qǐng)求的數(shù)據(jù),那么這部分可以省略。如果Content Provider要提供多種請(qǐng)求數(shù)據(jù)。就需要添加多個(gè)路徑,甚至是子路徑。例如,“l(fā)and/bus”、“l(fā)and/train”、“sea/ship” 就指定了3種可能提供的數(shù)據(jù)。
D:這部分也是可選的。如果要傳遞一個(gè)值給Content Provider,可以通過這部分傳遞。當(dāng)然,如果不需要傳值,這部分也可以省略,省略后的URI如下所示:

  1. content://com.example.transportationprovider/trains   
  1. content://com.example.transportationprovider/trains  

    本例利用了《基于 android SDK1.5的英文電子詞典的實(shí)現(xiàn)》一文中實(shí)現(xiàn)的電子詞典程序。通過ContentProvider,將電子詞典的查詞功能共享成Cursor對(duì)象。這樣其他的應(yīng)用程序就可以通過ContentProvider來查詞英文單詞了。關(guān)于英文詞典的具體實(shí)現(xiàn)細(xì)節(jié),讀者可以通過如下的地址查看《基于 android SDK1.5的英文電子詞典的實(shí)現(xiàn)》一文。



  1. http://www. androidsdn.com/article/show/111   
  1. http://www./article/show/111  

      在電子詞典程序中需要一個(gè)DictionaryContentProvider類,該類是ContentProvider的子類。在該類中實(shí)現(xiàn)了 query方法,并根據(jù)不同的URI來返回不同的結(jié)果。讓我們先看一下DictionaryContentProvider類,然后再對(duì)這些代碼做一些解釋。


  1. ... ...  
  2. public  class  DictionaryContentProvider extends  ContentProvider  
  3. {  
  4.     private  static  UriMatcher uriMatcher;  
  5.     private  static  final  String AUTHORITY = "net.blogjava.mobile.dictionarycontentprovider" ;  
  6.     private  static  final  int  SINGLE_WORD = 1 ;  
  7.     private  static  final  int  PREFIX_WORDS = 2 ;  
  8.     public  static  final  String DATABASE_PATH = android.os.Environment  
  9.     .getExternalStorageDirectory().getAbsolutePath()  
  10.     + "/dictionary" ;  
  11.     public  static  final  String DATABASE_FILENAME = "dictionary.db" ;  
  12.     private  SQLiteDatabase database;  
  13.     static   
  14.     {  
  15.         //  添加訪問ContentProvider的Uri   
  16.         uriMatcher = new  UriMatcher(UriMatcher.NO_MATCH);  
  17.         uriMatcher.addURI(AUTHORITY, "single" , SINGLE_WORD);  
  18.         uriMatcher.addURI(AUTHORITY, "prefix/*" , PREFIX_WORDS);  
  19.     }  
  20.     //  該方法在Activity的onCreate方法之前調(diào)用   
  21.     @Override   
  22.     public  boolean  onCreate()  
  23.     {  
  24.         database = openDatabase();  
  25.         return  true ;  
  26.     }  
  27.     //  在本例中只實(shí)現(xiàn)了query方法,其他的方法(insert、update和delete)與query方法的實(shí)現(xiàn)   
  28.     //  類似   
  29.     @Override   
  30.     public  Cursor query(Uri uri, String[] projection, String selection,  
  31.             String[] selectionArgs, String sortOrder)  
  32.     {  
  33.         Cursor cursor = null ;  
  34.         switch  (uriMatcher.match(uri))  
  35.         {  
  36.             case  SINGLE_WORD:  
  37.                 //  查找指定的單詞   
  38.                 cursor = database.query("t_words" , projection, selection,  
  39.                         selectionArgs, nullnull , sortOrder);  
  40.                 break ;  
  41.             case  PREFIX_WORDS:  
  42.                 String word = uri.getPathSegments().get(1 );  
  43.                 //  查找以指定字符串開頭的單詞集合   
  44.                 cursor = database  
  45.                         .rawQuery(  
  46.                                 "select english as _id, chinese from t_words where english like ?" ,  
  47.                                 new  String[]  
  48.                                 { word + "%"  });  
  49.                 break ;  
  50.   
  51.             default :  
  52.                 throw  new  IllegalArgumentException("<"  + uri + ">格式不正確." );  
  53.         }  
  54.         return  cursor;  
  55.     }  
  56.     ... ...  
  57. }  
  1. ... ... public class DictionaryContentProvider extends ContentProvider { private static UriMatcher uriMatcher; private static final String AUTHORITY = "net.blogjava.mobile.dictionarycontentprovider"private static final int SINGLE_WORD = 1private static final int PREFIX_WORDS = 2public 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方法之前調(diào)用 @Override public boolean onCreate() { database = openDatabase(); return true; } // 在本例中只實(shí)現(xiàn)了query方法,其他的方法(insert、update和delete)與query方法的實(shí)現(xiàn) // 類似 @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; } ... ... }  

      關(guān)于DictionaryContentProvider類的代碼需要做如下的解釋。


1.  在DictionaryContentProvider類的開頭定義的AUTHORITY是訪問ContentProvider的URI的前半部分。
2.  訪問ContentProvider的URI的后半部分由uriMatcher.addURI(...)方法指定。該方法的第1個(gè)參數(shù)就是 AUTHORITY(Uri的前半部分),第2個(gè)參數(shù)是Uri的后半部分,第3個(gè)參數(shù)是與第2個(gè)參數(shù)值對(duì)應(yīng)的代碼。當(dāng)其他的應(yīng)用程序通過Uri訪問 ContentProvider時(shí)。系統(tǒng)解析Uri后,將addURI方法的第2個(gè)參數(shù)值轉(zhuǎn)換成與之對(duì)應(yīng)的代碼(第3個(gè)參數(shù)值)。
3.  addURI的第2個(gè)參數(shù)值可以使用通配符。例如,prefix/*中的*表示所有字符。prefix/abc、prefix/xxx都會(huì)匹配成功。
4.  訪問ContentProvider的URI是addURI的第1個(gè)和第2個(gè)參數(shù)值的組件,例如,按著DictionaryContentProvider中設(shè)置的兩個(gè)URI,可以分別匹配下面的兩個(gè)URI。


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


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


5.  在query方法中建議使用SQLiteDatabase對(duì)象的query方法查詢。因?yàn)閝uery方法的參數(shù)正好和DictionaryContentProvider類中的query方法的參數(shù)對(duì)應(yīng),這樣使用起來比較方便。
6.  由于安裝了ContentProvider的應(yīng)用程序會(huì)先調(diào)用ContentProvider的onCreate方法(該方法會(huì)在Activity的 onCreate方法之前調(diào)用),因此,只需要將打開或復(fù)制數(shù)據(jù)庫(kù)的方法(openDatabase)放在 DictionaryContentProvider類中,并在onCreate方法中調(diào)用即可。
7.  在DictionaryContentProvider類中只實(shí)現(xiàn)了query方法。在該方法中判斷了其他應(yīng)用程序發(fā)送的是哪一個(gè)Uri。并進(jìn)行相應(yīng)的處理。這兩個(gè)Uri一個(gè)是查詢指定單詞的,另外一個(gè)是查詢以某個(gè)字符串開頭的所有單詞的(用于顯示單詞列表)。
下面在AndroidManifest.xml文件中配置DictionaryContentProvider類。


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

       OK,現(xiàn)在來看看應(yīng)用程序如何調(diào)用ContentProvider。調(diào)用ContentProvider的關(guān)鍵是使用 getContentResolver方法來獲得一個(gè)ContentResolver對(duì)象,并通過ContentResolver對(duì)象的query方法來訪問ContentProvider。

        首先來定義兩個(gè)訪問ContentProvider的常量。

  1. public  final  String DICTIONARY_SINGLE_WORD_URI = "content://net.blogjava.mobile.dictionarycontentprovider/single" ;  
  2. public  final  String DICTIONARY_PREFIX_WORD_URI = "content://net.blogjava.mobile.dictionarycontentprovider/prefix" ;  
  1. 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";  

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

 


  1. public  void  onClick(View view)  
  2. {  
  3.     Uri uri = Uri.parse(DICTIONARY_SINGLE_WORD_URI);  
  4.     //  通過ContentProvider查詢單詞,并返回Cursor對(duì)象,然后的操作就和直接從數(shù)據(jù)中獲得   
  5.     //  Cursor對(duì)象后的操作是一樣的了   
  6.     Cursor cursor = getContentResolver().query(uri, null"english=?" ,  
  7.             new  String[]{ actvWord.getText().toString() }, null );  
  8.     String result = "未找到該單詞." ;  
  9.     if  (cursor.getCount() > 0 )  
  10.     {  
  11.         cursor.moveToFirst();  
  12.         result = cursor.getString(cursor.getColumnIndex("chinese" ));  
  13.     }  
  14.     new  AlertDialog.Builder(this ).setTitle("查詢結(jié)果" ).setMessage(result)  
  15.             .setPositiveButton("關(guān)閉"null ).show();  
  16.   
  17. }  
  1. public void onClick(View view) { Uri uri = Uri.parse(DICTIONARY_SINGLE_WORD_URI); // 通過ContentProvider查詢單詞,并返回Cursor對(duì)象,然后的操作就和直接從數(shù)據(jù)中獲得 // Cursor對(duì)象后的操作是一樣的了 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("查詢結(jié)果").setMessage(result) .setPositiveButton("關(guān)閉", null).show(); }  

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

 


  1. public  void  afterTextChanged(Editable s)  
  2. {  
  3.     if  ("" .equals(s.toString()))  
  4.         return ;  
  5.     Uri uri = Uri.parse(DICTIONARY_PREFIX_WORD_URI + "/"  + s.toString());  
  6.     //  從ContentProvider中獲得以某個(gè)字符串開頭的所有單詞的Cursor對(duì)象   
  7.     Cursor cursor = getContentResolver().query(uri, nullnullnullnull );  
  8.     DictionaryAdapter dictionaryAdapter = new  DictionaryAdapter(this ,  
  9.             cursor, true );  
  10.     actvWord.setAdapter(dictionaryAdapter);  
  11. }  
  1. public void afterTextChanged(Editable s) { if ("".equals(s.toString())) return; Uri uri = Uri.parse(DICTIONARY_PREFIX_WORD_URI + "/" + s.toString()); // 從ContentProvider中獲得以某個(gè)字符串開頭的所有單詞的Cursor對(duì)象 Cursor cursor = getContentResolver().query(uri, null, null, null, null); DictionaryAdapter dictionaryAdapter = new DictionaryAdapter(this, cursor, true); actvWord.setAdapter(dictionaryAdapter); }  


      現(xiàn)在來運(yùn)行本例,會(huì)看到如圖6所示的界面。當(dāng)查詢單詞時(shí)會(huì)顯示如圖7所示的單詞列表,查詢出結(jié)果后,會(huì)顯示如圖8所示的界面。

方式三:廣播(Broadcast)
      廣播是一種被動(dòng)跨進(jìn)程通訊的方式。當(dāng)某個(gè)程序向系統(tǒng)發(fā)送廣播時(shí),其他的應(yīng)用程序只能被動(dòng)地接收廣播數(shù)據(jù)。這就象電臺(tái)進(jìn)行廣播一樣,聽眾只能被動(dòng)地收聽,而不能主動(dòng)與電臺(tái)進(jìn)行溝通。
在應(yīng)用程序中發(fā)送廣播比較簡(jiǎn)單。只需要調(diào)用sendBroadcast方法即可。該方法需要一個(gè)Intent對(duì)象。通過Intent對(duì)象可以發(fā)送需要廣播的數(shù)據(jù)。

     先建一個(gè)android工程:sendbroadcast。在XML布局文件中放兩個(gè)組件:EditText和Button,當(dāng)單擊按鈕后,會(huì)彈出顯示 EditText組件中文本的對(duì)話框,關(guān)閉對(duì)話框后, 會(huì)使用sendBroadcast方法發(fā)送消息,并將EditText組件的文本通過Intent對(duì)象發(fā)送出去。完整的代碼如下:


  1. package  net.blogjava.mobile.sendbroadcast;  
  2. ... ...  
  3. public  class  Main extends  Activity implements  OnClickListener  
  4. {  
  5.     private  EditText editText;  
  6.     @Override   
  7.     public  void  onClick(View view)  
  8.     {  
  9.         new  AlertDialog.Builder(this ).setMessage(editText.getText().toString())  
  10.                 .setPositiveButton("確定"null ).show();       
  11.         //  通過Intent類的構(gòu)造方法指定廣播的ID   
  12.         Intent intent = new  Intent("net.blogjava.mobile.MYBROADCAST" );  
  13.         //  將要廣播的數(shù)據(jù)添加到Intent對(duì)象中     
  14.         intent.putExtra("text" , editText.getText().toString());  
  15.         //  發(fā)送廣播     
  16.         sendBroadcast(intent);  
  17.     }  
  18.     ... ...  
  19. }  
  1. 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類的構(gòu)造方法指定廣播的ID Intent intent = new Intent("net.blogjava.mobile.MYBROADCAST"); // 將要廣播的數(shù)據(jù)添加到Intent對(duì)象中 intent.putExtra("text", editText.getText().toString()); // 發(fā)送廣播 sendBroadcast(intent); } ... ... }  


       發(fā)送廣播并不需要在AndroidManifest.xml文件中注冊(cè),但接收廣播必須在AndroidManifest.xml文件中注冊(cè) receiver。下面來編寫一個(gè)接收廣播的應(yīng)用程序。首先建立一個(gè)android工程:receiver。然后編寫一個(gè)MyReceiver類,該類是 BroadcastReceiver的子類,代碼如下:

 

  1. package  net.blogjava.mobile.receiver;  
  2. ... ...  
  3. public  class  MyReceiver extends  BroadcastReceiver  
  4. {  
  5.     //  當(dāng)sendbroadcast發(fā)送廣播時(shí),系統(tǒng)會(huì)調(diào)用onReceive方法來接收廣播   
  6.     @Override   
  7.     public  void  onReceive(Context context, Intent intent)  
  8. {  
  9.     //  判斷是否為sendbroadcast發(fā)送的廣播   
  10.         if  ("net.blogjava.mobile.MYBROADCAST" .equals(intent.getAction()))  
  11.         {  
  12.             Bundle bundle = intent.getExtras();  
  13.             if  (bundle != null )  
  14.             {  
  15.                 String text = bundle.getString("text" );  
  16.                 Toast.makeText(context, "成功接收廣播:"  + text, Toast.LENGTH_LONG).show();  
  17.             }  
  18.         }  
  19.     }  
  20. }  
  1. package net.blogjava.mobile.receiver; ... ... public class MyReceiver extends BroadcastReceiver { // 當(dāng)sendbroadcast發(fā)送廣播時(shí),系統(tǒng)會(huì)調(diào)用onReceive方法來接收廣播 @Override public void onReceive(Context context, Intent intent) { // 判斷是否為sendbroadcast發(fā)送的廣播 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(); } } } }  


     當(dāng)應(yīng)用程序發(fā)送廣播時(shí),系統(tǒng)會(huì)調(diào)用onReceive方法來接收廣播,并通過intent.getAction()方法返回廣播的ID,也就是在發(fā)送廣播時(shí)Intent構(gòu)造方法指定的字符串。然后就可以從Bundle對(duì)象中獲得相應(yīng)的數(shù)據(jù)了。

     最后還需要在AndroidManifest.xml文件中注冊(cè)receiver,代碼如下:

 


  1. <!--  注冊(cè)receiver ?  
  2. <receiver android:name="MyReceiver" >  
  3.     <intent-filter>  
  4.         <action android:name="net.blogjava.mobile.MYBROADCAST"  />  
  5.     </intent-filter>  
  6. </receiver>  
  1. <!-- 注冊(cè)receiver ? <receiver android:name="MyReceiver"> <intent-filter> <action android:name="net.blogjava.mobile.MYBROADCAST" /> </intent-filter> </receiver>  

       在注冊(cè)MyReceiver類時(shí)需要使用<receiver>標(biāo)簽,android:name屬性指定MyReceiver類,<action>標(biāo)簽的android:name指定了廣播的ID。

        首先運(yùn)行receiver程序,然后就可以關(guān)閉receiver程序了。接收廣播并不依賴于程序的狀態(tài)。就算程序關(guān)閉了,仍然可以接收廣播。然后再啟動(dòng) sendbroadcast程序。并在文本框中輸入“android”,然后單擊按鈕,會(huì)彈出一個(gè)顯示文本框內(nèi)容的對(duì)話框,如圖9所示。當(dāng)關(guān)閉對(duì)話框后,會(huì)顯示一個(gè)Toast信息提示框,這個(gè)信息框是由receiver程序彈出的。如圖10所示。

方式四:AIDL服務(wù)
       服務(wù)(Service)是android系統(tǒng)中非常重要的組件。Service可以脫離應(yīng)用程序運(yùn)行。也就是說,應(yīng)用程序只起到一個(gè)啟動(dòng)Service的作用。一但Service被啟動(dòng),就算應(yīng)用程序關(guān)閉,Service仍然會(huì)在后臺(tái)運(yùn)行。

       android系統(tǒng)中的Service主要有兩個(gè)作用:后臺(tái)運(yùn)行和跨進(jìn)程通訊。后臺(tái)運(yùn)行就不用說了,當(dāng)Service啟動(dòng)后,就可以在Service對(duì)象中運(yùn)行相應(yīng)的業(yè)務(wù)代碼,而這一切用戶并不會(huì)察覺。而跨進(jìn)程通訊是這一節(jié)的主題。如果想讓應(yīng)用程序可以跨進(jìn)程通訊,就要使用我們這節(jié)講的AIDL服務(wù),AIDL的全稱是Android Interface Definition Language,也就是說,AIDL實(shí)際上是一種接口定義語言。通過這種語言定義接口后,Eclipse插件(ODT)會(huì)自動(dòng)生成相應(yīng)的Java代碼接口代碼。下面來看一下編寫一個(gè)AIDL服務(wù)的基本步驟。

1.  在Eclipse工程的package目錄中建立一個(gè)擴(kuò)展名為aidl的文件。package目錄就是Java類所在的目錄。該文件的語法類似于Java代碼。aidl文件中定義的是AIDL服務(wù)的接口。這個(gè)接口需要在調(diào)用AIDL服務(wù)的程序中訪問。
2.  如果aidl文件的內(nèi)容是正確的,Eclipse插件會(huì)自動(dòng)生成一個(gè)Java接口文件(*.java)。
3.  建立一個(gè)服務(wù)類(Service的子類)。
4.  實(shí)現(xiàn)由aidl文件生成的Java接口。
5.  在AndroidManifest.xml文件中配置AIDL服務(wù),尤其要注意的是,<action>標(biāo)簽的android:name屬性值就是客戶端要引用該服務(wù)的ID,也就是Intent類構(gòu)造方法的參數(shù)值。

      現(xiàn)在我們來編寫一個(gè)AIDL服務(wù),首先建立一個(gè)android工程:aidlservice。在aidlservice工程中有一個(gè)Main類,在Main類所有的目錄建立一個(gè)IMyService.aidl文件,內(nèi)容如下:


  1. package  net.blogjava.mobile.aidlservice;  
  2. interface  IMyService  
  3. {  
  4.     String getValue();  //  為AIDL服務(wù)的接口方法,調(diào)用AIDL服務(wù)的程序需要調(diào)用該方法   
  5. }  
  1. package net.blogjava.mobile.aidlservice; interface IMyService { String getValue(); // 為AIDL服務(wù)的接口方法,調(diào)用AIDL服務(wù)的程序需要調(diào)用該方法 }  

      在保存IMyService.aidl文件后,ODT會(huì)在gen目錄下產(chǎn)生一個(gè)IMyService.java文件,讀者可以不必管這個(gè)文件中的內(nèi)容,也不需要修改該文件的內(nèi)容。這個(gè)文件是由ODT自動(dòng)維護(hù)的,只要修改了IMyService.aidl文件的內(nèi)容,IMyService.java文件的內(nèi)容就會(huì)隨之改變。

        然后建立一個(gè)MyService類,該類是Service的子類,代碼如下:


  1. package  net.blogjava.mobile.aidlservice;  
  2. ... ...  
  3. public  class  MyService extends  Service  
  4. {  
  5.     //  IMyService.Stub類是根據(jù)IMyService.aidl文件生成的類,該類中包含了接口方法(getValue)   
  6.     public  class  MyServiceImpl extends  IMyService.Stub  
  7.     {  
  8.         @Override   
  9.         public  String getValue() throws  RemoteException  
  10.         {  
  11.             return  "從AIDL服務(wù)獲得的值." ;  
  12.         }  
  13.     }  
  14.     @Override   
  15.     public  IBinder onBind(Intent intent)  
  16. {          
  17. //  該方法必須返回MyServiceImpl類的對(duì)象實(shí)例   
  18.         return  new  MyServiceImpl();  
  19.     }  
  20. }  
  1. package net.blogjava.mobile.aidlservice; ... ... public class MyService extends Service { // IMyService.Stub類是根據(jù)IMyService.aidl文件生成的類,該類中包含了接口方法(getValue) public class MyServiceImpl extends IMyService.Stub { @Override public String getValue() throws RemoteException { return "從AIDL服務(wù)獲得的值."; } } @Override public IBinder onBind(Intent intent) { // 該方法必須返回MyServiceImpl類的對(duì)象實(shí)例 return new MyServiceImpl(); } }  

     最后需要在AndroidManifest.xml文件中配置MyService類,代碼如下:


  1. <!--  注冊(cè)服務(wù) -->  
  2. <service android:name=".MyService" >  
  3.     <intent-filter>  
  4.         <!--  指定調(diào)用AIDL服務(wù)的ID  -->  
  5.         <action android:name="net.blogjava.mobile.aidlservice.IMyService"  />  
  6.     </intent-filter>  
  7. </service>  
  1. <!-- 注冊(cè)服務(wù) --> <service android:name=".MyService"> <intent-filter> <!-- 指定調(diào)用AIDL服務(wù)的ID --> <action android:name="net.blogjava.mobile.aidlservice.IMyService" /> </intent-filter> </service>  

       下面來看看如何調(diào)用這個(gè)AIDL服務(wù)。首先建立一個(gè)android工程:aidlclient。然后將aidlservice工程中自動(dòng)生成的 IMyService.java文件復(fù)制到aidlclient工程中。在調(diào)用AIDL服務(wù)之前需要先使用bindService方法綁定AIDL服務(wù)。 bindService方法需要一個(gè)ServiceConnection對(duì)象。ServiceConnection有一個(gè) onServiceConnected方法,當(dāng)成功綁定AIDL服務(wù)且,該方法被調(diào)用。并通過service參數(shù)返回AIDL服務(wù)對(duì)象。下面是調(diào)用 AIDL服務(wù)的完成代碼。

 


  1. package  net.blogjava.mobile.aidlclient;  
  2. ... ...  
  3. public  class  Main extends  Activity implements  OnClickListener  
  4. {  
  5. private  IMyService myService = null ;  
  6. //  創(chuàng)建ServiceConnection對(duì)象   
  7.     private  ServiceConnection serviceConnection = new  ServiceConnection()  
  8.     {  
  9.         @Override   
  10.         public  void  onServiceConnected(ComponentName name, IBinder service)  
  11.         {  
  12.             // 獲得AIDL服務(wù)對(duì)象   
  13.             myService = IMyService.Stub.asInterface(service);  
  14.             try   
  15.             {  
  16.                 //  調(diào)用AIDL服務(wù)對(duì)象中的getValue方法,并以對(duì)話框中顯示該方法的返回值   
  17.                 new  AlertDialog.Builder(Main.this ).setMessage(  
  18.                         myService.getValue()).setPositiveButton("確定"null )  
  19.                         .show();  
  20.             }  
  21.             catch  (Exception e)  
  22.             {  
  23.             }  
  24.         }  
  25.         @Override   
  26.         public  void  onServiceDisconnected(ComponentName name)  
  27.         {  
  28.         }  
  29.     };  
  30.     @Override   
  31.     public  void  onClick(View view)  
  32. {  
  33.     //  綁定AIDL服務(wù)   
  34.         bindService(new  Intent("net.blogjava.mobile.aidlservice.IMyService" ),  
  35.                 serviceConnection, Context.BIND_AUTO_CREATE);  
  36.     }  
  37.     ... ...  
  38. }  
  1. package net.blogjava.mobile.aidlclient; ... ... public class Main extends Activity implements OnClickListener { private IMyService myService = null// 創(chuàng)建ServiceConnection對(duì)象 private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { // 獲得AIDL服務(wù)對(duì)象 myService = IMyService.Stub.asInterface(service); try { // 調(diào)用AIDL服務(wù)對(duì)象中的getValue方法,并以對(duì)話框中顯示該方法的返回值 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服務(wù) bindService(new Intent("net.blogjava.mobile.aidlservice.IMyService"), serviceConnection, Context.BIND_AUTO_CREATE); } ... ... }  


   在編寫AIDL服務(wù)和客戶端時(shí)要注意如下兩點(diǎn):

1.  AIDL服務(wù)中的onBind方法必須返回AIDL接口對(duì)象(MyServiceImpl對(duì)象)。該對(duì)象也是onServiceConnected事件方法的第2個(gè)參數(shù)值。
2.  bindService方法的第1個(gè)參數(shù)是Intent對(duì)象,該對(duì)象構(gòu)造方法的參數(shù)需要指定AIDL服務(wù)的ID,也就是在 AndroidManifest.xml文件中<service>標(biāo)簽的<action>子標(biāo)簽的android:name屬性的值。

 現(xiàn)在先運(yùn)行aidlservice程序,以便安裝AIDL服務(wù),然后運(yùn)行aidlclient程序,并單擊按鈕,會(huì)顯示如圖11所示的對(duì)話框。對(duì)話框中的信息就是AIDL服務(wù)接口中g(shù)etValue方法的返回值。

總結(jié)
      本文介紹了4種跨進(jìn)程通訊的方式:Activity、ContentProvider、Broadcast和AIDL Service。其中Activity可以跨進(jìn)程調(diào)用其他應(yīng)用程序的Activity;ContentProvider可以訪問其他應(yīng)用程序返回的 Cursor對(duì)象;Broadcast采用的是被動(dòng)接收的方法,也就是說,客戶端只能接收廣播數(shù)據(jù),而不能向發(fā)送廣播的程序發(fā)送信息。AIDL Service可以將程序中的某個(gè)接口公開,這樣在其他的應(yīng)用程序中就可以象訪問本地對(duì)象一樣訪問AIDL服務(wù)對(duì)象了。這4種跨進(jìn)程通訊的方式可以應(yīng)用在不同的場(chǎng)合,例如,在需要顯示可視化的界面時(shí)可以用Activity,需要返回記錄集時(shí)可以用ContentProvider。至于在應(yīng)用程序中具體要用到哪一種或幾種方式進(jìn)行跨進(jìn)程通訊,讀者可以根據(jù)實(shí)際情況進(jìn)行選擇。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    日本免费一区二区三女| 精品欧美在线观看国产| 欧美美女视频在线免费看| 亚洲精品国产美女久久久99| 日韩精品一区二区三区射精| 丰满少妇被猛烈撞击在线视频| 日本午夜精品视频在线观看| 91日韩在线视频观看| 日韩精品综合福利在线观看| 欧美日韩亚洲国产av| 后入美臀少妇一区二区| 欧美成人国产精品高清| 亚洲一区二区三区av高清| 亚洲女同一区二区另类| 热久久这里只有精品视频| 91熟女大屁股偷偷对白| 中文字幕高清不卡一区| 久草视频在线视频在线观看| 国产一区二区精品高清免费| 美国欧洲日本韩国二本道| 亚洲av成人一区二区三区在线 | 国产精品流白浆无遮挡| 日本特黄特色大片免费观看 | 日韩一区二区三区有码| 国产中文字幕一二三区| 99久久国产精品成人观看| 99热在线播放免费观看| 国产超薄黑色肉色丝袜| 麻豆亚州无矿码专区视频| 欧美黑人在线精品极品| 久久国产人妻一区二区免费| 久草国产精品一区二区| 粉嫩国产美女国产av| 亚洲国产精品肉丝袜久久| 欧美亚洲另类久久久精品| 国产午夜在线精品视频| 视频一区日韩经典中文字幕| 欧美成人免费视频午夜色| 亚洲精选91福利在线观看| 我的性感妹妹在线观看| 观看日韩精品在线视频|