一文詳盡 Android 通訊:四大元件之間 & 程式間 & 執行緒間 & 多個App間

OCN_Yang發表於2017-12-14
     ## 本文大綱  
複製程式碼

一文詳盡 Android 通訊:四大元件之間 & 程式間 & 執行緒間 & 多個App間

看完本文能收穫什麼?按目錄索引,你可以學習到:

  1. 元件間的通訊,Activity,fragment,Service, Provider,Receiver

  2. 程式間的通訊,AIDL

  3. 執行緒間的通訊,Handler,AnsycTask,IntentService

  4. 多個App間的通訊

  5. 使用大型開源框架完成元件通訊,EventBus,otto

建議閱讀本文時遵循以下學習思路

  1. 研究物件:Activity,fragment等元件

  2. 資訊存在形式:Intent,Bundle,靜態變數,全域性變數,還是點選事件,觸控事件的回撥監聽,或者檔案形式(Sharepreference,SQLite,File , NetStream) ,本質就是資訊源

  3. 資訊傳遞的形式:網路,回撥監聽,執行緒,Intent,全域性Application

  4. 相同形式的思路,不會出現第二次,請讀者舉一反三

  5. 最後強調研究物件是單一的

Activity通訊

Activity 和 Activity

1. 常規方式:Intent Bundle

通過Intent 啟動另一個Activity時,有兩種過載方式:

startActivity(new Intent(),new Bundle());
startActivityForResult(new Intent(),FLAG,new Bundle());
複製程式碼

從引數列表就可以總結出來,有Intent,和Bundle,可以傳遞8種基本資料型別和可序列化的資料型別,比如字串和位元組陣列。提到可序列化,就引發 Intent和Bundle 的侷限性了:

  1. Intent Bundle 無法傳遞“不可序列化”的資料,比如Bitmap,InputStream,解決辦法有很多種,最簡單的就是將“不可序列化”的物件,轉換成位元組陣列,這裡因為主要是講解通訊,所以不展開講了。
  2. 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間

效果如下:

一文詳盡 Android 通訊:四大元件之間 & 程式間 & 執行緒間 & 多個App間

Fragment 通訊 Demo 例項

Service

Service 與 Activity 通訊

主要是如何獲得Service例項的問題
總結來說兩步:

  1. 在Service定義內部類,繼承Binder,封裝Service作為內部類的屬性,並且在onBind方法中返回內部類的例項物件
  2. 在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間的通訊

首先我們要知道以下兩點:

  1. Android 應用一般具有若干個Activity。每個Activity顯示一個使用者介面,使用者可通過該介面執行特定任務(比如,檢視地圖或拍照)。要將使用者從一個Activity轉至另一Activity,應用必須使用 Intent 定義做某事的“意向”。 當我們使用諸如 startActivity() 的方法將 Intent 傳遞至系統時,系統會使用 Intent 識別和啟動相應的應用元件。使用意向甚至可以讓我們的應用開始另一個應用中包含的Activity。
  2. 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

要顯示選擇器,需要使用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

使用方式:

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!"));
複製程式碼

本文參考並翻譯

文章來源

結尾

好了,這篇文章就到這了。作為新的一年,今天和大家瞎聊幾句,剛過完年,大家是不是和我一樣呢?

我又胖了

開玩笑的!說說自己吧,其實現在的過年給作者的感覺是年味越來越淡了,今年回家作者在家一直是大門不出二門不邁,也沒有趕幾家親戚。倒是在家自由自在的當了幾天大少爺,每天睡到飯做好醒,當然也少不了遭父母嫌棄(嘻嘻~),更少不了被家裡催著相親,還好家人態度不是特別強硬,被我都拒見了(唉,程式猿共同的痛啊!在新的一年裡,剛好也是我的本命年,找個女朋友一定是今年的首要任務啊,嘿嘿~)。

再和大家聊聊工作方面,唉,每每聽到身邊的朋友和你們談論拿了多少年終獎 / 抽獎中MacBook / 搶了多少老闆紅包 / 老闆發了多少開門紅包,我都是無比羨慕啊,自己自來到這家公司什麼節日福利啊、年終獎啊、旅遊獎勵啊都與我再無瓜葛了有沒有啊!說起來都是淚啊~~~
當初,選擇這家初創公司,是十分看好這家公司的業務方向,也有很大的市場能夠開發,想著跟著拼一波試試。雖然技術團隊人員不是太多,Android開發方面也是我獨立負責,但還算能保證業務的運作。但初創公司總是有很多你想不到的各種各樣的問題,最大的問題我自己感覺是領導層根本不懂得留住有能力的人才,在這公司工作半年,身邊各部門的同事換了近兩輪,就技術部好點也流失近一半。領導卻不在意,總是告訴你,人走了再去招新的就行了,沒一點初創公司應有的氣氛和人情味。就Android平臺來言,公司只是把開發的App作為一個銷售渠道,根本不會作為一個產品來做和維護。像大多走不遠的初創公司一樣,開發一個 v1.0 就沒有然後了。就轉戰下一個專案了。
幹到現在自己也算失望盡了,目前看看新請來的CTO的見識如何了,實在不行只能另尋他途了。

本來不該寫在這的,作為新的一年推的第一篇文章,大家原諒我的囉嗦吧。

相關文章