一文詳盡 Android 通訊:四大組件之間 & 進程間 & 線程間 & 多個App間

## 本文大綱  
複製代碼

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

  1. 組件間的通訊,Activity,fragment,Service, Provider,Receiverandroid

  2. 進程間的通訊,AIDLweb

  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();
        }
    }
}
複製代碼

下面我寫了一個實例來供你們理解:

各個類的聯繫圖:

效果以下:

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的見識如何了,實在不行只能另尋他途了。

原本不應寫在這的,做爲新的一年推的第一篇文章,你們原諒個人囉嗦吧。

相關文章
相關標籤/搜索