編輯:關於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 ArrayListmPendingInstalls = 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中存放的是所有應用的設置應用,比如一個應用聲明了哪些系統權限就定義在這裡面。關於應用的卸載,我們可以想到是應用安裝過程的逆過程,大致要做的是:停止應用、刪除各種文件,更新系統設置、權限等,大家感興趣自己看一下,完全是安裝過程的逆過程,這裡不介紹了。
觀察者模式(Observer)觀察者模式是對象的行為模式,又被叫做為模型-視圖模式。這種模式定義了一種一對多的依賴關系,使多個觀察者對象同時監聽某個角色對象。一旦這個角色
android actionbar這個導航欄,相信大家愛已經不陌生了。自從android 3.0以上就有了這個導航欄功能。在郭大神博客有詳細介紹actionbar功能。我
本程序主要實現了: 1.使用AssetManager將assets目錄中的文件復制到SD卡的指定位置 2.使用AlarmManager全局定時器,周期性的啟動指定組件切換
在Android開發中,經常需要通過點擊某個按鈕彈出對話框或者選擇框,通過Dialog或者PopupMenu、PopupWindow都能實現。 這裡主要介紹後兩者:Pop