在手机中点击一个apk文件,系统就会自动安装,这其中涉及到两个步骤:
1.系统识别apk文件,并启动负责安装的应用
2.安装应用
1.
首先看下负责安装apk 的activity的manifest中的声明:
<activity android:name=".PackageInstallerActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:excludeFromRecents="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.INSTALL_PACKAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:mimeType="application/vnd.android.package-archive" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.INSTALL_PACKAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="package" />
</intent-filter>
</activity>
再到fileexplorer中找到响应点击事件的地方:
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
rundalog = true;
if (mSelectionManager.inSelectionMode()) {
mSelectionManager.toggle(mFileAdapter.getItem(arg2).toString());
} else if (startMode == CATEGORY_SEL && category == CategoryFragment.FAVORITE) {
String path = mPathItems.get(arg2);
File f = new File(path);
listview.setItemChecked(arg2, false);
if (f.exists()) {
if (f.isDirectory())
mCallback.onDirectorySelectedOk(path, -1, 0, -1);
else
openFile(f);
} else
Toast.makeText(context, R.string.toast_file_not_exists,
Toast.LENGTH_LONG).show();
} else if (getActivity().getIntent().getAction() != null &&
getActivity().getIntent().getAction().equals(Intent.ACTION_GET_CONTENT)
&& (startMode == DEFAULT || startMode == CATEGORY_SEL)) {
File f = new File(mPathItems.get(arg2));
listview.setItemChecked(arg2, false);
if (f.isDirectory()) {
loadDir(mPathItems.get(arg2));
} else {
mCallback.onFileSelectedOk(MODE_INVALID, mPathItems.get(arg2));
}
} else {
listview.setItemChecked(arg2, false);
File f = new File(mPathItems.get(arg2));
if (f.exists()) {
if (f.isDirectory()) {
issearch = false;
loadDir(mPathItems.get(arg2));
} else {
openFile(f);
}
} else {
Toast.makeText(context, R.string.toast_file_not_exists,
Toast.LENGTH_LONG).show();
loadDir(mCurrentPath);
}
}
}
});
这里会先判断是文件还是文件夹,如果是文件就openFile(f),文件夹就 loadDir(mPathItems.get(arg2)),
这里我们看打开文件,主要代码:
private void openFile(File f) {
// We will first guess the MIME type of this file
final Uri fileUri = Uri.fromFile(f);
final Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
// Send file name to system application for sometimes it will be used.
intent.putExtra(Intent.EXTRA_TITLE, f.getName());
Uri contentUri = null;
String type = getMIMEType(f);
if (!"application/octet-stream".equals(type)) {
if (contentUri != null) {
intent.setDataAndType(contentUri, type);
} else {
intent.setDataAndType(fileUri, type);
}
// If no activity can handle the intent then
// give user a chooser dialog.
try {
startActivitySafely(intent);
} catch (ActivityNotFoundException e) {
showChooserDialog(fileUri, intent);
}
} else {
showChooserDialog(fileUri, intent);
}
}
这里一个非常重要的方法出现了,即文件的类型的鉴别,即String type = getMIMEType(f);
看源码:
private String getMIMEType(File f) {
String type = "";
String fileName = f.getName();
String ext = fileName.substring(fileName.lastIndexOf(".")
+ 1, fileName.length()).toLowerCase();
if (ext.equals("jpg") || ext.equals("jpeg") || ext.equals("gif")
|| ext.equals("png") || ext.equals("bmp") || ext.equals("wbmp")) {
type = "image/*";
} else if (ext.equals("mp3") || ext.equals("amr")
|| ext.equals("aac") || ext.equals("m4a") || ext.equals("mid")
|| ext.equals("xmf") || ext.equals("ogg") || ext.equals("wav")
|| ext.equals("qcp") || ext.equals("awb") || ext.equals("flac")
||ext.equals("3gpp")) {//wangcunxi add 20140102
type = "audio/*";
} else if (ext.equals("3gp") || ext.equals("avi") || ext.equals("mp4")
|| ext.equals("3g2") || ext.equals("divx")
|| ext.equals("mkv") || ext.equals("webm") || ext.equals("ts")
|| ext.equals("mov")) {
type = "video/*";
} else if (ext.equals("apk")) {
type = "application/vnd.android.package-archive";
} else if (ext.equals("vcf")) {
........ } else {
type = "application/octet-stream";
}
return type;
}
当文件类型为apk时, type="application/vnd.android.package-archive";
至此流程就基本清晰了,
点击文件后会new一个intent,set intent的action为:android.content.Intent.ACTION_VIEW
再判断文件的类型,如果是apk就把intent的type设置为:application/vnd.android.package-archive
用此intent去隐性启动 PackageInstallerActivity
2. 先看一个类图
启动PackageInstallerActivity后,用户会点选是安装到手机还是安装到SD卡,
点选后会启动InstallAppProgress:
Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallAppProgress.class);
newIntent.putExtra(InstallAppProgress.EXTRA_MANIFEST_DIGEST, mPkgDigest);
String installerPackageName = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
InstallAppProgress会调用ApplicationPackageManager的 installPackageWithVerificationAndEncryption方法
pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags,
installerPackageName, verificationParams, null);
其中pm=getPackageManager(),即
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));
}
return null;
}
ApplicationPackageManager则直接调用PackageManagerService 中的 installPackageWithVerification
mPM.installPackageWithVerification(packageURI, observer, flags, installerPackageName,
verificationURI, manifestDigest, encryptionParams);
mPM是构造函数中直接赋值为由外部传入的pm = ActivityThread.getPackageManager()即
public static IPackageManager getPackageManager() {
if (sPackageManager != null) {
//Slog.v("PackageManager", "returning cur default = " + sPackageManager);
return sPackageManager;
}
IBinder b = ServiceManager.getService("package");
//Slog.v("PackageManager", "default service binder = " + b);
sPackageManager = IPackageManager.Stub.asInterface(b);
//Slog.v("PackageManager", "default service = " + sPackageManager);
return sPackageManager;
}
在PackageManagerService中首先new一个InstallParams对象保存apk包的信息,
再给一个PackageHandler 类型的Handler发送一个INIT_COPY消息,连同InstallParams对象一起发送
final Message msg = mHandler.obtainMessage(INIT_COPY);
msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName,
verificationParams, encryptionParams, user);
mHandler.sendMessage(msg);
在handle收到INIT_COPY消息后,先链接MediaContainerService,MediaContainerService是在DefaultContainerService中实现其接口方法,
然后把InstallParams对象保存到一个arraylist列表中
if (!connectToService()) {
Slog.e(TAG, "Failed to bind to media container service");
params.serviceError();
return;
} else {
// Once we bind to the service, the first
// pending request will be processed.
mPendingInstalls.add(idx, params);
}
在连接成功后,会给handle发送一个MCS_BOUND消息
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceConnected");
IMediaContainerService imcs =
IMediaContainerService.Stub.asInterface(service);
mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs));
}
在handle收到MCS_BOUND后,会调用到InstallParams中的handleStartCopy()方法,
在handleStartCopy()中会先解析调用MediaContainerService中的getMinimalPackageInfo方法,
通过解析apk中manifest获取报名、安装地址、大小等信息:
public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags,
long threshold) {
PackageInfoLite ret = new PackageInfoLite();
if (packagePath == null) {
Slog.i(TAG, "Invalid package file " + packagePath);
ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
return ret;
}
DisplayMetrics metrics = new DisplayMetrics();
metrics.setToDefaults();
//解析manifest获取基本信息
PackageParser.PackageLite pkg = PackageParser.parsePackageLite(packagePath, 0);
if (pkg == null) {
Slog.w(TAG, "Failed to parse package");
final File apkFile = new File(packagePath);
if (!apkFile.exists()) {
ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
} else {
ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
}
return ret;
}
ret.packageName = pkg.packageName;
ret.versionCode = pkg.versionCode;
ret.installLocation = pkg.installLocation;
ret.verifiers = pkg.verifiers;
ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,
packagePath, flags, threshold);
return ret;
}
然后调用InstallArgs中的copyApk()方法,在创建InstallArgs时,会根据安装位置不一样,创建不同子类的对象:
private InstallArgs createInstallArgs(InstallParams params) {
if (installOnSd(params.flags) || params.isForwardLocked()) {
return new AsecInstallArgs(params);//安装在SD卡中
} else {
return new FileInstallArgs(params);//安装在手机中
}
}
安装到SD卡主要是调用MediaContainerService中的copyResourceToContainer()方法
安装到手机里调用MediaContainerService的copyResource()方法