什么是通信?
通信 ,顾名思义,指的就是信息的传递或者交换
看完本文能收获什么?
按目录索引,你可以学习到
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间的通信
- 使用大型开源框架完成组件通信,EventBus,otto
- 网络通信基础篇: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 解释 进程和线程