在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零根底入门到通晓,高手进阶之路

敲代码不易,重视一下吧。ღ( ・ᴗ・` )