详解 Android 通信史上最全
Posted 杨超凡
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了详解 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的实例对象
//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();
}
}
}
下面我写了一个实例来供大家理解:
各个类的联系图:
效果如下:
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.android.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的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 接口发布事件。
本项目较为简单,总体设计和流程图:
2)使用方式:
- 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!"));
```
很遗憾未完成的部分
- Binder 机制以及应用
- 网络通信基础篇:Google 课程–AnsycTask+HttpClient
- 网络通信提高篇:开源框架Ansyc-Httpclient,okttp,Retrofit
本文参考并翻译
- 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官方文档
以上是关于详解 Android 通信史上最全的主要内容,如果未能解决你的问题,请参考以下文章