美文网首页Android技术程序员的成长日记
[Android源码分析] - 轻松学习Intent机制

[Android源码分析] - 轻松学习Intent机制

作者: wingjay | 来源:发表于2015-03-25 11:01 被阅读2273次

    我们知道,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来做。具体关于ActivityManagerNative见另一篇文章。
    Android源码学习之六——ActivityManager框架解析

    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中就包含了两个完全一样的类对象。
    

    详细分析见我的另一篇文章-Andorid的IPC机制和序列化研究

    五、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

    详细分析见我的另一篇文章-Andorid的IPC机制和序列化研究

    此处围绕 - Android中如何使用Parcel实现对象的传递 - 简单介绍一下原因。
    首先要了解Android里面的Parcel容器。

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

    简单来说,Parcel容器实现了进程内与进程间通信,而且还能实现远程调用。

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

    1. 让要传递的对象所属类实现 Parcelable 接口;
    1. 实现 *describeContents * 方法;
    2. 实现抽象方法 writeToParcel,用于获取对象的当前状态并写入一个Parcel容器中;
    3. 给该目标类添加一个静态域 *CREATOR *,它是一个实现了Parcelable.Creator接口的对象;
    4. 添加一个参数为一个Parcel对象的构造函数,CREATOR会调用这个构造函数来重新改造我们的对象。

    问题:

    1. 为什么已经有了Java的Serializable接口还要创建一个Parcelable接口?
      性能
      虽然Parcelable使用起来更复杂一点,但是它的性能更好。

    2. Parcelable的限制:

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

    谢谢!

    wingjay

    image

    相关文章

      网友评论

      • 被风扬起的沙:Parcel同样可用于进程内通信,主要实现在应用程序的不同组件之间传递数据。例如,我们可以使用Intent封装Parcel对象在Activity之间传递。
        ---------使用 Intent 打开 activity 的时候不是进程间的通讯吗?应用程序跟系统进程的通讯啊,怎么又变成进程内的了啊?
      • UniGenius_Mx:关键的一句说了一半,bitmap比较优的方法是传递什么? Activity之间传递bitmap数据用什么好? :pray:
        拉贝:@UniGenius_Mx 可以传递图片文件地址地址,或者静态类

      本文标题:[Android源码分析] - 轻松学习Intent机制

      本文链接:https://www.haomeiwen.com/subject/vybgxttx.html