編輯:關於Android編程
我們知道,在android手機上安裝一個apk很簡單,只要打開apk文件,默認就會彈出安裝界面,然後點擊確定,經過若干秒後,apk就安裝成功了,可是你知道apk的安裝過程是什麼嗎?你知道android系統在安裝一個apk的時候都干了什麼嗎?在本文中,將一一解答這個問題。簡單來說,apk的安裝過程分兩步:第一步,將apk文件復制到程序目錄下(/data/app/);第二步,為應用創建數據目錄(/data/data/package name/)、提取dex文件到指定目錄(/data/dalvik-cache/)、修改系統包管理信息。注意,本文的分析基於Android 4.3源碼。
apk的安裝從PackageManager的installApk方法開始,由於PackageManager所對應的binder服務為PackageManagerService(PMS),所以,真正的安裝過程都在PackageManagerService中完成。PackageManagerService的installApk方法最終調用了installPackageWithVerificationAndEncryption方法,該方法的核心就是在最後發送了一個INIT_COPY的消息,這個消息的含義是完成apk的拷貝過程。
public void installPackageWithVerificationAndEncryption(Uri packageURI,
IPackageInstallObserver observer, int flags, String installerPackageName,
VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES,
null);
final int uid = Binder.getCallingUid();
if (isUserRestricted(UserHandle.getUserId(uid), UserManager.DISALLOW_INSTALL_APPS)) {
try {
observer.packageInstalled(, PackageManager.INSTALL_FAILED_USER_RESTRICTED);
} catch (RemoteException re) {
}
return;
}
UserHandle user;
if ((flags&PackageManager.INSTALL_ALL_USERS) != 0) {
user = UserHandle.ALL;
} else {
user = new UserHandle(UserHandle.getUserId(uid));
}
final int filteredFlags;
if (uid == Process.SHELL_UID || uid == 0) {
if (DEBUG_INSTALL) {
Slog.v(TAG, Install from ADB);
}
filteredFlags = flags | PackageManager.INSTALL_FROM_ADB;
} else {
filteredFlags = flags & ~PackageManager.INSTALL_FROM_ADB;
}
verificationParams.setInstallerUid(uid);
final Message msg = mHandler.obtainMessage(INIT_COPY);
msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName,
verificationParams, encryptionParams, user);
mHandler.sendMessage(msg);
}
通過分析代碼可以發現,真正實現apk拷貝的方法是InstallParams的handleStartCopy方法,InstallParams中有重試機制,拷貝如果失敗的話會重試,最多重試4次。在拷貝之前,還必須做一件事情,那就是綁定media container service,安裝過程中一些狀態的檢查會用到這個服務,代碼如下所示:
class PackageHandler extends Handler {
private boolean mBound = false;
final ArrayList mPendingInstalls =
new ArrayList();
private boolean connectToService() {
if (DEBUG_SD_INSTALL) Log.i(TAG, Trying to bind to +
DefaultContainerService);
Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
if (mContext.bindServiceAsUser(service, mDefContainerConn,
Context.BIND_AUTO_CREATE, UserHandle.OWNER)) {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mBound = true;
return true;
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return false;
}
...
}
現在分析一下InstallParams的handleStartCopy方法,這個方法很長,代碼就不帖出來了,大家可以自己去看看,這裡主要分析下它的工作流程:
1. 檢查安裝位置標記位是否有沖突,如果有沖突,則安裝失敗,這裡的有沖突是指“一個apk同時要求被安裝到內部存儲和sd卡”
2. 調用MCS服務的getMinimalPackageInfo方法來得到apk的推薦安裝位置,並檢查是否能夠進行正常的安裝。在這一步,有可能拋出一些無法安裝的狀態位:存儲空間不足、程序已經安裝、無效的apk文件等,這個時候安裝過程終止
3. 到這一步,表示程序可以正常安裝,同時MCS服務服務可能會根據需要調整安裝位置,在InstallParams的installLocationPolicy中完成
4. 文件的復制過程,PMS針對內部存儲和sd卡分別提供了一個類:FileInstallArgs和AsecInstallArgs,並分別調用二者的copyApk方法來完成apk的復制過程
經過了上面4步,待安裝apk已經被復制到了/data/app/目錄了。
上面,apk已經被復制到了/data/app/目錄,安裝的第一步已經完成,那麼系統是什麼時候對apk進行dex提取和解析的呢,這還要從PMS說起,在PMS內部有一個AppDirObserver類,顧名思義,它的作用是應用目錄觀察者,它時刻觀察著應用目錄/data/app/,當目錄內部結構改變的時候(創建文件和刪除文件)它會做出相應行為,下面看下它的代碼:
private final class AppDirObserver extends FileObserver {
public AppDirObserver(String path, int mask, boolean isrom) {
super(path, mask);
mRootDir = path;
mIsRom = isrom;
}
//在/data/app/目錄下添加或刪除apk的時候,此方法會被調用
public void onEvent(int event, String path) {
String removedPackage = null;
int removedAppId = -1;
int[] removedUsers = null;
String addedPackage = null;
int addedAppId = -1;
int[] addedUsers = null;
// TODO post a message to the handler to obtain serial ordering
synchronized (mInstallLock) {
String fullPathStr = null;
File fullPath = null;
if (path != null) {
fullPath = new File(mRootDir, path);
fullPathStr = fullPath.getPath();
}
if (DEBUG_APP_DIR_OBSERVER)
Log.v(TAG, File + fullPathStr + changed: + Integer.toHexString(event));
if (!isPackageFilename(path)) {
if (DEBUG_APP_DIR_OBSERVER)
Log.v(TAG, Ignoring change of non-package file: + fullPathStr);
return;
}
// Ignore packages that are being installed or
// have just been installed.
if (ignoreCodePath(fullPathStr)) {
return;
}
PackageParser.Package p = null;
PackageSetting ps = null;
// reader
synchronized (mPackages) {
p = mAppDirs.get(fullPathStr);
if (p != null) {
ps = mSettings.mPackages.get(p.applicationInfo.packageName);
if (ps != null) {
removedUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
} else {
removedUsers = sUserManager.getUserIds();
}
}
addedUsers = sUserManager.getUserIds();
}
//當apk被刪除的時候,往往意味著這個apk被卸載
if ((event&REMOVE_EVENTS) != 0) {
if (ps != null) {
if (DEBUG_REMOVE) Slog.d(TAG, Package disappeared: + ps);
//removePackageLI方法完成卸載apk的主要功能
removePackageLI(ps, true);
removedPackage = ps.name;
removedAppId = ps.appId;
}
}
//新添加了一個apk,往往意味著一個新的apk被安裝
if ((event&ADD_EVENTS) != 0) {
if (p == null) {
if (DEBUG_INSTALL) Slog.d(TAG, New file appeared: + fullPath);
//scanPackageLI方法完成了apk安裝的第二個步驟
p = scanPackageLI(fullPath,
(mIsRom ? PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR: 0) |
PackageParser.PARSE_CHATTY |
PackageParser.PARSE_MUST_BE_APK,
SCAN_MONITOR | SCAN_NO_PATHS | SCAN_UPDATE_TIME,
System.currentTimeMillis(), UserHandle.ALL);
if (p != null) {
/*
* TODO this seems dangerous as the package may have
* changed since we last acquired the mPackages
* lock.
*/
// writer
synchronized (mPackages) {
updatePermissionsLPw(p.packageName, p,
p.permissions.size() > 0 ? UPDATE_PERMISSIONS_ALL : 0);
}
addedPackage = p.applicationInfo.packageName;
addedAppId = UserHandle.getAppId(p.applicationInfo.uid);
}
}
}
// reader
synchronized (mPackages) {
mSettings.writeLPr();
}
}
//下面兩個if語句塊大家應用不陌生吧,在我們的應用中想監聽應用的安裝和卸載,
//就是通過收聽ACTION_PACKAGE_ADDED和ACTION_PACKAGE_REMOVED這兩個廣播來實現的
if (removedPackage != null) {
Bundle extras = new Bundle(1);
extras.putInt(Intent.EXTRA_UID, removedAppId);
extras.putBoolean(Intent.EXTRA_DATA_REMOVED, false);
sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage,
extras, null, null, removedUsers);
}
if (addedPackage != null) {
Bundle extras = new Bundle(1);
extras.putInt(Intent.EXTRA_UID, addedAppId);
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage,
extras, null, null, addedUsers);
}
}
private final String mRootDir;
private final boolean mIsRom;
}
下面,我們主要分析一下scanPackageLI方法,還是僅僅分析,不帖代碼,因為代碼太長了,帖出來沒法看了,這個方法不僅僅是完成apk包的掃描,還解析AndroidManifest.xml文件並提取出所有的intent-filter和permission信息,apk安裝的主要功能都由它來完成的,當apk包掃描完成後,系統會調用updatePermissionsLPw方法更新系統所具有的權限。
scanPackageLI方法有兩個,其第一個參數分別接受File和PackageParser.Package類型,第一個方法會從File中提取出package信息然後再調用第二個方法,下面分析第二個scanPackageLI方法,其完成的事情如下:
1. 如果包名是android,則會做一些特殊處理,這個包名為android的應用是系統內部應用的,其他應用的包名如果叫android則安裝會有問題,大家可以試一下
2. 解析常見的use-feature、shared-userId、use-library標簽並保存到成員變量中
3. 進行簽名驗證,對應的方法是verifySignaturesLP,驗證失敗則應用無法安裝
4. 創建應用程序目錄/data/data/包名,同時將apk中提取出dex文件並保存到/data/dalvik-cache,把apk當做zip解壓就能得到dex文件
5. 解析AndroidManifest.xml文件,提取出所需信息,包括具有intent-filter的四大組件信息(Activity、Service、BroadcastReceiver、ContentProvider)和聲明的系統權限等
到此為止,scanPackageLI方法結束了。而updatePermissionsLPw的作用是對系統中所有的權限進行更新,大家可以查看下/system/etc/permissons目錄,下面定義了android系統中所有的權限,開發中最常用的權限定義在目錄下的platform.xml裡面,大家可以打開看看,可以看到常見的訪問網絡、讀寫外部存儲等權限等都是在這裡定義的。權限更新完畢以後,系統就會發送ACTION_PACKAGE_ADDED廣播,告知所有應用有新應用安裝了。另外,大家可以查看下data/system/目錄,裡面有兩個文件packages.list和packages.xml,在packages.list裡面放的是手機上安裝的所有應用列表,而packages.xml中存放的是所有應用的設置應用,比如一個應用聲明了哪些系統權限就定義在這裡面。關於應用的卸載,我們可以想到是應用安裝過程的逆過程,大致要做的是:停止應用、刪除各種文件,更新系統設置、權限等,大家感興趣自己看一下,完全是安裝過程的逆過程,這裡不介紹了。
觀察者模式在android中使用
觀察者模式(Observer)觀察者模式是對象的行為模式,又被叫做為模型-視圖模式。這種模式定義了一種一對多的依賴關系,使多個觀察者對象同時監聽某個角色對象。一旦這個角色
android actionbar 網頁在線生成style(修改配置)
android actionbar這個導航欄,相信大家愛已經不陌生了。自從android 3.0以上就有了這個導航欄功能。在郭大神博客有詳細介紹actionbar功能。我
Android開發之自動更換壁紙
本程序主要實現了: 1.使用AssetManager將assets目錄中的文件復制到SD卡的指定位置 2.使用AlarmManager全局定時器,周期性的啟動指定組件切換
Android實現底部彈出PopupWindow背景逐漸變暗效果
在Android開發中,經常需要通過點擊某個按鈕彈出對話框或者選擇框,通過Dialog或者PopupMenu、PopupWindow都能實現。 這裡主要介紹後兩者:Pop