## 本文大綱
複製程式碼
![一文詳盡 Android 通訊:四大元件之間 & 程式間 & 執行緒間 & 多個App間](https://i.iter01.com/images/a14d51a9297310dec2bb5385680fef2b7cb4c9a634177d7c07eb27f30fb7a65f.jpg)
看完本文能收穫什麼?按目錄索引,你可以學習到:
-
元件間的通訊,Activity,fragment,Service, Provider,Receiver
-
程式間的通訊,AIDL
-
執行緒間的通訊,Handler,AnsycTask,IntentService
-
多個App間的通訊
-
使用大型開源框架完成元件通訊,EventBus,otto
建議閱讀本文時遵循以下學習思路
-
研究物件:Activity,fragment等元件
-
資訊存在形式:Intent,Bundle,靜態變數,全域性變數,還是點選事件,觸控事件的回撥監聽,或者檔案形式(Sharepreference,SQLite,File , NetStream) ,本質就是資訊源
-
資訊傳遞的形式:網路,回撥監聽,執行緒,Intent,全域性Application
-
相同形式的思路,不會出現第二次,請讀者舉一反三
-
最後強調研究物件是單一的
Activity通訊
Activity 和 Activity
1. 常規方式:Intent Bundle
通過Intent 啟動另一個Activity時,有兩種過載方式:
startActivity(new Intent(),new Bundle());
startActivityForResult(new Intent(),FLAG,new Bundle());
複製程式碼
從引數列表就可以總結出來,有Intent,和Bundle,可以傳遞8種基本資料型別和可序列化的資料型別,比如字串和位元組陣列。提到可序列化,就引發 Intent和Bundle 的侷限性了:
- Intent Bundle 無法傳遞“不可序列化”的資料,比如Bitmap,InputStream,解決辦法有很多種,最簡單的就是將“不可序列化”的物件,轉換成位元組陣列,這裡因為主要是講解通訊,所以不展開講了。
- Intent Bundle 能傳遞的資料大小在40K以內 。
很多人不理解為什麼把Intent和Bundle放在一起談,因為Intent 底層儲存資訊的原理也是通過Bundle儲存!
2. 公有靜態變數
比如 public static String flag=“楊歐神”;
使用方式 比如在其他Activity當中 FirstActivity.flag=“OCNYang”;
修改靜態變數的值
3. 基於物理形式:
比如 File,SQLite,Sharepreference
物理形式
4. 全域性變數:
比如Application:Application是與Activity,Service齊名的元件,非常強大,它的特點是全域性元件共用,單例形式存在,在其他元件中,我們只需要 Context.getApplication()
獲得該物件的引用即可
Activity 和 Fragment,Service,BrodcastReceiver
首先都遵循,如何啟動它們,就如何傳遞資訊的原則:
1. Activity與Fragment
1. 通過建構函式傳遞
2. 獲取Fragment的例項物件
//CustFragment 是自定義的fragment,引數列表也可以自己定義咯,
getSupportFragmentManager().beginTransaction()
.add(new CustFragment(自定義的的引數列表),new String("引數"))
//------------------method two-----------------------
getSupportFragmentManager().findFragmentById(R.id.headlines_fragment);
//------------------method three----------------------
getSupportFragmentManager().findFragmentByTag("HeadLines");
複製程式碼
聰明的讀者可能會問Fragment如何與Activity通訊類似的問題,這是個好問題,請注意我們的研究的原則是單一目標原則,在這節我研究的是Activity,你的疑惑在後面都會一一解答
2. Activity與Service
Activity啟動Service的兩種方式:
//CustomService 是自定義Service,完成一些後臺操作
startService(new Intent(FirstActivity.this,CustomService.class));
bindService(new Intent(FirstActivity.this,CustomService.class)), new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//當前啟動的service 一些資料就會回撥回這裡,我們在Activity中操作這些資料即可
get
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
},flags);
複製程式碼
從啟動方式就可以看出,通過Bundle物件的形式儲存,通過Intent傳輸,來完成Activity向Service傳遞資料的操作
3. Activity與BroadcastReceiver
啟動廣播的形式也有兩種:
//method one !!!-----------------------------------------------
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
}
},new IntentFilter(),"",new Handler());
//method two !!!-----------------------------------------------
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
}
},new IntentFilter());
複製程式碼
關於method one 的第三個引數Handler很多人會很費解
參照registerReceiver中原始碼關於該Handler引數的解釋:
Handler identifying the thread that will receive the Intent. If null, the main thread of the process will be used.
定義了一個用於接收Intent的子執行緒,如果不填或者預設為null,那麼就會在主執行緒中完成接收Intent的操作
很明顯,Activity與BroadcastReceiver通訊時,用的也是Intent傳遞,Bundle儲存。
4. 通訊時的同步問題
這裡的同步通訊問題,為下文Fragment通訊作鋪墊,不是這個問題不重要,不值得引起你注意,只是我想把問題放在它最應該出現的位置。
以上只是基礎的傳遞資料的形式,大部分都是靜態的,現在有一種需求,使用者操作Activity,發出了某些指令,比如按下,滑動,觸控等操作,如何完成這些資訊傳遞呢?這就要求同步了。
同步傳遞訊息也很簡單,就是呼叫系統寫好的回撥介面
首先我們要知道,使用者 點選,觸控 這些行為 也屬於 通訊的範疇—點選和觸控屬於 資訊源; 比如使用者行為進行點選,那就實現 :
new Button(mCotext).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new ImageView(mCotext).invalidate();
}
});
複製程式碼
通過此招提示指定的ImageView:嘿!老兄,你該重新整理了
又或者 當使用者 進行觸控操作,我們需要實現放大縮小平移指定的區域:
new RelativeLayout(mCotext).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
//縮放
v.setScaleX(1f);
v.setScaleY(1f);
//平移
v.setTranslationX(1f);
v.setTranslationY(1f);
v.setTranslationY(1f);
//旋轉
v.setRotation(2f);
v.setRotationX(2f);
v.setRotationY(2f);
v.invalidate();
return true;
}
});
複製程式碼
嘿,你看,當使用者進行觸控操作,我們可以通過回撥onTouchListenter來完成“觸控”這一操作
關於View重繪機制以及優化重新整理UI的細節,不屬於本文討論範圍。
Fragment
1. Fragment 與Activity通訊
通過例項物件傳遞
同樣的,在 Fragment 中 getActivity()
可以獲取到它相關聯的 Activity 例項,就可以輕鬆獲取並且修改 Activity 的資料。
2. Fragment 與 多個Fragment通訊
首先,兩個Fragment之間不可能直接通訊(非正規因素除外),Google官方提出的解決辦法是 通過相關聯的Activity來完成兩個Fragment的通訊
只需要記住三步:
1. 定義一個介面:
在讓Fragment關聯Activity之前,可以在Fragment中定義一個介面,然後讓宿主Activity來實現這個介面。接著,在Fragment中捕獲這個介面,並且在onAttach()中 捕獲Activity例項
//只需關注介面是如何定義的,以及onAttack中的實現
public class HeadlinesFragment extends ListFragment {
//定義的介面引用
OnHeadlineSelectedListener mCallback;
// 自定義回撥介面,宿主Activity必須要實現它
public interface OnHeadlineSelectedListener {
public void onArticleSelected(int position);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// 在這裡只是為了確保Activity實現了我們定義的介面,如果沒有實現,則丟擲異常
try {
mCallback = (OnHeadlineSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnHeadlineSelectedListener");
}
}
...
}
複製程式碼
一旦 Activity 通過 OnHeadlineSelectedListener
的例項 mCallBack 回撥 onArticleSelected()
,Fragment 就可以傳遞資訊給 Activity 了
例如 下面是 ListFragment 的一個回撥方法,當使用者點選了 list 中的 item,這個 Fragment 就會通過回撥介面向宿主 Activity 傳遞事件
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// 向Activity傳遞事件資訊
mCallback.onArticleSelected(position);
}
複製程式碼
2. 在宿主Activity實現這個介面
怎麼實現?很簡單,參考下面程式碼:
public static class MainActivity extends Activity
implements HeadlinesFragment.OnHeadlineSelectedListener{
...
public void onArticleSelected(int position) {
// 使用者從從 HeadlinesFragment選中了一個標題
//響應使用者的操作,做一些業務邏輯
}
}
複製程式碼
3. 向其他Fragment傳遞資訊 (完成通訊)
宿主Activity可以通過findFragmentById()向指定的Fragment傳遞資訊,宿主Activity可以直接獲取Fragment例項,回撥Fragment的公有方法
例如:
宿主Activity 包含了一個Listfragment用來展示條目資訊,當每個條目被點選的時候,我們希望ListFragment向另外一個DetailsFragment傳遞一個資訊用來 展示不同的細節
public static class MainActivity extends Activity
implements HeadlinesFragment.OnHeadlineSelectedListener{
...
public void onArticleSelected(int position) {
// 使用者在 HeadlinesFragment中選中了一個item
//在activity中新增新的fragment
ArticleFragment articleFrag = (ArticleFragment)
getSupportFragmentManager().findFragmentById(R.id.article_fragment);
if (articleFrag != null) {
// If article 物件 可以複用, 我們就不需要建立兩遍了
// 回撥articleFrag 更新
articleFrag.updateArticleView(position);
} else {
// 建立 Fragment 併為其新增一個引數,用來指定應顯示的文章
ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// 將 fragment_container View 時中的內容替換為此 Fragment ,
// 然後將該事務新增到返回堆疊,以便使用者可以向後回滾
transaction.replace(R.id.fragment_container, newFragment);
int setTransition=TRANSIT_FRAGMENT_OPEN;
transaction.setTransition(setTransition);
transaction.addToBackStack(null);
// 執行事務
transaction.commit();
}
}
}
複製程式碼
下面我寫了一個例項來供大家理解:
各個類的聯絡圖:
![一文詳盡 Android 通訊:四大元件之間 & 程式間 & 執行緒間 & 多個App間](https://i.iter01.com/images/293b36e0030ae360e07a198808fa61de5d74380cd6dc89dec3f465d81eeef862.jpg)
效果如下:
![一文詳盡 Android 通訊:四大元件之間 & 程式間 & 執行緒間 & 多個App間](https://i.iter01.com/images/f3472486b31c7951801735fdfba219fc43f540513b74c8ccfa6cdecae2271fc9.gif)
Service
Service 與 Activity 通訊
主要是如何獲得Service例項的問題
總結來說兩步:
- 在Service定義內部類,繼承Binder,封裝Service作為內部類的屬性,並且在onBind方法中返回內部類的例項物件
- 在Activity中實現ServiceConnection ,獲取到Binder物件,再通過Binder獲取Service
public class LocalService extends Service {
// 傳遞給客戶端的Binder
private final IBinder mBinder = new LocalBinder();
//構造Random物件
private final Random mGenerator = new Random();
/**
* 這個類提供給客戶端 ,因為Service總是執行在同一個程式中的
*/
public class LocalBinder extends Binder {
LocalService getService() {
// 當客戶端回撥的時候,返回LoacalService例項
return LocalService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/**交給客戶端回撥的方法 */
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
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();
// 繫結 LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// 解綁 service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
/**button已經通過 android:onClick (attribute) 設定此方法響應使用者click*/
public void onButtonClick(View v) {
if (mBound) {
// 回撥 LocalService的方法.
//因為在主執行緒中重新整理UI,可能會造成執行緒阻塞,這裡只是為了測試
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/**定義通過bindService 回撥的Binder */
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
//先通過Binder獲得Service的內部類 LoacalBinder
LocalBinder binder = (LocalBinder) service;
// 現在可以獲得service物件了
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
複製程式碼
除了這種回撥的方式外
還有一種方式 是在Service中 傳送廣播,
比如 在 Service 中 開啟了一個子執行緒執行任務,就在子執行緒的 run() 方法中去 sendBroadcast(intent);
資料用Intent封裝,傳遞形式用廣播
AIDL 完成程式間通訊
關於程式和執行緒的細節改天詳細說明,我們首先了解一下程式和執行緒的概念:
當某個應用元件啟動且該應用沒有執行其他任何元件時,Android 系統會使用單個執行執行緒為應用啟動新的 Linux 程式。預設情況下,同一應用的所有元件在相同的程式和執行緒(稱為“主”執行緒)中執行。
如果某個應用元件啟動且該應用已存在程式(因為存在該應用的其他元件),則該元件會在此程式內啟動並使用相同的執行執行緒。
但是,我們也可以安排應用中的其他元件在單獨的程式中執行,併為任何程式建立額外的執行緒。
各類元件元素的清單檔案條目—:activity,servicer,eceiver 和 provider 均支援 android:process 屬性,此屬性可以指定該元件應在哪個程式執行。我們可以設定此屬性,使每個元件均在各自的程式中執行,或者使一些元件共享一個程式,而其他元件則不共享。 此外,我們還可以設定 android:process,使不同應用的元件在相同的程式中執行
以及瞭解一下 程式間通訊的概念
Android 利用遠端過程呼叫 (RPC) 提供了一種程式間通訊 (IPC) 機制,通過這種機制,由 Activity 或其他應用元件呼叫的方法將(在其他程式中)遠端執行,而所有結果將返回給呼叫方。這就要求把方法呼叫及其資料分解至作業系統可以識別的程度,並將其從本地程式和地址空間傳輸至遠端程式和地址空間,然後在遠端程式中重新組裝並執行該呼叫。
然後,返回值將沿相反方向傳輸回來。 Android 提供了執行這些 IPC 事務所需的全部程式碼,因此我們只需集中精力定義和實現 RPC 程式設計介面即可。
要執行 IPC,必須使用 bindService() 將應用繫結到服務上。
具體實現 可以 參考這個例項 和文末給出的官方文件
執行緒間通訊
Handler 和AsyncTask都是用來完成子執行緒和主執行緒即UI執行緒通訊的
都可以解決主執行緒 處理耗時操作,造成介面卡頓或者程式無響應ANR異常 這一類問題
Handler 是 一種機制【Handler+Message+Looper】,所有的資料通過Message攜帶,,所有的執行順序按照佇列的形式執行,Looper用來輪詢判斷訊息佇列,Handler用來接收和傳送Message
AsyncTask 是一個單獨的類,設計之初的目的只是為了 非同步方式完成耗時操作的,順便可以通知主執行緒重新整理Ui,AsyncTask的內部機制則是維護了一個執行緒池,提升效能。
在這裡提供另一種優雅的做法完成執行緒間的通訊:
擴充套件 IntentService 類
由於大多數啟動服務都不必同時處理多個請求(實際上,這種多執行緒情況可能很危險),因此使用 IntentService 類實現服務值得一試。
但如需同時處理多個啟動請求,則更適合使用該基類Service。
IntentService 執行以下操作:
- 建立預設的工作執行緒,用於在應用的主執行緒外執行傳遞給 onStartCommand() 的所有 Intent。
- 建立工作佇列,用於將一個 Intent 逐一傳遞給 onHandleIntent() 實現,這樣我們就永遠不必擔心多執行緒問題。
- 在處理完所有啟動請求後停止服務,因此我們不必呼叫 stopSelf()。
- 提供 onBind() 的預設實現(返回 null)。
- 提供 onStartCommand() 的預設實現,可將 Intent 依次傳送到工作佇列和 onHandleIntent() 實現。 綜上所述,您只需實現 onHandleIntent() 來完成客戶端提供的工作即可。(不過,我們還需要為服務提供小型建構函式。)
以下是 IntentService 的實現示例:
public class HelloIntentService extends IntentService {
/**
* 必須有建構函式 必須呼叫父 IntentService(String)帶有name的建構函式來執行工作執行緒
*/
public HelloIntentService() {
super("HelloIntentService");
}
/**
* IntentService 呼叫預設的工作執行緒啟動服務
* 當此方法結束,, IntentService 服務結束
*/
@Override
protected void onHandleIntent(Intent intent) {
// 通常在這裡會執行一些操作,比如下載檔案
//在這裡只是sleep 5 s
long endTime = System.currentTimeMillis() + 5*1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
}
}
複製程式碼
看吧,我們只需要一個建構函式和一個 onHandleIntent() 實現即可。
對於Service 當然也有基礎一點的做法,來完成多執行緒的操作,只不過程式碼量更多了:
public class HelloService extends Service {
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
// Handler 接收來自主執行緒的Message
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
//執行任務,比如下載什麼的,這裡只是 讓執行緒sleep
long endTime = System.currentTimeMillis() + 5*1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
// 手動停止服務,來處理下一個執行緒
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
//啟動執行緒. 注意我們在主執行緒中建立了一些子執行緒, 這些執行緒都沒有加鎖同步. 這些現場都是後臺執行緒,所以不會阻塞UI執行緒
HandlerThread thread = new HandlerThread("ServiceStartArguments",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
// Handler開始輪詢遍歷了
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
// 每一次請求,都會通過handler傳送Message
// startID只是為了讓我們知道正在進行的是哪一個執行緒,以便於我們停止服務
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
mServiceHandler.sendMessage(msg);
// If we get killed, after returning from here, restart
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
// 不提供 binding, 所以返回空
return null;
}
@Override
public void onDestroy() {
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
}
}
複製程式碼
多個App間的通訊
首先我們要知道以下兩點:
- Android 應用一般具有若干個Activity。每個Activity顯示一個使用者介面,使用者可通過該介面執行特定任務(比如,檢視地圖或拍照)。要將使用者從一個Activity轉至另一Activity,應用必須使用 Intent 定義做某事的“意向”。 當我們使用諸如 startActivity() 的方法將 Intent 傳遞至系統時,系統會使用 Intent 識別和啟動相應的應用元件。使用意向甚至可以讓我們的應用開始另一個應用中包含的Activity。
- Intent 可以為 顯式 以便啟動特定元件(特定的 Activity 例項)或隱式 以便啟動處理意向操作(比如“拍攝照片”)的任何元件。
1. 向另一個應用傳送使用者
Android最重要的功能之一,是可以操作其他應用,比如在我們的應用中,需要使用地圖顯示公司地址,我們無序在地圖應用程式中構建Activity,而是直接建立Intent檢視 地址的請求,Android系統之後啟動 可以在地圖上顯示 地址的應用。
1) 構建隱式的意圖
隱式意圖不用宣告要啟動的元件類名稱,而是宣告操作,比如檢視,編輯,傳送,或者獲取某項。
如果您我們的資料是Uri,可以這樣構建Intent:
//當我們的應用通過startActivity()呼叫此Intent時,電話應用會發起向指定電話號碼呼叫
Uri number = Uri.parse("tel:5551234");
Intent callIntent = new Intent(Intent.ACTION_DIAL, number);
複製程式碼
這裡還有一些其他Intent的操作和Uri資料對:
· 檢視地圖:
// 基於地址的地圖位置
Uri location = Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California");
// 基於經緯度的地圖位置
// Uri location = Uri.parse("geo:37.422219,-122.08364?z=14"); // z param is zoom level
Intent mapIntent = new Intent(Intent.ACTION_VIEW, location);
複製程式碼
· 檢視網頁:
Uri webpage = Uri.parse("http://www.ocnyang.com");
Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage);
複製程式碼
有的同學會問了,我從哪裡可以知道,Intent可以傳遞的 Uri的型別,或者其他資料型別呢?
答:可以查閱 Google Intent 的 API
2) 確認是否存在 接收意向的應用
注意:如果呼叫了意向,但裝置上沒有可用於處理意向的應用,我們的應用將崩潰。
要確認是否存在可響應意向的可用Activity,請呼叫 queryIntentActivities() 來獲取能夠處理ntent 的Activity列表。 如果返回的 List 不為空,則可以安全地使用該意向。例如:
PackageManager packageManager = getPackageManager();
List activities = packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
boolean isIntentSafe = activities.size() > 0;
複製程式碼
如果 isIntentSafe 是 true,則至少有一個應用將響應該意向。 如果它是 false,則沒有任何應用處理該意向。
3) 啟動指定Activity
當我指定意圖後,通過startActivity(intent);就可以啟動指定Activity
此處有一個Google官方的示例:
// 構建Intent
Uri location = Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California");
Intent mapIntent = new Intent(Intent.ACTION_VIEW, location);
// 確定意圖可以被接收
PackageManager packageManager = getPackageManager();
List<ResolveInfo> activities = packageManager.queryIntentActivities(mapIntent, 0);
boolean isIntentSafe = activities.size() > 0;
//啟動指定應用
if (isIntentSafe) {
startActivity(mapIntent);
}
複製程式碼
4) 顯示應用選擇器
比如我們要完成 分享操作,使用者可以使用多個App完成分享,我們應明確顯示選擇器對話方塊,如圖
![intent-chooser](https://i.iter01.com/images/8b1bb9f9b6c0b569a240df02f86f8a1944b4a4a0ac521817ff968e6cecc6c284.png)
要顯示選擇器,需要使用Intent的createChooser()方法 建立Intent,並將其傳遞至 startActivity()
Intent intent = new Intent(Intent.ACTION_SEND);
...
String title = getResources().getString(R.string.chooser_title);
// Create intent to show chooser
Intent chooser = Intent.createChooser(intent, title);
// Verify the intent will resolve to at least one activity
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(chooser);
}
複製程式碼
這將顯示一個對話方塊,其中有響應傳遞給 createChooser() 方法的意向的應用列表,並且將提供的文字用作 對話方塊標題
2. 接收其他Activity返回的結果
通過Intent.startActivityForResult()來完成。
首先在啟動另一個Activity時,我們需要指定request code以便返回結果時,我們可以正常處理它。
static final int PICK_CONTACT_REQUEST = 1; // The request code
...
private void pickContact() {
Intent pickContactIntent = new Intent(Intent.ACTION_PICK, Uri.parse("content://contacts"));
pickContactIntent.setType(Phone.CONTENT_TYPE);
startActivityForResult(pickContactIntent, PICK_CONTACT_REQUEST);
}
複製程式碼
當使用者完成操作後,返回資料,系統會呼叫Activity的 onActivityResult()方法,
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// 檢查requestCode是否真確
if (requestCode == PICK_CONTACT_REQUEST) {
// 確保請求時成功的
if (resultCode == RESULT_OK) {
// 完成我們的業務邏輯
}
}
}
複製程式碼
為了成功處理結果,我們必須瞭解Intent的格式,比如聯絡人返回的是帶內容的URI,照相機返回的是Bitmap
如何根據返回的URI來讀取資料,我們需要對ContentResolver 和 ContentProvider 有了解
下面就是一個三者結合的獲取聯絡人的例項:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// 檢查requestCode
if (requestCode == PICK_CONTACT_REQUEST) {
// 確保請求成功
if (resultCode == RESULT_OK) {
//獲得選擇的聯絡人的URI
Uri contactUri = data.getData();
// 我們只需要NUMBER這一列的資訊,
String[] projection = {Phone.NUMBER};
// 顯示根據NUMBER查詢的結果
// We don't need a selection or sort order (there's only one result for the given URI)
// 在這裡我們並沒有對查詢的結果進行排序,因為在主執行緒中進行這種資料庫操作,有可能阻塞執行緒
//優化方案是非同步完成排序的操作,這裡只是展示多個App間的通訊
Cursor cursor = getContentResolver()
.query(contactUri, projection, null, null, null);
cursor.moveToFirst();
//從NUMBER那一列當中取回phone NUMBER
int column = cursor.getColumnIndex(Phone.NUMBER);
String number = cursor.getString(column);
//接下來就是要操作這些phone number了
}
}
}
複製程式碼
3. 接收其他Activity返回的結果
要允許其他應用開始您的Activity,需要 在相應元素的宣示說明檔案中新增一個 元素。
例如,此處有一個在資料型別為文字或影象時處理 ACTION_SEND 意向的意向過濾器:
<activity android:name="ShareActivity">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
<data android:mimeType="image/*"/>
</intent-filter>
</activity>
複製程式碼
定義操作,通常是系統定義的值之一,比如ACTION_SEND 或 ACTION_VIEW。
定義與Intent關聯的資料,只需通過 android:mimeType 指定我們接收的資料型別,比如text/plain 或 image/jpeg。
所有的隱式Intent,都使用 CATEGORY_DEFAULT 進行定義
4. 處理Activity中的Intent
當Activity開始時,呼叫getIntent檢索開始Activity的Intent,
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Intent intent = getIntent();
Uri data = intent.getData();
// 指出接收的資料型別
if (intent.getType().indexOf("image/") != -1) {
// 處理帶有圖片的Intent
} else if (intent.getType().equals("text/plain")) {
// 處理帶有文字的Intent
}
}
複製程式碼
5. 向指定Activity中返回資料
只需呼叫setResult指定結果程式碼和Intent
Intent result = new Intent("com.example.RESULT_ACTION", Uri.parse("content://result_uri");
setResult(Activity.RESULT_OK, result);
finish();
複製程式碼
記住必須為結果指定結果碼,通常為 RESULT_OK 或 RESULT_CANCELED。
我們也可以在Intent中 用Bundle儲存額外的資訊
細心的同學可能發現一個問題:
啟動 Activity 有 startActivity() 和 startActivityForResult() 兩種啟動方式,返回結果的形式id偶有 setResult() 嗎?
如果開啟當前Activity的Intent可能需要結果,只需呼叫 setResult()。 如果原始 Activity 已呼叫 startActivityForResult(),則系統將向其傳遞您提供給 setResult() 的結果;否則,會忽略結果。
使用大型開源框架完成元件間的通訊
Github上非常火的兩大通訊元件EventBus和otto:
1. EventBus
EventBus 是一個 Android 事件釋出/訂閱框架,通過解耦釋出者和訂閱者簡化 Android 事件傳遞,這裡的事件可以理解為訊息,本文中統一稱為事件。事件傳遞既可用於 Android 四大元件間通訊,也可以使用者非同步執行緒和主執行緒間通訊等等。
傳統的事件傳遞方式包括:Handler、BroadCastReceiver、Interface 回撥,相比之下 EventBus 的優點是程式碼簡潔,使用簡單,並將事件釋出和訂閱充分解耦。
1)概念:
事件(Event):又可稱為訊息,本文中統一用事件表示。其實就是一個物件,可以是網路請求返回的字串,也可以是某個開關狀態等等。事件型別(EventType)指事件所屬的 Class。
事件分為一般事件和 Sticky 事件,相對於一般事件,Sticky 事件不同之處在於,當事件釋出後,再有訂閱者開始訂閱該型別事件,依然能收到該型別事件最近一個 Sticky 事件。
訂閱者(Subscriber):訂閱某種事件型別的物件。當有釋出者釋出這類事件後,EventBus 會執行訂閱者的 onEvent 函式,這個函式叫事件響應函式。訂閱者通過 register 介面訂閱某個事件型別,unregister 介面退訂。訂閱者存在優先順序,優先順序高的訂閱者可以取消事件繼續向優先順序低的訂閱者分發,預設所有訂閱者優先順序都為 0。
釋出者(Publisher):釋出某事件的物件,通過 post 介面釋出事件。
本專案較為簡單,總體設計和流程圖:
![EventBus-Publish-Subscribe](https://i.iter01.com/images/afed02f5d836b2222ac63886e58be53f221edf0f925e4097194ab6d66ef3b43e.png)
使用方式:
build.gradle 中加入依賴
compile 'org.greenrobot:eventbus:3.0.0'
複製程式碼
程式碼中指需三步
1. 定義事件:只需要是一個Java類
public class MessageEvent {
public final String message;
public MessageEvent(String message) {
this.message = message;
}
}
複製程式碼
2. 完成訂閱者
//MessageEvent被Eventbus post提交的時候 將會回撥這個方法
//這種方式 提示我們可以直接定義自己的事件
@Subscribe
public void onMessageEvent(MessageEvent event){
Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}
// 當一些其他事件post提交的時候,回撥這個方法
@Subscribe
public void handleSomethingElse(SomeOtherEvent event){
doSomethingWith(event);
複製程式碼
在Activity或者Fragment中繫結訂閱者
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
複製程式碼
3. 釋出事件:
EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
複製程式碼
本文參考並翻譯
- Google 課程 Communicating with Other Fragments
- Google 解釋 AIDL程式間通訊
- Google 解釋 Handler
- Google 解釋 AsyncTask
- Google BroadcastReceiver API
- Google 課程 Interacting with Other Apps
- Google 解釋 contentprovider
- Google BroadcastReceiver 課程
- Google Service 課程
- Google 解釋 程式和執行緒
- EventBus官方文件
結尾
好了,這篇文章就到這了。作為新的一年,今天和大家瞎聊幾句,剛過完年,大家是不是和我一樣呢?
![我又胖了](https://i.iter01.com/images/0e887cd14c8a92377a8bde563e9932e6022067dc10ebac7cddc420f9210c4cf8.png)
開玩笑的!說說自己吧,其實現在的過年給作者的感覺是年味越來越淡了,今年回家作者在家一直是大門不出二門不邁,也沒有趕幾家親戚。倒是在家自由自在的當了幾天大少爺,每天睡到飯做好醒,當然也少不了遭父母嫌棄(嘻嘻~),更少不了被家裡催著相親,還好家人態度不是特別強硬,被我都拒見了(唉,程式猿共同的痛啊!在新的一年裡,剛好也是我的本命年,找個女朋友一定是今年的首要任務啊,嘿嘿~)。
再和大家聊聊工作方面,唉,每每聽到身邊的朋友和你們談論拿了多少年終獎 / 抽獎中MacBook / 搶了多少老闆紅包 / 老闆發了多少開門紅包,我都是無比羨慕啊,自己自來到這家公司什麼節日福利啊、年終獎啊、旅遊獎勵啊都與我再無瓜葛了有沒有啊!說起來都是淚啊~~~
當初,選擇這家初創公司,是十分看好這家公司的業務方向,也有很大的市場能夠開發,想著跟著拼一波試試。雖然技術團隊人員不是太多,Android開發方面也是我獨立負責,但還算能保證業務的運作。但初創公司總是有很多你想不到的各種各樣的問題,最大的問題我自己感覺是領導層根本不懂得留住有能力的人才,在這公司工作半年,身邊各部門的同事換了近兩輪,就技術部好點也流失近一半。領導卻不在意,總是告訴你,人走了再去招新的就行了,沒一點初創公司應有的氣氛和人情味。就Android平臺來言,公司只是把開發的App作為一個銷售渠道,根本不會作為一個產品來做和維護。像大多走不遠的初創公司一樣,開發一個 v1.0 就沒有然後了。就轉戰下一個專案了。
幹到現在自己也算失望盡了,目前看看新請來的CTO的見識如何了,實在不行只能另尋他途了。
本來不該寫在這的,作為新的一年推的第一篇文章,大家原諒我的囉嗦吧。