原型模式
原型模式是一个创建型模式,可以理解为对象的复制,原型模式多用于创建复杂的或者耗时的实例,因为复制一个存在的实例可以使程序更加高效。原型模式的定义是,用原型实例指定创建对象的种类,并通过复制这些原型创建新的对象。
1、原型模式的使用场景
类初始化需要消耗非常多的资源,这个资源包括数据、硬件等,通过原型复制避免这些消耗。
通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,这时可以使用原型模式。
一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值,可以考虑使用原型模式复制多个对象供调用者使用,即保护性拷贝。
需要注意的是,通过实现 Cloneable 接口的原型模式在调用 clone 函数构造实例时并不一定比通过 new 操作速度快,只有当通过 new 构造对象较为耗时,或者成本较高时,通过 clone 方法才能够得到效率的提升。因此,在使用 Cloneable 时需要考虑构建对象的成本以及做一些效率上的测试。当然,实现原型模式也不一定非要实现 Cloneable 接口,也有其他的实现方式。
二、原型模式的 UML 类图
- Client:客户端用户
- Prototype: 抽象类或者接口,声明具备 clone 能力
- ConcrePrototype: 具体的原型类
三、原型模式的简单实现
下面以简单的文档拷贝为例来演示一下简单的原型模式。我们在这个例子首先创建一个文档对象,即 WordDocument,这个文档中含有文字和图片,用户经过了长时间的内容编辑后,打算对文档做进一步的编辑,但是,这个编辑后的文档是否是否会被采用还不确定,因此,为了安全起见,用户需要将当前文档拷贝一份,然后再在文档副本上进行修改,这个原始文档就是我们上述所说的样板实例,就是“ 原型”
文档类:
import java.util.ArrayList;
import java.util.List;
/**
* 文档类型, 扮演的是ConcretePrototype角色,而cloneable是代表prototype角色
*
* @author mrsimple
*/
public class WordDocument implements Cloneable {
/**
* 文本
*/
private String mText;
/**
* 图片名列表
*/
private ArrayList mImages = new ArrayList();
public WordDocument() {
System.out.println("----------- WordDocument构造函数 -----------");
}
/**
* 克隆对象
*/
@Override
protected WordDocument clone() {
try {
WordDocument doc = (WordDocument) super.clone();
doc.mText = this.mText;
doc.mImages = this.mImages;
return doc;
} catch (Exception e) {
}
return null;
}
public String getText() {
return mText;
}
public void setText(String mText) {
this.mText = mText;
}
public List getImages() {
return mImages;
}
/**
* @param img
*/
public void addImage(String img) {
this.mImages.add(img);
}
/**
* 打印文档内容
*/
public void showDocument() {
System.out.println("----------- Word Content Start -----------");
System.out.println("Text : " + mText);
System.out.println("Images List: ");
for (String imgName : mImages) {
System.out.println("image name : " + imgName);
}
System.out.println("----------- Word Content End -----------");
}
}
通过 WordDocument 类模拟了 Word 文档中的基本元素,即文字和图片。WordDocument 在该原型模式中扮演的角色为 ConcretePrototype,而 Cloneable 的角色则为 Prototype。WordDocument 中的 clone 方法用以实现对象的克隆。这个方法并不是 Clone4able 接口中的方法,而是 Object 中的方法,Cloneable 也是一个标识接口,它表明这个类的对象是可拷贝,如果没有实现 Cloneable 接口却调用了 clone() 函数,将会抛出异常。
下面看 Client 端的使用:
public class Client {
public static void main(String[] args) {
WordDocument originDoc = new WordDocument();
originDoc.setText("这是一篇文档");
originDoc.addImage("图片1");
originDoc.addImage("图片2");
originDoc.addImage("图片3");
originDoc.showDocument();
WordDocument doc2 = originDoc.clone();
doc2.showDocument();
doc2.setText("这是修改过的Doc2文本");
doc2.showDocument();
originDoc.showDocument();
}
}
在客户端中,doc2 是 originDoc 的一份拷贝,他们的内容是一样的,而 doc2 中修改了文本内容并不会影响 originDoc 的文本内容,这就保证了 originDoc 的安全性。还需要注意的是,通过 clone 拷贝对象时并不会执行构造函数,因此需要一些特殊的初始化操作的类型。
四、浅拷贝和深拷贝
上述原型模式的实现只是一个浅拷贝,也叫影子拷贝。这份拷贝实际上并不是将原始文档的所有字段都重新构造了一份,而是副本文档的字段引用原始文档的字段。如下图所示:
我们知道 A 引用 B 就是说两个对象指向同一个地址,当修改 A 时 B 也会改变,B 修改 A 同样改变,将 main 函数的内容修改如下:
public static void main(String[] args) {
WordDocument originDoc = new WordDocument();
originDoc.setText("这是一篇文档");
originDoc.addImage("图片1");
originDoc.addImage("图片2");
originDoc.addImage("图片3");
originDoc.showDocument();
WordDocument doc2 = originDoc.clone();
doc2.showDocument();
doc2.setText("这是修改过的Doc2文本");
doc2.addImage("哈哈.jpg");
doc2.showDocument();
originDoc.showDocument();
再这个例子中,两个文档信息输出是一致的。我们在 doc2 添加了一张名为 "哈哈"的照片,但是同样显示在 originDoc 中了,出现这个原因是因为 WordDocument 的 clone 方法只是简单的进行浅拷贝,引用类型的新对象 doc2 的 mImages 只是单纯地指向了 this.mImages 引用,并没有重新构造一个 mImages 对象,然后将原始文档中的图片添加到新的 mImages 对象中,这样就导致 doc2 中的 mImages 与 原始文档中的是同一个对象,因为修改一个,另一个也会受到影响。如果要解决这个问题,就要采用深拷贝,也就是拷贝对象时,对于引用类型的字段也要采用拷贝的形式,而不是单纯的引用的形式。clone 修改如下:
/**
* 克隆对象
*/
@Override
protected WordDocument clone() {
try {
WordDocument doc = (WordDocument) super.clone();
doc.mText = this.mText;
// doc.mImages = this.mImages;
doc.mImages = (ArrayList) this.mImages.clone();
return doc;
} catch (Exception e) {
}
return null;
}
这样,doc2 中添加图片将不会再影响 originDoc。
原型模式,是一个简单的模式,它的核心问题就是对原始对象进行拷贝,这个模式使用过程中需要注意的就是深拷贝和浅拷贝的问题。这里建议尽量使用深拷贝避免对原始对象的影响。
五、Android 源码中的原型模式实现
在上述文档拷贝示例,mImages 对象调用了 clone() 方法,这就是原型模式,在上述代码中,mImages 的实际类型为 ArrayList,其源码如下:
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
{
//序列版本号
private static final long serialVersionUID = 8683452581122892189L;
//默认初始化容量
private static final int DEFAULT_CAPACITY = 10;
//空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//Object[]类型的数组,它保存了添加到ArrayList中的元素。
//实际上它是一个动态数组
transient Object[] elementData; // non-private to simplify nested class access
...
//克隆函数
public Object clone() {
try {
ArrayList> v = (ArrayList>) super.clone();
//将当前ArrayList的全部元素拷贝到V中
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
}
主要看上述代码的 clone 函数,clone 对象首先克隆自身对象,然后再克隆存储元素的数组。这就是 Java 中原型模式的实现实例。在 Android 中,Intent 的源码中,可以体现原型模式。代码如下:
Uri uri = Uri.parse("smsto:0800000123");
Intent shareIntent = new Intent(Intent.ACTION_SENDTO, uri);
shareIntent.putExtra("sms_body", "The SMS text");
Intent intent = (Intent)shareIntent.clone() ;
startActivity(intent);
上面的代码主要是拷贝了一份 Intent,然后通过副本 Intent 进行条状。起其作用和原始 Intent 作用一样。下面看一下 Intent 中的 clone() 实现:
/**
* Copy constructor.
*/
public Intent(Intent o) {
this.mAction = o.mAction;
this.mData = o.mData;
this.mType = o.mType;
this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
this.mFlags = o.mFlags;
if (o.mCategories != null) {
this.mCategories = new HashSet(o.mCategories);
}
if (o.mExtras != null) {
this.mExtras = new Bundle(o.mExtras);
}
if (o.mSourceBounds != null) {
this.mSourceBounds = new Rect(o.mSourceBounds);
}
if (o.mSelector != null) {
this.mSelector = new Intent(o.mSelector);
}
if (o.mClipData != null) {
this.mClipData = new ClipData(o.mClipData);
}
}
@Override
public Object clone() {
return new Intent(this);
}
可以看到 clone 方法没有调用 super.clone() 来实现对象拷贝,而是调用 new Intent(this) 。这个原因在上文提到过,使用 clone 和 new 要根据构造对象的成本来决定,如果对象的构造成本比较高或者比较麻烦,使用 clone() 函数效率较高,否则可以使用 new 的形式。
这是原型模式在 Android 系统的体现。
六、Intent 的查找与匹配
1、App 信息表的构建
在开始之前,首先我们要知道,系统在启动之后,会注册各种系统服务,如 WindowManagerService、ActivityManagerService 等,其中一个就是 PackageManagerService,PMS 启动之后会扫描系统中已经安装的 apk 目录,其中系统 APP 的安装目录为 /system/app,第三方应用安装目录为 /data/app,PMS 会解析 apk 包下的 AndroidManifest.xml 文件得到 App 的相关信息,因为 AndroidManifest.xml 包含了 Activity、Service 等组件的注册信息,所以当 PMS 扫描并解析完成之后,就构建好了整个 apk 的信息树,大致的流程如下所示:
下面我们来分析一下 PMS 解析已安装 apk 信息的过程。
首先看一下 PMS 构造函数:
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
...
synchronized (mInstallLock) {
// writer
synchronized (mPackages) {
mHandlerThread.start();
mHandler = new PackageHandler(mHandlerThread.getLooper());
Watchdog.getInstance().addThread(mHandler, mHandlerThread.getName(),
WATCHDOG_TIMEOUT);
// 获取 /data 目录
File dataDir = Environment.getDataDirectory();
mAppDataDir = new File(dataDir, "data");
// 获取 /data/app 目录
mAppInstallDir = new File(dataDir, "app");
mAppLibInstallDir = new File(dataDir, "app-lib");
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mUserAppDataDir = new File(dataDir, "user");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
sUserManager = new UserManagerService(context, this,
mInstallLock, mPackages);
readPermissions();
...
//加载 FrameWork 资源
File frameworkDir = new File(Environment.getRootDirectory(), "framework");
// Gross hack for now: we know this file doesn't contain any
// code, so don't dexopt it to avoid the resulting log spew.
alreadyDexOpted.add(frameworkDir.getPath() + "/framework-res.apk");
// Gross hack for now: we know this file is only part of
// the boot class path for art, so don't dexopt it to
// avoid the resulting log spew.
// 加载核心库
alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar");
String[] frameworkFiles = frameworkDir.list();
...
// Find base frameworks (resource packages without code).
mFrameworkInstallObserver = new AppDirObserver(
frameworkDir.getPath(), OBSERVER_EVENTS, true, false);
mFrameworkInstallObserver.startWatching();
// 扫描 app 安装路径
scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanMode | SCAN_NO_DEX, 0);
...
// 非核心应用
if (!mOnlyCore) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
SystemClock.uptimeMillis());
mAppInstallObserver = new AppDirObserver(
mAppInstallDir.getPath(), OBSERVER_EVENTS, false, false);
mAppInstallObserver.startWatching();
// 扫描第三方 app 安装路径
scanDirLI(mAppInstallDir, 0, scanMode, 0);
mDrmAppInstallObserver = new AppDirObserver(
mDrmAppPrivateInstallDir.getPath(), OBSERVER_EVENTS, false, false);
mDrmAppInstallObserver.startWatching();
scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
scanMode, 0);
/**
* Remove disable package settings for any updated system
* apps that were removed via an OTA. If they're not a
* previously-updated app, remove them completely.
* Otherwise, just revoke their system-level permissions.
*/
for (String deletedAppName : possiblyDeletedUpdatedSystemApps) {
PackageParser.Package deletedPkg = mPackages.get(deletedAppName);
mSettings.removeDisabledSystemPackageLPw(deletedAppName);
String msg;
if (deletedPkg == null) {
msg = "Updated system package " + deletedAppName
+ " no longer exists; wiping its data";
removeDataDirsLI(deletedAppName);
} else {
msg = "Updated system app + " + deletedAppName
+ " no longer present; removing system privileges for "
+ deletedAppName;
deletedPkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM;
PackageSetting deletedPs = mSettings.mPackages.get(deletedAppName);
deletedPs.pkgFlags &= ~ApplicationInfo.FLAG_SYSTEM;
}
reportSettingsProblem(Log.WARN, msg);
}
} else {
mAppInstallObserver = null;
mDrmAppInstallObserver = null;
}
// Now that we know all of the shared libraries, update all clients to have
// the correct library paths.
updateAllSharedLibrariesLPw();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
SystemClock.uptimeMillis());
final boolean regrantPermissions = mSettings.mInternalSdkPlatform
!= mSdkVersion;
if (regrantPermissions) Slog.i(TAG, "Platform changed from "
+ mSettings.mInternalSdkPlatform + " to " + mSdkVersion
+ "; regranting permissions for internal storage");
mSettings.mInternalSdkPlatform = mSdkVersion;
updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
| (regrantPermissions
? (UPDATE_PERMISSIONS_REPLACE_PKG|UPDATE_PERMISSIONS_REPLACE_ALL)
: 0));
// If this is the first boot, and it is a normal boot, then
// we need to initialize the default preferred apps.
if (!mRestoredSettings && !onlyCore) {
mSettings.readDefaultPreferredAppsLPw(this, 0);
}
// can downgrade to reader
mSettings.writeLPr();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
SystemClock.uptimeMillis());
Runtime.getRuntime().gc();
mRequiredVerifierPackage = getRequiredVerifierLPr();
} // synchronized (mPackages)
} // synchronized (mInstallLock)
}
分析上面的主要代码可以得知, PMS 不仅需要加载系统已安装的 apk,还有加载 Framework 资源与核心库,在这之后才开始对指定目录下的 apk 进行解析,扫描函数为 scanDirLI,这个函数实现如下:
private void scanDirLI(File dir, int flags, int scanMode, long currentTime) {
// 获取目录下所有文件
String[] files = dir.list();
if (files == null) {
Log.d(TAG, "No files in app dir " + dir);
return;
}
if (DEBUG_PACKAGE_SCANNING) {
Log.d(TAG, "Scanning app dir " + dir + " scanMode=" + scanMode
+ " flags=0x" + Integer.toHexString(flags));
}
int i;
// 解析目录下所以 apk 文件
for (i=0; i
所以,scanLI 主要就是扫描所有的 apk 文件,然后通过 scanPackageLI 函数进行解析,因此 scanPackageLI 函数是最重要的:
/*
* Scan a package and return the newly parsed package.
* Returns null in case of errors and the error code is stored in mLastScanError
*/
private PackageParser.Package scanPackageLI(File scanFile,
int parseFlags, int scanMode, long currentTime, UserHandle user) {
// 创建一个包解析器
PackageParser pp = new PackageParser(scanPath);
pp.setSeparateProcesses(mSeparateProcesses);
pp.setOnlyCoreApps(mOnlyCore);
// 解析 apk
final PackageParser.Package pkg = pp.parsePackage(scanFile,
scanPath, mMetrics, parseFlags);
if (pkg == null) {
mLastScanError = pp.getParseError();
return null;
}
PackageSetting ps = null;
PackageSetting updatedPkg;
...
codePath = pkg.mScanPath;
// Set application objects path explicitly.
setApplicationInfoPaths(pkg, codePath, resPath);
// Note that we invoke the following method only if we are about to unpack an application
// 解析 apk 中的 Activity、service 等组件
PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanMode
| SCAN_UPDATE_SIGNATURE, currentTime, user);
if (shouldHideSystemApp) {
synchronized (mPackages) {
/*
* We have to grant systems permissions before we hide, because
* grantPermissions will assume the package update is trying to
* expand its permissions.
*/
grantPermissionsLPw(pkg, true);
mSettings.disableSystemPackageLPw(pkg.packageName);
}
}
return scannedPkg;
}
在 scanPackageLI 中首先构造了一个 PackageParser,也就是一个 apk 包解析器,如注释处调用 PackageParser 对象的 parsePackage 函数,具体代码如下:
public Package parsePackage(File sourceFile, String destCodePath,
DisplayMetrics metrics, int flags) {
// 是否文件夹类型
if(packageFile.isDirectory()){
return parseClusterpackage(packageFile,flags);
}else{
// 解析单个 apk
return parseMOnolithicPackage(packageFile,flags);
}
}
在 parsePackage 函数中会根据 packageFile 的类型选择不同的解析方法,例如 parseClusterPackage 是一个文件夹,则会解析文件夹下的所有的 apk,如果是一个文件,那么则调用 parseMOnolithicPackage 函数,具体代码如下:
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
//如果是核心应用则以更轻量级的方式进行解析后,判断是否是核心应用,非核心应用不执行解析过程
if (mOnlyCoreApps) {
final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
if (!lite.coreApp) {
throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"Not a coreApp: " + apkFile);
}
}
// 构造应用程序资源
final AssetManager assets = new AssetManager();
try {
//调用parseBaseAPK()继续解析操作
final Package pkg = parseBaseApk(apkFile, assets, flags);
pkg.codePath = apkFile.getAbsolutePath();
return pkg;
} finally {
IoUtils.closeQuietly(assets);
}
}
在 PackageParser 的 parseMonolithicPackage 函数中我们看到,parseBaseApk(File apkFile,AssetManager assets,int flags) 函数,这三个参数的 parseBaseApk 最终会调用 4 个参数的 parseBaseApk(Resources res,XmlResourceParser parser,int flags,String[] outError) 函数来解析得到的 Package 对象,在 4 个参数的 parseBaseApk 函数中主要就是解析 AndroidManifest.xml 中的各个节点。比如 application、use-permission 等,通过解析 AndroidManifest.xml 中的各个节点就能够得到应用的详细信息。parseBaseApk(res,parser,flags,errorText) 函数的实现:
private Package parsePackage(
Resources res, XmlResourceParser parser, int flags, String[] outError)
throws XmlPullParserException, IOException {
// 1、解析到 AndroidManifest 的包名
String pkgName = parsePackageName(parser, attrs, flags, outError);
...
// 构建 Package 对象
final Package pkg = new Package(pkgName);
boolean foundApp = false;
...
// 解析 AndroidManifest.xml 中的元素
int outerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
String tagName = parser.getName();
if (tagName.equals("application")) {
foundApp = true;
//解析Acclication 标签,Activity、Service 等都在这里
if (!parseApplication(pkg, res, parser, attrs, flags, outError)) {
return null;
}
} else if (tagName.equals("keys")) {
if (!parseKeys(pkg, res, parser, attrs, outError)) {
return null;
}
} else if (tagName.equals("permission-group")) {
if (parsePermissionGroup(pkg, flags, res, parser, attrs, outError) == null) {
return null;
}
} else if (tagName.equals("permission")) {
if (parsePermission(pkg, res, parser, attrs, outError) == null) {
return null;
}
} else if (tagName.equals("permission-tree")) {
if (parsePermissionTree(pkg, res, parser, attrs, outError) == null) {
return null;
}
// 用户权限
} else if (tagName.equals("uses-permission")) {
if (!parseUsesPermission(pkg, res, parser, attrs, outError)) {
return null;
}
} else if (tagName.equals("uses-configuration")) {
ConfigurationInfo cPref = new ConfigurationInfo();
sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestUsesConfiguration);
cPref.reqTouchScreen = sa.getInt(
com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqTouchScreen,
Configuration.TOUCHSCREEN_UNDEFINED);
cPref.reqKeyboardType = sa.getInt(
com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqKeyboardType,
Configuration.KEYBOARD_UNDEFINED);
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqHardKeyboard,
false)) {
cPref.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD;
}
cPref.reqNavigation = sa.getInt(
com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqNavigation,
Configuration.NAVIGATION_UNDEFINED);
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqFiveWayNav,
false)) {
cPref.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV;
}
sa.recycle();
pkg.configPreferences.add(cPref);
XmlUtils.skipCurrentTag(parser);
}
...
XmlUtils.skipCurrentTag(parser);
}
}
return pkg;
}
这个 parsePackage 函数真正解析 AndroidManifest.xml 的函数,这里有两个标签 Application 和 use-permission。Application 中包含了 Activity、Service 等标签,也就是 Intent 所需要的标签。
private boolean parseApplication(Package owner, Resources res,
XmlPullParser parser, AttributeSet attrs, int flags, String[] outError)
throws XmlPullParserException, IOException {
// 应用信息
final ApplicationInfo ai = owner.applicationInfo;
// 包名
final String pkgName = owner.applicationInfo.packageName;
// 获取 Application 标签的 TypedArray
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestApplication);
// 应用名
String name = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifestApplication_name, 0);
...
// 应用图标
ai.icon = sa.getResourceId(
com.android.internal.R.styleable.AndroidManifestApplication_icon, 0);
ai.logo = sa.getResourceId(
com.android.internal.R.styleable.AndroidManifestApplication_logo, 0);
ai.theme = sa.getResourceId(
com.android.internal.R.styleable.AndroidManifestApplication_theme, 0);
ai.descriptionRes = sa.getResourceId(
com.android.internal.R.styleable.AndroidManifestApplication_description, 0);
...
// 获取 taskAffinity 属性
ai.taskAffinity = buildTaskAffinityName(ai.packageName, ai.packageName,
str, outError);
ai.uiOptions = sa.getInt(
com.android.internal.R.styleable.AndroidManifestApplication_uiOptions, 0);
sa.recycle();
if (outError[0] != null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
final int innerDepth = parser.getDepth();
int type;
// 依次解析 Application 元素下的所有子元素
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
// 获取标签名
String tagName = parser.getName();
// 解析 Activity
if (tagName.equals("activity")) {
Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false,
hardwareAccelerated);
if (a == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.activities.add(a);
} else if (tagName.equals("receiver")) {
Activity a = parseActivity(owner, res, parser, attrs, flags, outError, true, false);
if (a == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.receivers.add(a);
} else if (tagName.equals("service")) {
// 解析 Service
Service s = parseService(owner, res, parser, attrs, flags, outError);
if (s == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.services.add(s);
} else if (tagName.equals("provider")) {
// 解析 Provider 等标签
Provider p = parseProvider(owner, res, parser, attrs, flags, outError);
if (p == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.providers.add(p);
}
...
}
return true;
}
这里 Activity、Service、Provider 等标签就是我们要解析需要的,这里的解析过程就是普通的 xml 解析,不同的标签有不同的解析方法,例如 解析 Acitvity 就会调用 parseAcitvity 函数,返回一个 Activity 实例,并且将这个实例添加到 Package 对象的 activities 列表中。接下来回到上述的 PackageManagerService 类中的 scanPackageLI 的最后一步,也就是调用 scanPackageLI(PackageParser.Package pkg,int parseFlags,int scanMode,long currentTime,UserHandle user) 函数,函数实现如下:
private PackageParser.Package scanPackageLI(File scanFile,
int parseFlags, int scanMode, long currentTime, UserHandle user) {
boolean success = false;
try{
// 调用 scanPackageDirtyLI 进行扫描 apk 包
final PackageParser.Package res = scanPackageDirtyLI(pkg,parseFlags,scanFlags,currentTime,user);
success = true;
return res;
}finally{
// 代码省略
}
return scannedPkg;
}
这里主要调用了 scanPackageDirtyLI 函数,这个函数将间隙得到的 Acitiviy、Service 等添加到对应的 mActivitys、mServices 等列表中,这个函数很复杂,代码非常长,主要的功能如下:
1.建立ResolverActivity的内存对象
就是当发出一个Intent,如果有多个Activity响应该Intent的Activity,会弹出一个对话框让用户选择,这个对话框就是ResolverActivity。
2.处理带有original-package标签的应用
3.校验签名
4.检查ContentProvider名称
5.确定应用将来的进程名称
6.创建应用的数据目录
7.安装动态库
8.重新优化dex
9.提取应用中的组件信息
把应用的Activity、Service、Provider、Receiver信息、Permission、PermissionGroup信息都提取出来加入到PackageManagerService的成员变量中。
final ActivityIntentResolver mActivities;
final ActivityIntentResolver mReceivers;
final ActivityIntentResolver mServices;
final ActivityIntentResolver mProviders;
系统中所有的组件信息都会加入到这4个变量中。Android系统运行时对Intent的解析就是通过这些变量查找的。
经过这个过程,整个已经安装的 apk 信息树已经建立了,每个 apk 的应用名、包名、图标、Activity、Service 等信息都存储在系统中,当用户使用 Intent 跳转到某个 Activity 或者启动某个 Service 时,系统则会在这个信息表中查找,符合要求的组件则会被启动起来。这样就通过 Intent 将系统的各个组件联系在了一起,使得 Android 系统成为一个组件可复用、灵活的系统。
2、精确匹配
上面分析了 apk 信息表的构建过程,下面分析一下Intent 的的查找与匹配过程。
在我们开发过程中,如果要启动一个 Activity,代码如下:
Intent intent = new Intent(mContext,SecondActivity.class);
this.startActivity(intent);
这里指定了具体的组件,也就是 SecondActivity,此时系统在查找时,会使用精确匹配,我们称为显示 Intent。
除了这种方式,还有一种方式,就是不指定具体的组件,而是指定条件去查询,例如:
Intent intent = new Intent(Intent.ACTION_SENDTO);
this.startActivity(intent);
这种方式为隐式 Intent。
接下来,我们分析一下这些 Intent 的查找和匹配。
startActivity 这个函数经过几个函数转发,最终会调用 startActivityForResult,这个函数如下:
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
options = transferSpringboardActivityOptions(options);
// 1 启动 Activity
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());
}
if (requestCode >= 0) {
mStartedActivity = true;
}
cancelInputsAndStartExitTransition(options);
// TODO Consider clearing/flushing other event sources and events for child windows.
} else {
if (options != null) {
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {
// Note we want to go through this method for compatibility with
// existing applications that may have overridden it.
//2 发送启动请求
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}
在上面的代码中,启动 Activiy 调用了 Instrumentation 中的 execStartActivity 函数,具体代码如下:
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
Uri referrer = target != null ? target.onProvideReferrer() : null;
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i= 0 ? am.getResult() : null;
}
break;
}
}
}
}
try {
// 将 Intent 中的数据迁移到粘贴板中。
intent.migrateExtraStreamToClipData();
// 准备离开当前进程
intent.prepareToLeaveProcess(who);
// 调用 ActivityManagerService 的 startActivity 方法
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
// 检测结构并返回
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
execStartActivity 这方法主要就是调用了 ActivityManagerService 的 startActivity 方法,这个方法里就是调用了 ActivityStackSupervisor 的 startActivityMayWait 方法,该方法最后调用 PMS 的 resoveIntent 方法。在 PMS 中, resolveIntent 就是调用自身的 queryIntentActivitys 方法,这个方法会返回一个 ActivityInfo 对象列表,也就是符合 Intent 的 ActivityInfo 列表,而每个 ActivityInfo 对象就是一个 Activity 的档案对象,记录了一个 Activity 的相关信息。queryIntentActivitys 源码如下:
@Override
public List queryIntentActivities(Intent intent,
String resolvedType, int flags, int userId) {
if (!sUserManager.exists(userId)) return Collections.emptyList();
enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "query intent activities");
// 获取 Intent 的 Component 对象
ComponentName comp = intent.getComponent();
//精确跳转时,这个对象不能为空
if (comp == null) {
if (intent.getSelector() != null) {
intent = intent.getSelector();
comp = intent.getComponent();
}
}
if (comp != null) {
final List list = new ArrayList(1);
// 通过 Component 直接得到 ActivityInfo 对象
final ActivityInfo ai = getActivityInfo(comp, flags, userId);
if (ai != null) {
final ResolveInfo ri = new ResolveInfo();
ri.activityInfo = ai;
list.add(ri);
}
return list;
}
// 当 Component 为空时,为隐式 Intent
// reader
synchronized (mPackages) {
final String pkgName = intent.getPackage();
if (pkgName == null) {
return mActivities.queryIntent(intent, resolvedType, flags, userId);
}
// 通过包名,获取 Package 对象
final PackageParser.Package pkg = mPackages.get(pkgName);
//通过 package 获取 ActivityInfo 对象
if (pkg != null) {
return mActivities.queryIntentForPackage(intent, resolvedType, flags,
pkg.activities, userId);
}
return new ArrayList();
}
}
查找的过程大致如下:
如果 Intent 指明了 Component,那么直接通过 Componet 就可以找到 ActivityInfo 列表,这个列表的数量只有一个,这个 ActivityInfo 就是指定的那个组件。如果没有指定具体的组件,那么 Component 为空,此时先看 Intent 是否指定了要跳转的目标组件的包名,如果有,则通过包名获取对应的 ActivityInfo。如果这些都没有,那么只能通过 ActivityIntentResolver 等类的 queryIntentForPackage 进行模糊匹配,如根据 Action、Catagory 等。
最后,总结一下,在系统启动时,PackageManagerService 会启动,此时 PMS 将解析所有已安装的应用的信息,构建一个信息表,当用户通过 Intent 来跳转到某个组件时,会根据 Intent 中包含的信息到 PMS 中查找对用的组件列表,最后调到目标组件。
七、原型模式实战
在我们开发中,会有这样的一个要求,就是有的对象中的内容只允许客户端程序读取,而不允许修改。比如,我们常见的登录 app 中,登录之后会有一个 LoginSession 保存用户的登录信息,这个Session 会被其他模块当作登录校验、用户个人信息的显示等。但是这个数据肯定是不能修改的,而只能在其他模块调用。因此需要开放已经登录的用户信息访问接口,初步实现如下:
- User.java 用户实体类
public class User {
public int age;
public String name;
public String phoneNum;
public Address address;
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
", phoneNum='" + phoneNum + '\'' +
", address=" + address +
'}';
}
}
- Address.java 用户地址类
public class Address {
public String city;
public String district;
public String street;
public Address(String city, String district, String street) {
this.city = city;
this.district = district;
this.street = street;
}
@Override
public String toString() {
return "Address{" +
"city='" + city + '\'' +
", district='" + district + '\'' +
", street='" + street + '\'' +
'}';
}
}
- Login.java 登录接口
public interface Login {
void login();
}
- LoginImpl.java 登录实现
public class LoginImpl implements Login{
public void login() {
// 登录到服务器,获取到用户信息
User loginedUser = new User();
// 将服务器返回的完整信息设置给loginedUser对象
loginedUser.age = 22;
loginedUser.name = "xiaoguan";
loginedUser.address = new Address("北京市","海淀区","北四环西路");
// 登录完后将用户信息设置到Session中LoginSession.getLoginSession()中
LoginSession.getLoginSession().setLoginedUser(loginedUser);
}
}
- LoginSession.java 登陆 Session
public class LoginSession {
static LoginSession loginSession = null;
private User loginedUser;// 已登录用户
public LoginSession() {
}
public static LoginSession getLoginSession() {
if (loginSession == null) {
loginSession = new LoginSession();
}
return loginSession;
}
public User getLoginedUser() {
// return loginedUser.clone();// 返回已登录用户的一个拷贝
return loginedUser;// 返回原始已登录用户
}
// 只通过私有方法修改用户信息
void setLoginedUser(User loginedUser) {
this.loginedUser = loginedUser;
}
}
上面的代码主要的意思就是,用户登录之后,会通过 LogenSession 的 setLoginedUser 函数将登录用户的信息设置到 Session 中,这个 setLoginedUser 是包级私有的,因此外部模块无法调用,也就是外部客户端无法修改已经登录的用户信息。
假设客户端有如下代码:
public class Client {
public static void main(String[] args) {
// 已登录用户
LoginImpl loginImpl = new LoginImpl();
loginImpl.login();
User tempUser = LoginSession.getLoginSession().getLoginedUser();
User user = LoginSession.getLoginSession().getLoginedUser();// 获得已登录的User对象
user.address = new Address("北京市","海淀区","北四环东路");
// 只能通过私有方法setLoginedUser更新用户信息
LoginSession.getLoginSession().setLoginedUser(user);
System.out.println("tempUser:" + tempUser);
System.out.println("已登录用户:" + LoginSession.getLoginSession().getLoginedUser());
}
}
上面代码的运行结果将是,user 与 tempUser 的信息都被修改了,都是最新的数据,出现这个问题的原因是 getLoginUser() 返回的只是引用,并不是拷贝,那么这个问题可以通过原型模式进行保护性拷贝解决,也就是说在 LoginSession 的 getLoginUser() 函数中返回一个已经登陆用户的拷贝,这样使得在任何地方调用getLoginedUser函数获取到的用户对象都是一个拷贝对象,即使客户端不小心修改了这个拷贝对象,也不会影响最初的已登录用户对象,确保了它的安全。
因此 User 对象修改如下:
public class User implements Cloneable{
public int age;
public String name;
public String phoneNum;
public Address address;
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
", phoneNum='" + phoneNum + '\'' +
", address=" + address +
'}';
}
/**
* 拷贝
*/
public User clone() {
User user = null;
try {
user = (User) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return user;
}
}
LoginSession 中的 getLoginedUser 修改如下:
public class LoginSession {
public User getLoginedUser() {
return loginedUser.clone();// 返回已登录用户的一个拷贝
// return loginedUser;// 返回原始已登录用户
}
}
这样就可以保证登录 Session 的安全。
八、小结
原型模式本质上就是对象拷贝,这里需要区分的就是浅拷贝与深拷贝。使用原型模式可以解决构建复杂对象的资源消耗问题,能够在某些场景下提升创建对象的效率。还一个重要的作用就是保护性拷贝,保证数据安全。
优点
原型模式是内存中二进制流的拷贝,要比一个 new 一个对象性能好很多,特别是在一个循环体内产生大量对象时。缺点
因为是内存中拷贝,因此构造函数不会执行,需要注意这个问题。
参考 《Android 源码设计模式解析与实战》——原型模式