在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零根底入门到通晓,高手进阶之路
敲代码不易,重视一下吧。ღ( ・ᴗ・` )