欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

从源码剖析Android中的Intent组件

程序员文章站 2024-02-27 13:36:45
我们知道,intent主要用来激活安卓几大组件,那么它具体是怎样来激活的?激活时是否可以携带java对象?为何要将对象序列化后才能传递? 一、intent官网解释 in...

我们知道,intent主要用来激活安卓几大组件,那么它具体是怎样来激活的?激活时是否可以携带java对象?为何要将对象序列化后才能传递?

一、intent官网解释
intent可以被startactivity用来加载activity,也可以被broadcastintent发送给指定的broadreceiver组件,
或者被startservice、bingservice来与后台service通信。
intent最主要作用就是加载activity,好比activity之间的胶水。
intent数据结构:

  • action:所要执行的动作;(例如:action_call创建打电话activity;action_battery_low 发出广播警告电池电量低,)
  • data: 要使用的数据(uri);
  • category:关于目标组件的信息;
  • component:目标组件的类名;
  • extras :这是bundle数据。

intent解析:

  • 显式intent,指定了目标组件的类名,即component,则已知目标组件,不需解析;
  • 隐式intent,未指定目标组件component,或者不知道、不关心谁来接收intent,需要android自己去解析找到目标组件。

隐式intent解析方法:

1.在androidmanifest.xml里所有<intent-filter>及其中定义的intent;
2.通过packagemanager(获取当前设备所安装的应用程序package)查找能处理这个intent的component。匹配action、type、category三个变量来寻找。
二、简单解释:
intent可以激活andorid的三大组件:activity、service和broadcastreceiver。使用intent时一般要显式指定目标组件,若未指定则要根据intent附带的action、type、category三个值来解析,查找能处理的组件。

三、问题:intent如何实现组件的切换,具体流程?
1、基本方法:(以启动activity为例)

intent i = new intent(mainactivity.this, targetactivity.class);
startactivity(i);

2、实例化intent:

/**
* 创建一个intent,直接指定intent要激活的**组件类名**,而不用依赖**系统去解析**合适的类来处理intent
* @param packagecontext 要执行这个intent的context对象
* @param cls intent要激活的组件类名
*/
public intent(context packagecontext, class cls) {
//创建一个组件并赋值给intent的component成员
  mcomponent = new componentname(packagecontext, cls);
}

3、启动activity

startactivity(i) ->
startactivity(intent intent, @nullable bundle options)->
startactivityforresult(intent, -1, options)


/** 
*startactivityforresult(intent intent, int requestcode, @nullable bundle options)
* 加载一个activity并获取结果
* @param intent 要启动的intent.
* @param requestcode 如果大于0则会被返回,且只有返回值返回成功后才会显示视图
* @param options 其他信息.
*/
public void startactivityforresult(intent intent, int requestcode, @nullable bundle options) {
if (mparent == null) { //如果没有父activity;instrumentation是用来与程序指南清单androidmanifest文件交互的。
  instrumentation.activityresult ar =
  minstrumentation.execstartactivity(this, mmainthread.getapplicationthread(), mtoken, this,intent, requestcode, options); //执行startactivity命令
..... 
} else { //如果有父activity
   if (options != null) {
     mparent.startactivityfromchild(this, intent, requestcode, options);
   } .....
}

4、执行startactivity命令核心代码:
启动activity的任务交给了底层activitymanagernative来做。

intent.migrateextrastreamtoclipdata(); //将intent里的bundle数据进行处理以便给底层处理
intent.preparetoleaveprocess(); //准备离开应用程序进程,进入activitymanagerservice进程(意味着bundle的数据要在进程间传递)
int result = activitymanagernative.getdefault().startactivity(whothread,   
who.getbasepackagename(), intent,
intent.resolvetypeifneeded(who.getcontentresolver()),
token, target != null ? target.membeddedid : null,
requestcode, 0, null, options); //调用系统的activity manager服务来启动新的activity。考虑如果是显式intent,则直接找对对应的组件类(此处是activity组件);如果是隐式intent,为指定目标组件类名,则自动去application->system搜索合适的组件来处理。
//todo:具体的系统级代码下次进行分析

四、核心问题:为何intent不能直接在组件间传递对象而要通过序列化机制?
根据上面代码可以看到,intent在启动其他组件时,会离开当前应用程序进程,进入activitymanagerservice进程(intent.preparetoleaveprocess()),这也就意味着,intent所携带的数据要能够在不同进程间传输。首先我们知道,android是基于linux系统,不同进程之间的java对象是无法传输,所以我们此处要对对象进行序列化,从而实现对象在 应用程序进程 和 activitymanagerservice进程 之间传输。
而parcel或者serializable都可以将对象序列化,其中,serializable使用方便,但性能不如parcel容器,后者也是android系统专门推出的用于进程间通信等的接口。

附加知识:
在不同进程之间,常规数据类型可以直接传递,如整数,以传递字符串为例,要从a进程传递到b进程,只需在b进程的内存区开辟一样大小的空间,然后复制过去即可。
但是,对象却不能直接跨进程传递。即使成员变量值能传递过去,成员方法是无法传递过去的,此时如果b进程要调用成员方法则出错。
具体传递对象的方法:
1. 在进程a中把类中的非默认值的属性和类的唯一标志打成包(这就叫序列化);
2. 把这个包传递到进程b;
3. 进程b接收到包后,根据类的唯一标志把类创建出来(java反射机制);
4. 然后把传来的属性更新到类对象中。
这样进程a和进程b中就包含了两个完全一样的类对象。

五、intent如何实现对象传递?
object implements serializable {...};bundle.putserializable(key, object);
object implements parcelable {...} ; bundle.putparcelable(key, object);
serializable接口:这是java的序列化技术,将java对象序列化为二进制文件。让对象实现serializable接口,使用objectinputstream 和 objectoutputstream 进行对象读写。
parcelable接口:这是android提供的用作封装数据的容器,封装后的数据可以通过intent或ipc来传递。只有基本类型和实现了parcelable接口的类才能被放入parcel中。
六、serializable接口 - java
属于java序列化机制:只需让java类实现该接口,不用实现任何方法,即可标记该类可序列化。

class person implements serializable {...}
person per = new person();
bundle.putserializable("person", per); //传递person对象的引用

person mperson = (person)getintent().**getserializableextra**("person");

注意:如果此处序列化类person内部包含其他类(如:personinfo)的引用,如:

class person implements serializable {
   personinf**o info;
}

那么所引用的类必须也可序列化,即实现serializable接口。因为person对象在序列化过程中,也会对成员变量序列化。

七、parcelable接口 - android
此处围绕 - android中如何使用parcel实现对象的传递 - 简单介绍一下原因。
首先要了解android里面的parcel容器。

parcel是一个容器,用来存储可通过ibindler传送的消息(数据或对象引用)。
主要用于轻量级、高性能ipc进程间通信的消息容器。在android里,一个“process”是一个标准linux进程,一般而言一个进程无法接触到另一个进程的内存区。而通过parcel,android系统会将对象分解成可序列化与反序列化,从而实现进程间通信。
不过,parcel同样可用于进程内通信,主要实现在应用程序的不同组件之间传递数据。例如,我们可以使用intent封装parcel对象在activity之间传递。
简单来说,parcel容器实现了进程内与进程间通信,而且还能实现远程调用。

组件间传递对象的具体方法:

让要传递的对象所属类实现 parcelable 接口;
实现 describecontents 方法;
实现抽象方法 writetoparcel,用于获取对象的当前状态并写入一个parcel容器中;
给该目标类添加一个静态域 creator ,它是一个实现了parcelable.creator接口的对象;
添加一个参数为一个parcel对象的构造函数,creator会调用这个构造函数来重新改造我们的对象。
问题:
为什么已经有了java的serializable接口还要创建一个parcelable接口?
性能
虽然parcelable使用起来更复杂一点,但是它的性能更好。

parcelable的限制:

当使用parcelable来传递图片bitmap时不太理想,虽然bitmap也实现了parcelable接口。比较优的方法是传递
parcelable不能用来当做常规的序列化存储,因为android系统版本不同,parcelable的具体实现方法也不完全一样,可能导致无法读取parcel数据。