likes
comments
collection
share

每日一题:Intent的原理,作用,可以传递哪些类型的参数?

作者站长头像
站长
· 阅读数 50

在android面试中,我们常会遇到Framework面试相关问题,而今天要分享的就是Intent的原理,作用,可以传递哪些类型的参数?

其主要考察的是程序员对对 Intent 的理解。

问题正解:

Android中的Intent是一个非常重要且常用的类,可以用来在一个组件中启动App中的另一个组件或者是启动另一个App的组件,这里所说的组件指的是Activity、Service以及Broadcast。

Intent中文意思指”意图”,按照Android的设计理念,Android使用Intent来封装程序的”调用意图”,不管启动Activity、Service、BroadcastReceiver,Android都使用统一的Intent对象来封装这一”启动意图”。

那么如何实现Intent的这个功能了,我们一起来分析一下intent的原理。

Intent的原理

在手机系统启动的时候,PMS会扫描所有已安装的apk目录,解析apk包中的AndroidManifest.xml文件得到App的相关信息,并且将所有的这些信息存储起来构建一个完整的apk的信息树。在Intent去进行各组件间通信的时候,会调用PMS去查找apk信息表,找到相关的组件进行类似于启动的操作。

Intent原理分为查找和匹配,

1.查找

Android使用Intent组件,用于进程之间的通信和跳转。Intent具有隐式、显式两种。我们知道Android系统通过PackageManagerService来进行系统组件的维护。系统启动之后会有各种系统服务的注册,其中就含有PackageManagerService。在启动之后,PMS会扫描所有已安装的apk目录,解析apk包中的AndroidManifest.xml文件得到App的相关信息,而每个AndroidManifest.xml清单文件又包含了Activity,Service等组件的声明信息,当PMS扫描并且解析完信息后,就清晰地绘制出了整棵apk的信息树。 PackageManagerService的构造函数加载了系统已安装的各种apk,并加载了Framework资源内容和核心库。加载了资源和核心库之后才开始对扫描的指定目录下的apk文件进行分析,然后里面的PackageParser的parsePackage方法进行文件分析。

private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,
        String[] outError) throws XmlPullParserException, IOException {
    final String splitName;
    final String pkgName;

    try {
        Pair<String, String> packageSplit = parsePackageSplitNames(parser, parser);
        pkgName = packageSplit.first;
        splitName = packageSplit.second;

        if (!TextUtils.isEmpty(splitName)) {
            outError[0] = "Expected base APK, but found split " + splitName;
            mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
            return null;
        }
    } catch (PackageParserException e) {
        mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
        return null;
    }

    final Package pkg = new Package(pkgName);

    TypedArray sa = res.obtainAttributes(parser,
            com.android.internal.R.styleable.AndroidManifest);

    pkg.mVersionCode = sa.getInteger(
            com.android.internal.R.styleable.AndroidManifest_versionCode, 0);
    pkg.mVersionCodeMajor = sa.getInteger(
            com.android.internal.R.styleable.AndroidManifest_versionCodeMajor, 0);
    pkg.applicationInfo.setVersionCode(pkg.getLongVersionCode());
    pkg.baseRevisionCode = sa.getInteger(
            com.android.internal.R.styleable.AndroidManifest_revisionCode, 0);
    pkg.mVersionName = sa.getNonConfigurationString(
            com.android.internal.R.styleable.AndroidManifest_versionName, 0);
    if (pkg.mVersionName != null) {
        pkg.mVersionName = pkg.mVersionName.intern();
    }

    pkg.coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);

    final boolean isolatedSplits = sa.getBoolean(
            com.android.internal.R.styleable.AndroidManifest_isolatedSplits, false);
    if (isolatedSplits) {
        pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING;
    }

    pkg.mCompileSdkVersion = sa.getInteger(
            com.android.internal.R.styleable.AndroidManifest_compileSdkVersion, 0);
    pkg.applicationInfo.compileSdkVersion = pkg.mCompileSdkVersion;
    pkg.mCompileSdkVersionCodename = sa.getNonConfigurationString(
            com.android.internal.R.styleable.AndroidManifest_compileSdkVersionCodename, 0);
    if (pkg.mCompileSdkVersionCodename != null) {
        pkg.mCompileSdkVersionCodename = pkg.mCompileSdkVersionCodename.intern();
    }
    pkg.applicationInfo.compileSdkVersionCodename = pkg.mCompileSdkVersionCodename;

    sa.recycle();

    return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
}

可以明确地看到它解析了App的versionName,versionCode,baseVersionCode。那么App的各个组件是在哪里解析的呢? 那么我们继续分析方法parseBaseApplication,一切都十分清晰了。

private boolean parseBaseApplication(Package owner, Resources res,
        XmlResourceParser parser, int flags, String[] outError)
    throws XmlPullParserException, IOException {
    
    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
            && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
        String tagName = parser.getName();
        if (tagName.equals("activity")) {
            Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false, owner.baseHardwareAccelerated);//解析activity标签
            if (a == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }

            hasActivityOrder |= (a.order != 0);
            owner.activities.add(a);

        } else if (tagName.equals("receiver")) {//解析广播标签
            Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,
                    true, false);
            if (a == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }

            hasReceiverOrder |= (a.order != 0);
            owner.receivers.add(a);

        } else if (tagName.equals("service")) {//解析服务标签
            Service s = parseService(owner, res, parser, flags, outError, cachedArgs);
            if (s == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }

            hasServiceOrder |= (s.order != 0);
            owner.services.add(s);

        } else if (tagName.equals("provider")) {//解析provider标签
            Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs);
            if (p == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }

            owner.providers.add(p);

        } else if (tagName.equals("activity-alias")) {//解析activity-alias
            Activity a = parseActivityAlias(owner, res, parser, flags, outError, cachedArgs);
            if (a == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }

            hasActivityOrder |= (a.order != 0);
            owner.activities.add(a);

        } else if (parser.getName().equals("meta-data")) {//解析meta-data
            // note: application meta-data is stored off to the side, so it can
            // remain null in the primary copy (we like to avoid extra copies because
            // it can be large)
            if ((owner.mAppMetaData = parseMetaData(res, parser, owner.mAppMetaData,
                    outError)) == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }
        } 
}

通过上面的代码十分明显的发现这里会将App manifest文件中定义的各个组件解析出来,并且存入对应的集合当中。至此,整个信息树绘制完毕,已经存储好了这个应用的组件信息。

2.信息匹配

我们看一下执行intent的具体方法,一般情况下,我们都是使用startActivity方法。我们点进去最终会调用startActivityForResult方法。

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
        @Nullable Bundle options) {
    if (mParent == null) {
        options = transferSpringboardActivityOptions(options);
        Instrumentation.ActivityResult ar =
            mInstrumentation.execStartActivity(
                this, mMainThread.getApplicationThread(), mToken, this,
                intent, requestCode, options);
        if (ar != null) {
            mMainThread.sendActivityResult(
                mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                ar.getResultData());
        }

其中核心代码是execStartActivity方法。 这个方法经过一系列执行,最终调用Intent类中的 resolveActivityInfo 方法

public ActivityInfo resolveActivityInfo(@NonNull PackageManager pm,
        @PackageManager.ComponentInfoFlags int flags) {
    ActivityInfo ai = null;
    if (mComponent != null) {
        try {
            ai = pm.getActivityInfo(mComponent, flags);
        } catch (PackageManager.NameNotFoundException e) {
            // ignore
        }
    } else {
        ResolveInfo info = pm.resolveActivity(
            this, PackageManager.MATCH_DEFAULT_ONLY | flags);
        if (info != null) {
            ai = info.activityInfo;
        }
    }

    return ai;
}

这个方法会根据传进来的flags在PMS所存储的组件列表中挑选最合适的系统组件,进行回传。

整个流程就是

在安卓系统启动时,PackageManagerService(PMS)就会启动,PMS将分析所有已安装的应用信息,构建相对应的信息表,当用户需要通过Intent跳转到某个组件时,会根据Intent中包含的信息,然后从PMS中查找对应的组件列表,最后跳转到目标组件。

Intent的作用

Intent可以实现界面间的切换,过程中可以包含动作和动作数据,它是连接四大组件的纽带。具体的功能如下:

1.打开指定网页

button1.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                // 指定了Intent的action是 Intent.ACTION_VIEW,表示查看的意思,这是一个Android系统内置的动作;
                intent.setAction(Intent.ACTION_VIEW);//方法:android.content.Intent.Intent(String action)
                Uri data = Uri.parse("http://www.baidu.com");
                intent.setData(data);
                startActivity(intent);
            }
        });

2.打电话

方式一:打开拨打电话的界面:

Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);

方式二:直接拨打电话:

Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);

我们使用这项功能需要添加权限:

<uses-permission android:name="android.permission.CALL_PHONE"/>

3.发送信息

方式一:跳转信息发送的界面:action+type

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setType("vnd.android-dir/mms-sms");
intent.putExtra("sms_body", "具体短信内容"); //"sms_body"为固定内容
startActivity(intent); 

方式二:跳转发送短信的界面(同时指定电话号码):action+data

Intent intent = new Intent(Intent.ACTION_SENDTO);
intent.setData(Uri.parse("smsto:18780260012"));
intent.putExtra("sms_body", "具体短信内容"); //"sms_body"为固定内容
startActivity(intent);

4.播放指定路径音乐

action+data+type

Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse("file:///storage/sdcard0/平凡之路.mp3"); ////路径也可以写成:"/storage/sdcard0/平凡之路.mp3"
intent.setDataAndType(uri, "audio/mp3"); //方法:Intent android.content.Intent.setDataAndType(Uri data, String type)
startActivity(intent);

5.卸载程序

action+data(例如点击按钮,卸载某个应用程序,根据包名来识别)

注:无论是安装还是卸载,应用程序是根据包名package来识别的。

Intent intent = new Intent(Intent.ACTION_DELETE);
Uri data = Uri.parse("package:com.example.smyh006intent01");
intent.setData(data);
startActivity(intent);

6.安装程序:action+data+type

Intent intent = new Intent(Intent.ACTION_VIEW);
Uri data = Uri.fromFile(new File("/storage/sdcard0/AndroidTest/smyh006_Intent01.apk"));    //路径不能写成:"file:///storage/sdcard0/···"
intent.setDataAndType(data, "application/vnd.android.package-archive");  //Type的字符串为固定内容
startActivity(intent);
Intent传递的数据类型

Intent基本上可以涵盖大多数的数据类型,当然使用者也可以基于需要自定义数据类型,不过这些数据类型必须实现Parcelable接口或是Serializable接口。具体的数据类型大家可以在下面一一学习到:

  1. 传递简单的数据

1.1 存取一个数据

// 存数据
Intent i1 = new Intent(A.this,B.class);
i1.putExtra("key",value);
startActivity(i1);

// 取数据
Intent i2 = getIntent();
getStringExtra("key");

1.2 存取多个数据

// 存数据
Intent i1 = new Intent(A.this,B.class);
Bundle bundle = new Bundle();
bundle.putInt("num",1);
bundle.putString("detail","haha");
i1.putExtras(bundle);
startActivity(i1);

// 取数据
Intent i2 = getIntent();
Bundle bundle = i2.getExtras();
int i = bundle.getInt("num");
String str = bundle.getString("detail");
  1. 传递数组
bundle.putStringArray("StringArray",new String[]{"hehe","呵呵"});

读取数组:

String[] str = bundle.getStringArray("StringArray");
  1. 传递集合

3.1 List<基本数据类型或String>

intent.putStringArrayListExtra(name, value)
intent.putIntegerArrayListExtra(name, value)

读取集合

intent.getStringArrayListExtra(name)
intent.getIntegerArrayListExtra(name)

3.2 List< Object> 将list强制类型转换成Serializable类型,然后传入(可用Bundle做媒介) 写入集合:

putExtras(key, (Serializable)list)

读取集合:

(List<Object>) getIntent().getSerializable(key)

注意:Object类需要实现Serializable接口

3.3 Map<String, Object>或许更复杂的 解决方案是:外层包装个List

//传递复杂些的参数 
Map<String, Object> map1 = new HashMap<String, Object>();  
map1.put("key1", "value1");  
map1.put("key2", "value2");  
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();  
list.add(map1);  

Intent intent = new Intent();  
intent.setClass(MainActivity.this,ComplexActivity.class);  
Bundle bundle = new Bundle();  

//须定义一个list用于在budnle中传递需要传递的ArrayList<Object>,这个是必须要的  
ArrayList bundlelist = new ArrayList();   
bundlelist.add(list);   
bundle.putParcelableArrayList("list",bundlelist);  
intent.putExtras(bundle);                
startActivity(intent); 

4. 传递对象

有两种传递对象的方式:通过Serializable,Parcelable序列化或者将对象转换成Json字符串, 不推荐使用Android内置的Json解析器,可使用Gson第三方库!

4.1 将对象转换为Json字符串 写入数据:

Book book=new Book();
book.setTitle("Java编程大法");
Author author=new Author();
author.setId(1);
author.setName("Bruce Eckel");
book.setAuthor(author);
Intent intent=new Intent(this,SecondActivity.class);
intent.putExtra("book",new Gson().toJson(book));
startActivity(intent);

读取数据

String bookJson=getIntent().getStringExtra("book");
Book book=new Gson().fromJson(bookJson,Book.class);
Log.d(TAG,"book title->"+book.getTitle());
Log.d(TAG,"book author name->"+book.getAuthor().getName());

4.2 运用Serializable序列化对象 Serializable 是序列化的意思,表示将一个对象换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,或存储到本地。至于序列化的方法是很简单,只需要让一个类去实现Serializable 这个接口。 比如说有一个Person 类,其中包含两个字段name 和age,可以这样写:

public class Person implements Serializable{  
private String name;  
private int age;  
public String getName() {  
        return name;  
    }  
public void setName(String name) {  
        this.name = name;  
    }  
public int getAge() {  
        return age;  
        }  
public void setAge(int age) {  
        this.age = age;  
    }  
} 

其中get、set 方法用于赋值和读取字段,第一行是最重要。这里让Person 类去实现了Serializable 接口,这样一来所有的Person 都可序列化。 接下来在FirstActivity 中的写法非常容易:

Person person = new Person();  
person.setName("Tom");  
person.setAge(20);  
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);  
intent.putExtra("person_data", person);  
startActivity(intent); 

可以看到,这里我们创建了一个Person 的对象,然后就直接将它传入到putExtra()方法中了。由于Person 类实现了Serializable 接口。

接下来在SecondActivity 中获取这个对象也很容易,写法如下:

Person person = (Person) getIntent().getSerializableExtra("person_data"); 

4.3 使用Parcelable序列化对象 除了Serializable 以外,使用Parcelable 也可以实现同样的效果,不过和将对象进行序列化不同,Parcelable 方式的实现原理是分解完整的对象,而分解后的每一部分都是Intent 所支持的数据类型,这样也就实现传递对象。 下面我们来看一下Parcelable 的实现方式,修改Person 中的代码,如下所示:

public class Person implements Parcelable {  
    private String name;  
    private int age;  
      
    @Override  
    public int describeContents() {  
        // TODO Auto-generated method stub  
        return 0;  
    }  
  
    @Override  
    public void writeToParcel(Parcel dest, int flags) {  
        // TODO Auto-generated method stub  
        dest.writeString(name);  
        dest.writeInt(age);  
    }  
    public static final Parcelable.Creator<Person> CREATOR=new Parcelable.Creator<Person>() {  
  
        @Override  
        public Person createFromParcel(Parcel source) {  
            // TODO Auto-generated method stub  
            Person person=new Person();  
            person.name=source.readString();  
            person.age=source.readInt();  
            return person;  
        }  
  
        @Override  
        public Person[] newArray(int size) {  
            // TODO Auto-generated method stub  
            return new Person[size];  
        }  
    };    
}  

Parcelable 的实现方式是要复杂一些。可以看出,首先我们让Person 类去实现了Parcelable 接口,这样就必须重写两个方法,describeContents()和writeToParcel()。其中describeContents()方法直接返回0就可以了,而writeToParcel()方法中我们需要调用Parcel的writeXxx()方法将Person 类中的字段逐个写出。注意writeString()方法是字符串型数据调用,writeInt()方法是整型数据调用,以此类推。

除此之外,我们还必须在Person 类中声明一个名为CREATOR 的常量,这里创建了Parcelable.Creator 接口的一个实现类,并将泛型类型指定为Person。接着需要重写两个方法createFromParcel()和newArray(),在createFromParcel()方法中去获取刚才写出的name 和age字段,并创建一个Person 对象并进行返回,其中name 和age 都是调用Parcel 的readXxx()方法获取到,注意这里获取的顺序一定要和刚才写出的顺序完全一致。而newArray()方法中的实现就容易多了,只需要new 出一个Person 数组,并使用方法中传入的size 作为数组大小就可以了。

接下来在FirstActivity 中我们仍然可以使用相同的代码来传递Person 对象,只不过在SecondActivity 中获取对象的时候需要稍微做些改动,如下所示:

Person person = (Person) getIntent().getParcelableExtra("person_data"); 
  1. 传递Bitmap

bitmap默认实现Parcelable接口,直接传递即可

Bitmap bitmap = null;
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putParcelable("bitmap", bitmap);
intent.putExtra("bundle", bundle);
总结

Android都使用统一的Intent对象来封装这一”启动意图”,Intent在启动四大组件的过程中,会去查找PMS中存储的四大组件的信息来进行数据传递。需要注意的是Intent采用了隐士和显示两种调用方案。它所能传递的数据类型基本上包含了所有的类型,只是自定义的数据类型需要必须实现Parcelable接口或是Serializable接口。

今日分享到此结束,对你有帮助的话,点个赞再走呗,下期更精彩~

关注公众号:Android老皮 解锁  《Android十大板块文档》 ,让学习更贴近未来实战。已形成PDF版

内容如下

1.Android车载应用开发系统学习指南(附项目实战) 2.Android Framework学习指南,助力成为系统级开发高手 3.2023最新Android中高级面试题汇总+解析,告别零offer 4.企业级Android音视频开发学习路线+项目实战(附源码) 5.Android Jetpack从入门到精通,构建高质量UI界面 6.Flutter技术解析与实战,跨平台首要之选 7.Kotlin从入门到实战,全方面提升架构基础 8.高级Android插件化与组件化(含实战教程和源码) 9.Android 性能优化实战+360°全方面性能调优 10.Android零基础入门到精通,高手进阶之路

敲代码不易,关注一下吧。ღ( ´・ᴗ・` ) 🤔

转载自:https://juejin.cn/post/7252951745013727292
评论
请登录