详解 Android 通信

摘自本人16年的的博客《详解 Android 通信》

什么是通信?#

通信 ,顾名思义,指的就是信息的传递或者交换

看完本文能收获什么?#

按目录索引,你可以学习到
1. 组件间的通信,Activity,fragment,Service, Provider,Receiver
2. 进程间的通信,AIDL
3. 线程间的通信,Handler,AnsycTask,IntentService
4. 多个App间的通信
5. 使用大型开源框架完成组件通信,EventBus,otto
6. 网络通信基础篇:Google 课程–AnsycTask+HttpClient
7. 网络通信提高篇:开源框架Ansyc-Httpclient,okttp,Retrofit

建议阅读本文时遵循以下学习思路#

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 的局限性了:

  • Intent Bundle 无法传递“不可序列化”的数据,比如Bitmap,InputStream,解决办法有很多种,最简单的就是将“不可序列化”的对象,转换成字节数组,这里因为主要是讲解通信,所以不展开讲了。
  • Intent Bundle 能传递的数据大小在40K以内 。

PS : 很多人不理解为什么把Intent和Bundle放在一起谈,因为Intent 底层存储信息的原理也是通过Bundle存储!

2. 公有静态变量

比如 public static String flag=“中国”;

使用方式 比如 在其他Activity当中 FirstActivity.flag=“china”; 修改 静态变量的值

3. 基于物理形式:

比如 File,SQLite,Sharepreference 物理形式

4. 全局变量:

比如Application:Application是与Activity,Service齐名的组件,非常强大,它的特点是全局组件共用,单例形式存在,在其他组件中,我们只需要Context.getApplication()获得该对象的引用即可

Activity 和Fragment,Service,BrodcastReceiver

,首先都遵循,如何启动它们,就如何传递信息的原则:

1. Activity与Fragment

1. 通过构造函数传递 2.获取Fragment的实例对象

1
2
3
4
5
6
7
8
//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的两种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

//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

启动广播的形式也有两种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//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,发出了某些指令,比如按下,滑动,触摸等操作,如何完成这些信息传递呢?这就要求同步了。

同步传递消息也很简单,就是调用系统写好的回调接口

首先我们要知道,用户 点击,触摸 这些行为 也属于 通信的范畴—点击和触摸属于 信息源;
比如用户行为进行点击,那就实现 :

1
2
3
4
5
6
new Button(mCotext).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new ImageView(mCotext).invalidate();
}
});

通过此招提示指定的ImageView:嘿!老兄,你该刷新了

又或者 当用户 进行触摸操作,我们需要实现放大缩小平移指定的区域:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//只需关注接口是如何定义的,以及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传递事件

1
2
3
4
5
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// 向Activity传递事件信息
mCallback.onArticleSelected(position);
}

2. 在宿主Activity实现这个接口

怎么实现?很简单,参考下面代码:

1
2
3
4
5
6
7
8
9
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传递一个信息用来 展示不同的细节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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);
    }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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 的实现示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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 当然也有基础一点的做法,来完成多线程的操作,只不过代码量更多了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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:

1
2
3
//当我们的应用通过startActivity()调用此Intent时,电话应用会发起向指定电话号码呼叫
Uri number = Uri.parse("tel:5551234");
Intent callIntent = new Intent(Intent.ACTION_DIAL, number);

这里还有一些其他Intent的操作和Uri数据对:

  • 查看地图:

    1
    2
    3
    4
    5
    // 基于地址的地图位置
    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);
  • 查看网页:

1
2
Uri webpage = Uri.parse("http://www.android.com");
Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage);

有的同学会问了,我从哪里可以知道,Intent可以传递的 Uri的类型,或者其他数据类型呢?
答:可以查阅Google Intent的Api

2) 确认是否存在 接收意向的应用

注意:如果调用了意向,但设备上没有可用于处理意向的应用,我们的应用将崩溃。

要确认是否存在可响应意向的可用Activity,请调用 queryIntentActivities() 来获取能够处理ntent 的Activity列表。 如果返回的 List 不为空,则可以安全地使用该意向。例如:

1
2
3
4
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官方的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 构建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的createChooser()方法 创建Intent,并将其传递至startActivity()

1
2
3
4
5
6
7
8
9
10
11
12
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以便返回结果时,我们可以正常处理它。

1
2
3
4
5
6
7
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()方法,

1
2
3
4
5
6
7
8
9
10
@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 有了解

下面就是一个三者结合的获取联系人的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@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 意向的意向过滤器:

1
2
3
4
5
6
7
8
<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,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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

1
2
3
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 接口发布事件。

本项目较为简单,总体设计和流程图:

2)使用方式:

  • build.gradle 中加入依赖
    1
    compile 'org.greenrobot:eventbus:3.0.0'
  • 代码中指需三步

1. 定义事件:只需要是一个Java类

1
2
3
4
5
6
7
  public class MessageEvent {
public final String message;

public MessageEvent(String message) {
this.message = message;
}
}

2. 完成订阅者

1
2
3
4
5
6
7
8
9
10
11
12
//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中绑定订阅者

1
2
3
4
5
6
7
8
9
10
11
@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!"));
​```

很遗憾未完成的部分#

  1. Binder 机制以及应用
  2. 网络通信基础篇:Google 课程–AnsycTask+HttpClient
  3. 网络通信提高篇:开源框架Ansyc-Httpclient,okttp,Retrofit

本文参考并翻译#

  1. Google 课程 Communicating with Other Fragments
  2. Google 解释 AIDL进程间通信
  3. Google 解释 Handler
  4. Google 解释 AsyncTask
  5. Google BroadcastReceiver API
  6. Google 课程 Interacting with Other Apps
  7. Google 解释 contentprovider
  8. Google BroadcastReceiver 课程
  9. Google Service 课程
  10. Google 解释 进程和线程
  11. EventBus官方文档
点击查看
-------------------本文结束 感谢您的阅读-------------------