編輯:關於Android編程
上一篇文章中給大家分析了一下android系統啟動之後調用PackageManagerService服務並解析系統特定目錄,解析apk文件並安裝的過程,這個安裝過期實際上是沒有圖形界面的,底層調用的是我們平時比較熟悉的adb命令,那麼我們平時安裝apk文件的時候大部分是都過圖形界面安裝的,那麼這種方式安裝apk具體的流程是怎樣的呢?
下面我們就來具體看一下apk的具體安裝過程,相信大家都知道如果我們想在代碼裡執行apk的安裝,那麼一般都是這樣:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.parse("file://" + path),"application/vnd.android.package-archive");
context.startActivity(intent);
這樣,我們就會打開安裝apk文件的程序並執行安裝邏輯了,那麼這段代碼具體是打開那個activity呢?好吧,從這個問題開始,我們來解析apk的安裝流程…
這裡跟大姐簡單介紹一下android的源碼,平時我們使用的android.jar裡面的java源碼只是android系統源碼的一部分,還有好多源碼並沒有打入到android.jar中,這裡為大家推薦一個android源碼的地址:https://github.com/android
裡面根據android系統的不同模塊包含了許多android模塊的源碼。
這裡我們找到platform_packages_apps_packageinstaller庫,這裡面就是android系統安裝程序的源碼了。
這裡我們找到其androidManifest.xml,然後我們來看一下其具體的定義:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">
好吧,這裡我們大概看一下Activity的定義,這裡我們重點看一下PackageInstallerActivity的定義:
恩?這裡不就是我們剛剛定義的啟動安裝Apk activity的intent filter?好吧,所以說一開始我們調用的startActivity其實啟動的就是PackageInstallerActivity,那麼下面我們就看一下PackageInstellerActivity的具體實現:
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
mPm = getPackageManager();
mInstaller = mPm.getPackageInstaller();
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
final Intent intent = getIntent();
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
finish();
return;
}
mSessionId = sessionId;
mPackageURI = Uri.fromFile(new File(info.resolvedBaseCodePath));
mOriginatingURI = null;
mReferrerURI = null;
} else {
mSessionId = -1;
mPackageURI = intent.getData();
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
}
final boolean unknownSourcesAllowedByAdmin = isUnknownSourcesAllowedByAdmin();
final boolean unknownSourcesAllowedByUser = isUnknownSourcesEnabled();
boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(intent);
mInstallFlowAnalytics = new InstallFlowAnalytics();
mInstallFlowAnalytics.setContext(this);
mInstallFlowAnalytics.setStartTimestampMillis(SystemClock.elapsedRealtime());
mInstallFlowAnalytics.setInstallsFromUnknownSourcesPermitted(unknownSourcesAllowedByAdmin
&& unknownSourcesAllowedByUser);
mInstallFlowAnalytics.setInstallRequestFromUnknownSource(requestFromUnknownSource);
mInstallFlowAnalytics.setVerifyAppsEnabled(isVerifyAppsEnabled());
mInstallFlowAnalytics.setAppVerifierInstalled(isAppVerifierInstalled());
mInstallFlowAnalytics.setPackageUri(mPackageURI.toString());
if (DeviceUtils.isWear(this)) {
showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_NOT_ALLOWED_ON_WEAR);
return;
}
final String scheme = mPackageURI.getScheme();
if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
Log.w(TAG, "Unsupported scheme " + scheme);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_FAILED_UNSUPPORTED_SCHEME);
finish();
return;
}
final PackageUtil.AppSnippet as;
if ("package".equals(mPackageURI.getScheme())) {
mInstallFlowAnalytics.setFileUri(false);
try {
mPkgInfo = mPm.getPackageInfo(mPackageURI.getSchemeSpecificPart(),
PackageManager.GET_PERMISSIONS | PackageManager.GET_UNINSTALLED_PACKAGES);
} catch (NameNotFoundException e) {
}
if (mPkgInfo == null) {
Log.w(TAG, "Requested package " + mPackageURI.getScheme()
+ " not available. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
mInstallFlowAnalytics.setPackageInfoObtained();
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_FAILED_PACKAGE_MISSING);
return;
}
as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
mPm.getApplicationIcon(mPkgInfo.applicationInfo));
} else {
mInstallFlowAnalytics.setFileUri(true);
final File sourceFile = new File(mPackageURI.getPath());
PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);
// Check for parse errors
if (parsed == null) {
Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
mInstallFlowAnalytics.setPackageInfoObtained();
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_FAILED_TO_GET_PACKAGE_INFO);
return;
}
mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
PackageManager.GET_PERMISSIONS, 0, 0, null,
new PackageUserState());
mPkgDigest = parsed.manifestDigest;
as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
}
mInstallFlowAnalytics.setPackageInfoObtained();
//set view
setContentView(R.layout.install_start);
mInstallConfirm = findViewById(R.id.install_confirm_panel);
mInstallConfirm.setVisibility(View.INVISIBLE);
PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
mOriginatingUid = getOriginatingUid(intent);
// Block the install attempt on the Unknown Sources setting if necessary.
if (!requestFromUnknownSource) {
initiateInstall();
return;
}
// If the admin prohibits it, or we're running in a managed profile, just show error
// and exit. Otherwise show an option to take the user to Settings to change the setting.
final boolean isManagedProfile = mUserManager.isManagedProfile();
if (!unknownSourcesAllowedByAdmin
|| (!unknownSourcesAllowedByUser && isManagedProfile)) {
showDialogInner(DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES);
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING);
} else if (!unknownSourcesAllowedByUser) {
// Ask user to enable setting first
showDialogInner(DLG_UNKNOWN_SOURCES);
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING);
} else {
initiateInstall();
}
}
這裡我們主要先看一下PackageInstallerActivity的onCreate方法:
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
mPm = getPackageManager();
mInstaller = mPm.getPackageInstaller();
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
final Intent intent = getIntent();
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
finish();
return;
}
mSessionId = sessionId;
mPackageURI = Uri.fromFile(new File(info.resolvedBaseCodePath));
mOriginatingURI = null;
mReferrerURI = null;
} else {
mSessionId = -1;
mPackageURI = intent.getData();
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
}
final boolean unknownSourcesAllowedByAdmin = isUnknownSourcesAllowedByAdmin();
final boolean unknownSourcesAllowedByUser = isUnknownSourcesEnabled();
boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(intent);
mInstallFlowAnalytics = new InstallFlowAnalytics();
mInstallFlowAnalytics.setContext(this);
mInstallFlowAnalytics.setStartTimestampMillis(SystemClock.elapsedRealtime());
mInstallFlowAnalytics.setInstallsFromUnknownSourcesPermitted(unknownSourcesAllowedByAdmin
&& unknownSourcesAllowedByUser);
mInstallFlowAnalytics.setInstallRequestFromUnknownSource(requestFromUnknownSource);
mInstallFlowAnalytics.setVerifyAppsEnabled(isVerifyAppsEnabled());
mInstallFlowAnalytics.setAppVerifierInstalled(isAppVerifierInstalled());
mInstallFlowAnalytics.setPackageUri(mPackageURI.toString());
if (DeviceUtils.isWear(this)) {
showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_NOT_ALLOWED_ON_WEAR);
return;
}
final String scheme = mPackageURI.getScheme();
if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
Log.w(TAG, "Unsupported scheme " + scheme);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_FAILED_UNSUPPORTED_SCHEME);
finish();
return;
}
final PackageUtil.AppSnippet as;
if ("package".equals(mPackageURI.getScheme())) {
mInstallFlowAnalytics.setFileUri(false);
try {
mPkgInfo = mPm.getPackageInfo(mPackageURI.getSchemeSpecificPart(),
PackageManager.GET_PERMISSIONS | PackageManager.GET_UNINSTALLED_PACKAGES);
} catch (NameNotFoundException e) {
}
if (mPkgInfo == null) {
Log.w(TAG, "Requested package " + mPackageURI.getScheme()
+ " not available. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
mInstallFlowAnalytics.setPackageInfoObtained();
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_FAILED_PACKAGE_MISSING);
return;
}
as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
mPm.getApplicationIcon(mPkgInfo.applicationInfo));
} else {
mInstallFlowAnalytics.setFileUri(true);
final File sourceFile = new File(mPackageURI.getPath());
PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);
// Check for parse errors
if (parsed == null) {
Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
mInstallFlowAnalytics.setPackageInfoObtained();
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_FAILED_TO_GET_PACKAGE_INFO);
return;
}
mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
PackageManager.GET_PERMISSIONS, 0, 0, null,
new PackageUserState());
mPkgDigest = parsed.manifestDigest;
as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
}
mInstallFlowAnalytics.setPackageInfoObtained();
//set view
setContentView(R.layout.install_start);
mInstallConfirm = findViewById(R.id.install_confirm_panel);
mInstallConfirm.setVisibility(View.INVISIBLE);
PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
mOriginatingUid = getOriginatingUid(intent);
// Block the install attempt on the Unknown Sources setting if necessary.
if (!requestFromUnknownSource) {
initiateInstall();
return;
}
// If the admin prohibits it, or we're running in a managed profile, just show error
// and exit. Otherwise show an option to take the user to Settings to change the setting.
final boolean isManagedProfile = mUserManager.isManagedProfile();
if (!unknownSourcesAllowedByAdmin
|| (!unknownSourcesAllowedByUser && isManagedProfile)) {
showDialogInner(DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES);
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING);
} else if (!unknownSourcesAllowedByUser) {
// Ask user to enable setting first
showDialogInner(DLG_UNKNOWN_SOURCES);
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING);
} else {
initiateInstall();
}
}
可以發現,在onCreate方法中,首先執行一些初始化操作,獲取PackageManager和Installer、UserManager等對象,然後會根據當前Intent的信息最一些邏輯判斷並彈出消息彈窗,我們可以看一下具體的消息彈窗類型:
private static final int DLG_BASE = 0;
private static final int DLG_UNKNOWN_SOURCES = DLG_BASE + 1;
private static final int DLG_PACKAGE_ERROR = DLG_BASE + 2;
private static final int DLG_OUT_OF_SPACE = DLG_BASE + 3;
private static final int DLG_INSTALL_ERROR = DLG_BASE + 4;
private static final int DLG_ALLOW_SOURCE = DLG_BASE + 5;
private static final int DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES = DLG_BASE + 6;
private static final int DLG_NOT_SUPPORTED_ON_WEAR = DLG_BASE + 7;
可以發現當分析Intent對象的時候,如果可以得到這樣幾種結果:不知道apk的來源,package信息錯誤,存儲空間不夠,安裝時報,來源正確,允許未知來源的apk文件,在wear上不支持等,這樣根據不同的消息類型會彈出不同的消息彈窗:
@Override
public Dialog onCreateDialog(int id, Bundle bundle) {
switch (id) {
case DLG_UNKNOWN_SOURCES:
return new AlertDialog.Builder(this)
.setTitle(R.string.unknown_apps_dlg_title)
.setMessage(R.string.unknown_apps_dlg_text)
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Log.i(TAG, "Finishing off activity so that user can navigate to settings manually");
finish();
}})
.setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Log.i(TAG, "Launching settings");
launchSecuritySettings();
}
})
.setOnCancelListener(this)
.create();
case DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES:
return new AlertDialog.Builder(this)
.setTitle(R.string.unknown_apps_dlg_title)
.setMessage(R.string.unknown_apps_admin_dlg_text)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.setOnCancelListener(this)
.create();
case DLG_PACKAGE_ERROR :
return new AlertDialog.Builder(this)
.setTitle(R.string.Parse_error_dlg_title)
.setMessage(R.string.Parse_error_dlg_text)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.setOnCancelListener(this)
.create();
case DLG_OUT_OF_SPACE:
// Guaranteed not to be null. will default to package name if not set by app
CharSequence appTitle = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
String dlgText = getString(R.string.out_of_space_dlg_text,
appTitle.toString());
return new AlertDialog.Builder(this)
.setTitle(R.string.out_of_space_dlg_title)
.setMessage(dlgText)
.setPositiveButton(R.string.manage_applications, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
//launch manage applications
Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Log.i(TAG, "Canceling installation");
finish();
}
})
.setOnCancelListener(this)
.create();
case DLG_INSTALL_ERROR :
// Guaranteed not to be null. will default to package name if not set by app
CharSequence appTitle1 = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
String dlgText1 = getString(R.string.install_failed_msg,
appTitle1.toString());
return new AlertDialog.Builder(this)
.setTitle(R.string.install_failed)
.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.setMessage(dlgText1)
.setOnCancelListener(this)
.create();
case DLG_ALLOW_SOURCE:
CharSequence appTitle2 = mPm.getApplicationLabel(mSourceInfo);
String dlgText2 = getString(R.string.allow_source_dlg_text,
appTitle2.toString());
return new AlertDialog.Builder(this)
.setTitle(R.string.allow_source_dlg_title)
.setMessage(dlgText2)
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
setResult(RESULT_CANCELED);
finish();
}})
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = getSharedPreferences(PREFS_ALLOWED_SOURCES,
Context.MODE_PRIVATE);
prefs.edit().putBoolean(mSourceInfo.packageName, true).apply();
startInstallConfirm();
}
})
.setOnCancelListener(this)
.create();
case DLG_NOT_SUPPORTED_ON_WEAR:
return new AlertDialog.Builder(this)
.setTitle(R.string.wear_not_allowed_dlg_title)
.setMessage(R.string.wear_not_allowed_dlg_text)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
setResult(RESULT_OK);
finish();
}
})
.setOnCancelListener(this)
.create();
}
return null;
}
消息彈窗的主要作用,用於提示用戶當前安裝apk文件的特性。都知道android系統在android apk文件之前會解析器manifest文件,這個操作也是早onCreate方法中執行的:
PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);
我們具體看一下getPackageInfo方法的實現:
public static PackageParser.Package getPackageInfo(File sourceFile) {
final PackageParser parser = new PackageParser();
try {
PackageParser.Package pkg = parser.parseMonolithicPackage(sourceFile, 0);
parser.collectManifestDigest(pkg);
return pkg;
} catch (PackageParserException e) {
return null;
}
}
好吧,到了這裡是不是代碼變得很熟悉了?parseMonolithicPackage就是我們上一節分析的android系統解析manifest文件的過程。
而collectManifestDigest方法,我們這裡簡單的介紹一下,其主要是要爭apk的簽名是否正確。好吧通過這兩部我們就把apk文件的manifest和簽名信息都解析完成並保存在了Package中。
接著往下走,在所有的解析完成之後我們會在onCreate方法中執行initiateInstall();方法,剛方法的主要作用是初始化安裝。
private void initiateInstall() {
String pkgName = mPkgInfo.packageName;
// Check if there is already a package on the device with this name
// but it has been renamed to something else.
String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
if (oldName != null && oldName.length > 0 && oldName[0] != null) {
pkgName = oldName[0];
mPkgInfo.packageName = pkgName;
mPkgInfo.applicationInfo.packageName = pkgName;
}
// Check if package is already installed. display confirmation dialog if replacing pkg
try {
// This is a little convoluted because we want to get all uninstalled
// apps, but this may include apps with just data, and if it is just
// data we still want to count it as "installed".
mAppInfo = mPm.getApplicationInfo(pkgName,
PackageManager.GET_UNINSTALLED_PACKAGES);
if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
mAppInfo = null;
}
} catch (NameNotFoundException e) {
mAppInfo = null;
}
mInstallFlowAnalytics.setReplace(mAppInfo != null);
mInstallFlowAnalytics.setSystemApp(
(mAppInfo != null) && ((mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0));
// If we have a session id, we're invoked to verify the permissions for the given
// package. Otherwise, we start the install process.
if (mSessionId != -1) {
startInstallConfirm();
} else {
startInstall();
}
}
好吧,這裡面有調用了startInstallConfirm方法,然後我們看一下startInstallConfirm方法的實現:
private void startInstallConfirm() {
...
//初始化安裝確認界面
...
}
好吧,這個方法的實現比較簡單,主要的實現邏輯就是現實該activity的用戶界面,平時我們安裝某一個應用的時候會彈出一個安裝確認頁面,還有一個確認和取消按鈕,有印象麼?其實就是在這裡執行的界面初始化操作。
好吧,一般情況下在apk安裝確認頁面,我們會點擊確認按鈕執行安裝邏輯吧?那麼這裡我們找一下確認按鈕的點擊事件:
public void onClick(View v) {
if (v == mOk) {
if (mOkCanInstall || mScrollView == null) {
mInstallFlowAnalytics.setInstallButtonClicked();
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, true);
// We're only confirming permissions, so we don't really know how the
// story ends; assume success.
mInstallFlowAnalytics.setFlowFinishedWithPackageManagerResult(
PackageManager.INSTALL_SUCCEEDED);
finish();
} else {
startInstall();
}
} else {
mScrollView.pageScroll(View.FOCUS_DOWN);
}
} else if (v == mCancel) {
// Cancel and finish
setResult(RESULT_CANCELED);
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, false);
}
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_CANCELLED_BY_USER);
finish();
}
}
很明顯了,這裡當我們點擊確認按鈕的時候會執行startInstall方法,也就是開始執行安裝邏輯:
private void startInstall() {
// Start subactivity to actually install the application
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);
newIntent.putExtra(
InstallAppProgress.EXTRA_INSTALL_FLOW_ANALYTICS, mInstallFlowAnalytics);
String installerPackageName = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (mOriginatingURI != null) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
}
if (mReferrerURI != null) {
newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
}
if (mOriginatingUid != VerificationParams.NO_UID) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
}
if (installerPackageName != null) {
newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
installerPackageName);
}
if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
}
if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
startActivity(newIntent);
finish();
}
可以發現,點擊確認按鈕之後我們調用啟用了一個新的Activity–>InstallAppProgress,這個Activity主要用於執行apk的安裝邏輯了。
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
Intent intent = getIntent();
mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
mInstallFlowAnalytics = intent.getParcelableExtra(EXTRA_INSTALL_FLOW_ANALYTICS);
mInstallFlowAnalytics.setContext(this);
mPackageURI = intent.getData();
final String scheme = mPackageURI.getScheme();
if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_FAILED_UNSUPPORTED_SCHEME);
throw new IllegalArgumentException("unexpected scheme " + scheme);
}
mInstallThread = new HandlerThread("InstallThread");
mInstallThread.start();
mInstallHandler = new Handler(mInstallThread.getLooper());
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BROADCAST_ACTION);
registerReceiver(
mBroadcastReceiver, intentFilter, BROADCAST_SENDER_PERMISSION, null /*scheduler*/);
initView();
}
可以發現InstallAppProcess這個Activity的onCreate方法中主要初始化了一些成員變量,並調用initView方法,我們在iniTView方法中可以看到:
void initView() {
...
mInstallHandler.post(new Runnable() {
@Override
public void run() {
doPackageStage(pm, params);
}
});
...
}
經過一些view的初始化操作之後調用了doPackageStage方法,該方法主要是通過調用PackageInstaller執行apk文件的安裝,這裡就不在詳細的介紹了,在apk文件安裝完成之後PackageInstaller會發送一個安裝完成的廣播,剛剛我們在onCreate方法中注冊了一個廣播接收器,其可以用來接收apk安裝完成的廣播:
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final int statusCode = intent.getIntExtra(
PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
if (statusCode == PackageInstaller.STATUS_PENDING_USER_ACTION) {
context.startActivity((Intent)intent.getParcelableExtra(Intent.EXTRA_INTENT));
} else {
onPackageInstalled(statusCode);
}
}
};
這樣apk安裝完成之後,這裡的廣播接收器會接收到廣播並執行onPackageInstalled方法,執行後續的處理邏輯,那麼我們來看一下onPackageInstalled方法的具體實現邏輯:
void onPackageInstalled(int statusCode) {
Message msg = mHandler.obtainMessage(INSTALL_COMPLETE);
msg.arg1 = statusCode;
mHandler.sendMessage(msg);
}
好吧,這裡是發送Handler異步消息,我們來看一下異步消息的處理邏輯:
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case INSTALL_COMPLETE:
mInstallFlowAnalytics.setFlowFinishedWithPackageManagerResult(msg.arg1);
if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
Intent result = new Intent();
result.putExtra(Intent.EXTRA_INSTALL_RESULT, msg.arg1);
setResult(msg.arg1 == PackageInstaller.STATUS_SUCCESS
? Activity.RESULT_OK : Activity.RESULT_FIRST_USER,
result);
finish();
return;
}
// Update the status text
mProgressBar.setVisibility(View.INVISIBLE);
// Show the ok button
int centerTextLabel;
int centerExplanationLabel = -1;
LevelListDrawable centerTextDrawable =
(LevelListDrawable) getDrawable(R.drawable.ic_result_status);
if (msg.arg1 == PackageInstaller.STATUS_SUCCESS) {
mLaunchButton.setVisibility(View.VISIBLE);
centerTextDrawable.setLevel(0);
centerTextLabel = R.string.install_done;
// Enable or disable launch button
mLaunchIntent = getPackageManager().getLaunchIntentForPackage(
mAppInfo.packageName);
boolean enabled = false;
if(mLaunchIntent != null) {
List list = getPackageManager().
queryIntentActivities(mLaunchIntent, 0);
if (list != null && list.size() > 0) {
enabled = true;
}
}
if (enabled) {
mLaunchButton.setOnClickListener(InstallAppProgress.this);
} else {
mLaunchButton.setEnabled(false);
}
} else if (msg.arg1 == PackageInstaller.STATUS_FAILURE_STORAGE){
showDialogInner(DLG_OUT_OF_SPACE);
return;
} else {
// Generic error handling for all other error codes.
centerTextDrawable.setLevel(1);
centerExplanationLabel = getExplanationFromErrorCode(msg.arg1);
centerTextLabel = R.string.install_failed;
mLaunchButton.setVisibility(View.INVISIBLE);
}
if (centerTextDrawable != null) {
centerTextDrawable.setBounds(0, 0,
centerTextDrawable.getIntrinsicWidth(),
centerTextDrawable.getIntrinsicHeight());
mStatusTextView.setCompoundDrawablesRelative(centerTextDrawable, null,
null, null);
}
mStatusTextView.setText(centerTextLabel);
if (centerExplanationLabel != -1) {
mExplanationTextView.setText(centerExplanationLabel);
mExplanationTextView.setVisibility(View.VISIBLE);
} else {
mExplanationTextView.setVisibility(View.GONE);
}
mDoneButton.setOnClickListener(InstallAppProgress.this);
mOkPanel.setVisibility(View.VISIBLE);
break;
default:
break;
}
}
};
可以發現,當apk安裝完成之後,我們會更新UI,顯示完成和打開按鈕,是不是和我們平時安裝apk的邏輯對應上了?這時候我們可以看一下這兩個按鈕的點擊事件。
public void onClick(View v) {
if(v == mDoneButton) {
if (mAppInfo.packageName != null) {
Log.i(TAG, "Finished installing "+mAppInfo.packageName);
}
finish();
} else if(v == mLaunchButton) {
startActivity(mLaunchIntent);
finish();
}
}
好吧,比較簡單,點擊完成按鈕,直接finish掉這個activity,點擊打開,則直接調用startActivity啟動安裝的應用,然後直接finish自身。
總結:
代碼中執行intent.setDataAndType(Uri.parse(“file://” + path),”application/vnd.android.package-archive”);可以調起PackageInstallerActivity;
PackageInstallerActivity主要用於執行解析apk文件,解析manifest,解析簽名等操作;
InstallAppProcess主要用於執行安裝apk邏輯,用於初始化安裝界面,用於初始化用戶UI。並調用PackageInstaller執行安裝邏輯;
InstallAppProcess內注冊有廣播,當安裝完成之後接收廣播,更新UI。顯示apk安裝完成界面;
什麼是wipe?wipe什麼意思?雙wipe又是什麼?安卓手機如何wipe?對於剛接觸安卓手機的同學來說是比較模糊的概念,那到底wipe是什麼意思呢,下面來
Android Studio快速提取方法 在開發過程中,有時在一個方法內部寫了過多的代碼,然後想要把一些代碼提取出來封裝下,分離開放在一個單獨的方法裡,可能你的
自定義控件(類似按鈕等)的使用,自定義一個SurfaceView。 如某一塊的動態圖(自定義相應),或者類似UC浏覽器下面的工具欄。 如下圖示例: 自定義類代
字體圖標字體圖標是指將圖標做成字體文件(.ttf),從而代替傳統的png等圖標資源。使用字體圖標的優點和缺點分別為: 優點: &nbs