在手機中點擊一個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()方法