每日一题:Intent的原理,作用,可以传递哪些类型的参数?
在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 存取一个数据
// 存数据
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");
- 传递数组
bundle.putStringArray("StringArray",new String[]{"hehe","呵呵"});
读取数组:
String[] str = bundle.getStringArray("StringArray");
- 传递集合
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");
- 传递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