Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android應用安裝

Android應用安裝

編輯:關於Android編程

Android通過PackageManagerService(後面簡稱Pms)進行包管理,其主要功能包括:用戶ID分配、包解析、包的安裝卸載等。本文不對Pms進行分析,主要目的是探討一下包安裝。在本文中主要探討包安裝的相關操作,卸載作為安裝的逆過程,實現類似,不再贅述。

Android中APK的安裝方式

在Android中APK的安裝有三種方式:

1、開機Pms初始化時,掃描包安裝目錄。

@/frameworks/base/services/java/com/android/server/SystemServer.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public void initAndLoop() { ...... IPackageManager pm = null; ...... try { ...... pm = PackageManagerService.main(context, installer, factoryTest != SystemServer.FACTORY_TEST_OFF, onlyCore); ...... } catch (RuntimeException e) { Slog.e("System", "******************************************"); Slog.e("System", "************ Failure starting core service", e); } ...... }

@/frameworks/base/services/java/com/android/server/pm/PackageManagerService.java

1 2 3 4 5 6 7 public static final IPackageManager main(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { PackageManagerService m = new PackageManagerService(context, installer, factoryTest, onlyCore); ServiceManager.addService("package", m); return m; }

下面是Pms構造函數的實現:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { ...... synchronized (mInstallLock) { // writer synchronized (mPackages) { ...... File dataDir = Environment.getDataDirectory(); mAppDataDir = new File(dataDir, "data"); mAppInstallDir = new File(dataDir, "app"); mAppLibInstallDir = new File(dataDir, "app-lib"); mAsecInternalPath = new File(dataDir, "app-asec").getPath(); mUserAppDataDir = new File(dataDir, "user"); mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); ...... // Find base frameworks (resource packages without code). mFrameworkInstallObserver = new AppDirObserver( frameworkDir.getPath(), OBSERVER_EVENTS, true, false); mFrameworkInstallObserver.startWatching(); scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode | SCAN_NO_DEX, 0); // Collected privileged system packages. File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app"); mPrivilegedInstallObserver = new AppDirObserver( privilegedAppDir.getPath(), OBSERVER_EVENTS, true, true); mPrivilegedInstallObserver.startWatching(); scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR | PackageParser.PARSE_IS_PRIVILEGED, scanMode, 0); // Collect ordinary system packages. File systemAppDir = new File(Environment.getRootDirectory(), "app"); mSystemInstallObserver = new AppDirObserver( systemAppDir.getPath(), OBSERVER_EVENTS, true, false); mSystemInstallObserver.startWatching(); scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0); // Collect all vendor packages. File vendorAppDir = new File("/vendor/app"); mVendorInstallObserver = new AppDirObserver( vendorAppDir.getPath(), OBSERVER_EVENTS, true, false); mVendorInstallObserver.startWatching(); scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0); ...... if (!mOnlyCore) { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START, SystemClock.uptimeMillis()); mAppInstallObserver = new AppDirObserver( mAppInstallDir.getPath(), OBSERVER_EVENTS, false, false); mAppInstallObserver.startWatching(); scanDirLI(mAppInstallDir, 0, scanMode, 0); mDrmAppInstallObserver = new AppDirObserver( mDrmAppPrivateInstallDir.getPath(), OBSERVER_EVENTS, false, false); mDrmAppInstallObserver.startWatching(); scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK, scanMode, 0); ...... } // synchronized (mPackages) } // synchronized (mInstallLock) }

通過Pms的構造函數可以看出,Pms在初始化時會掃描/system/app、vender/app、/data/app、/data/app-private四個應用安裝目錄,然後調用sanDirLI方法進行安裝。Pms通過AppDirObserver對這四個應用安裝目錄進行監控,一旦發現APK格式的文件則會調用scanPackageLI進行安裝。

2、通過包安裝器PackageInstaller安裝

Android提供了一個默認的包安裝器,位於/package/app/PackageInstaller目錄。通過其Manifest文件可以看出,PackageInstaller會對我們安裝應用發出的Intent進行處理,這裡PackageInstaller提供了兩種處理方式,分別是:file方式和package方式。

@/package/app/PackageInstaller/AndroidManifest.xml

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <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>

@/package/app/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java

1 2 3 4 5 6 7 8 @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); ...... initiateInstall(); } 1 2 3 4 5 private void initiateInstall() { ...... startInstallConfirm(); }

在startInstallConfirm方法中點擊“確認”後,會發出一個Intent,接收者為InstallAppProgress。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public void onClick(View v) { if(v == mOk) { if (mOkCanInstall || mScrollView == null) { // Start subactivity to actually install the application mInstallFlowAnalytics.setInstallButtonClicked(); Intent newIntent = new Intent(); newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mPkgInfo.applicationInfo); newIntent.setData(mPackageURI); newIntent.setClass(this, InstallAppProgress.class); ...... startActivity(newIntent); finish(); } else { mScrollView.pageScroll(View.FOCUS_DOWN); } } else if(v == mCancel) { ...... } }

@/package/app/PackageInstaller/src/com/android/packageinstaller/InstallAppProgress.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public void initView() { setContentView(R.layout.op_progress); int installFlags = 0; PackageManager pm = getPackageManager(); ...... String installerPackageName = getIntent().getStringExtra( Intent.EXTRA_INSTALLER_PACKAGE_NAME); Uri originatingURI = getIntent().getParcelableExtra(Intent.EXTRA_ORIGINATING_URI); Uri referrer = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER); int originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID, VerificationParams.NO_UID); ManifestDigest manifestDigest = getIntent().getParcelableExtra(EXTRA_MANIFEST_DIGEST); VerificationParams verificationParams = new VerificationParams(null, originatingURI, referrer, originatingUid, manifestDigest); PackageInstallObserver observer = new PackageInstallObserver(); if ("package".equals(mPackageURI.getScheme())) { try { pm.installExistingPackage(mAppInfo.packageName); observer.packageInstalled(mAppInfo.packageName, PackageManager.INSTALL_SUCCEEDED); } catch (PackageManager.NameNotFoundException e) { observer.packageInstalled(mAppInfo.packageName, PackageManager.INSTALL_FAILED_INVALID_APK); } } else { pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags, installerPackageName, verificationParams, null); } }

InstallAppProgress即應用安裝過程中的進度條界面。通過上面的代碼可以看到在initView方法的最後會調用Pms的installPackageWithVerificationAndEncryption方法進行安裝。

3、通過adb命令安裝

adb命令pm是Pms的Shell客戶端,通過pm可以進行包相關的一些操作,包括安裝和卸載。pm命令的用法如下:

\\

pm的代碼實現在Pm.java中,如下:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+CkAvZnJhbWV3b3Jrcy9iYXNlL2NtZHMvcG0vc3JjL2NvbS9hbmRyb2lkL2NvbW1hbmRzL3BtL1BtLmphdmE8L3A+Cgo8dGFibGUgYm9yZGVyPQ=="0" cellpadding="0" cellspacing="0" class="noBorderTable "> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public static void main(String[] args) { new Pm().run(args); } public void run(String[] args) { ...... mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); ...... if ("install".equals(op)) { runInstall(); return; } if ("uninstall".equals(op)) { runUninstall(); return; } ...... }

在run方法中初始化了一個Pms的客戶端代理對象mPm,後續的相關操作將有mPm完成。下面看一下Pm中負責安裝的方法runInstall的代碼實現:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 private void runInstall() { int installFlags = PackageManager.INSTALL_ALL_USERS; ...... while ((opt=nextOption()) != null) { if (opt.equals("-l")) { installFlags |= PackageManager.INSTALL_FORWARD_LOCK; } else if (opt.equals("-r")) { installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; } else if (opt.equals("-i")) { installerPackageName = nextOptionData(); if (installerPackageName == null) { System.err.println("Error: no value specified for -i"); return; } } else if (opt.equals("-t")) { installFlags |= PackageManager.INSTALL_ALLOW_TEST; } else if (opt.equals("-s")) { // Override if -s option is specified. installFlags |= PackageManager.INSTALL_EXTERNAL; } else if (opt.equals("-f")) { // Override if -s option is specified. installFlags |= PackageManager.INSTALL_INTERNAL; } else if (opt.equals("-d")) { installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE; ...... PackageInstallObserver obs = new PackageInstallObserver(); try { VerificationParams verificationParams = new VerificationParams(verificationURI, originatingURI, referrerURI, VerificationParams.NO_UID, null); mPm.installPackageWithVerificationAndEncryption(apkURI, obs, installFlags, installerPackageName, verificationParams, encryptionParams); synchronized (obs) { while (!obs.finished) { try { obs.wait(); } catch (InterruptedException e) { } } if (obs.result == PackageManager.INSTALL_SUCCEEDED) { System.out.println("Success"); } else { System.err.println("Failure [" + installFailureToString(obs.result) + "]"); } } } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); } }

可以看出runInstall最終會調用Pms的installPackageWithVerificationAndEncryption方法進行安裝。通過pm安裝時,安裝成功的返回信息為“Success”,安裝失敗的返回信息為”Failure[失敗信息]"。

靜默安裝實現

在了解了Android中包安裝的方式後,接下來探討一些如何實現”靜默安裝“。所謂靜默安裝即跳過安裝界面和進度條,在不被用戶察覺的情況下載後台安裝。下面針對上面的三種安裝方式分別來分析如何實現靜默安裝。

1、push安裝包到應用安裝目錄的方式

在Pms初始化時安裝包的流程中,我們知道Pms會監控/system/app、vender/app、/data/app、/data/app-private這四個應用安裝目錄。因此如果能夠將APK文件push進應用安裝目錄不就可以觸發AppDirObserver中的包安裝邏輯了了嗎?所以這種思路理論上是行得通的,但有兩個局限:

    局限一:如下圖所示,/system/app的訪問權限為root,這就要求在push到/system/app目錄時必須具有root權限。

    而/data/app的訪問權限為system。要獲得system權限就要求使用這種方式的應用程序必須簽名為platform並且sharedUserId制定為“android.uid.system”。

    局限二:系統應用(/system/app)與普通應用(/data/app)的安裝方式是不同的,對於系統應用,所有資源都包含在apk這個zip包中,而且其在/system/app不必以包名命名(理論上可以隨便起名)。

    而對於普通應用安裝後,它的dex、lib、資源文件(安裝包)分別存放在不同的目錄,並且安裝後以packagename-x.apk的形式保存在/data/app目錄下。?

    \

    那這種安裝方式是不是就沒有用了呢?非也。

    網上有些電子市場或管家類軟件實現的”秒裝“功能應該就是安裝這個思路實現的,當然這裡只是猜測,需要進一步研究。

    2、調用Pm隱藏API

    Android實現了一個應用安裝器的APK負責包的安裝工作,在上面的分析中我們知道,PackageInstaller的工作實際上只是安裝界面、權限確認、進度顯示等,真正的安裝工作依然是調用Pms實現的。到這裡我們就有了第二種思路,能不能繞過安裝界面,直接調用Pms裡面的相應方法呢?當然可以,PackageManager類中就提供了這樣的方法:

    @/frameworks/base/core/java/android/content/pm/PackageManager.java

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 /** * @hide * * Install a package. Since this may take a little while, the result will * be posted back to the given observer. An installation will fail if the calling context * lacks the {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if the * package named in the package file's manifest is already installed, or if there's no space * available on the device. * * @param packageURI The location of the package file to install. This can be a 'file:' or a * 'content:' URI. * @param observer An observer callback to get notified when the package installation is * complete. {@link IPackageInstallObserver#packageInstalled(String, int)} will be * called when that happens. observer may be null to indicate that no callback is desired. * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK}, * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}. * @param installerPackageName Optional package name of the application that is performing the * installation. This identifies which market the package came from. */ public abstract void installPackage( Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName);

    可以看出,這個方法是hide的,因此在應用開發時如果要使用,必須通過反射。

    這裡的IPackageInstallObserver是installPackage方法的一個回調接口通知,其實現在IPackageInstallObserver.aidl中,如下:

    @/frameworks/base/core/java/com/android/content/pm/IPackageInstallObserver.aidl

    1 2 3 4 5 6 7 8 9 package android.content.pm; /** * API for installation callbacks from the Package Manager. * @hide */ oneway interface IPackageInstallObserver { void packageInstalled(in String packageName, int returnCode); }

    使用Android內置未公開API有兩種方法:一種是通過反射的方式實現;另一種是在工程目錄下建立與所引用系統類相同的類和方法,這裡只要求類和方法名相同,不需要實現,只保證編譯時不報錯就可以了,根據Java的類加載機制,在運行時,會去加載系統類。下面是采用第二種方法時的兩段示例代碼:

    實現接口回調的代碼如下:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class MyPakcageInstallObserver extends IPackageInstallObserver.Stub { Context cxt; String appName; String filename; String pkname; public MyPakcageInstallObserver(Context c, String appName, String filename,String packagename) { this.cxt = c; this.appName = appName; this.filename = filename; this.pkname = packagename; } @Override public void packageInstalled(String packageName, int returnCode) { Log.i(TAG, "returnCode = " + returnCode);// 返回1代表安裝成功 if (returnCode == 1) { //TODO } Intent it = new Intent(); it.setAction(CustomAction.INSTALL_ACTION); it.putExtra("install_returnCode", returnCode); it.putExtra("install_packageName", packageName); it.putExtra("install_appName", appName); cxt.sendBroadcast(it); } }

    調用PackageManager.java隱藏方法,代碼如下:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 /** * 靜默安裝 * */ public static void autoInstallApk(Context context, String fileName, String packageName, String APPName) { Log.d(TAG, "jing mo an zhuang:" + packageName + ",fileName:" + fileName); File file = new File(fileName); int installFlags = 0; if (!file.exists()) return; installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; if (hasSdcard()) { installFlags |= PackageManager.INSTALL_EXTERNAL; } PackageManager pm = context.getPackageManager(); try { IPackageInstallObserver observer = new MyPakcageInstallObserver( context, APPName, appId, fileName,packageName,type_name); Log.i(TAG, "########installFlags:" + installFlags+"packagename:"+packageName); pm.installPackage(Uri.fromFile(file), observer, installFlags, packageName); } catch (Exception e) { } }

    這種方法也有一定的限制:

    首先,要在AndroidManifest.xml中聲明”android.permission.INSTALL_PACKAGES”權限;

    其次,應用需要system權限。


    3、調用pm命令進行安裝

    在adb窗口通過pm install安裝包本來就是沒有安裝界面的,這不正是我們想要的嗎?通過pm的安裝方式需要取得root或system權限。

    pm的安裝方式有兩種,一種需要root權限,示例代碼如下:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 new Thread() { public void run() { Process process = null; OutputStream out = null; InputStream in = null; try { // 請求root process = Runtime.getRuntime().exec("su"); out = process.getOutputStream(); // 調用安裝 out.write(("pm install -r " + currentTempFilePath + "\n").getBytes()); in = process.getInputStream(); int len = 0; byte[] bs = new byte[256]; while (-1 != (len = in.read(bs))) { String state = new String(bs, 0, len); if (state.equals("Success\n")) { //安裝成功後的操作 } } } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (out != null) { out.flush(); out.close(); } if (in != null) { in.close(); } } catch (IOException e) { e.printStackTrace(); } } } }.start();

    另一鐘需要system權限,示例如下:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 new Thread() { public void run() { Process process = null; InputStream in = null; try { // 請求root process = Runtime.getRuntime().exec("pm install -r " + currentTempFilePath + "\n"); in = process.getInputStream(); int len = 0; byte[] bs = new byte[256]; while (-1 != (len = in.read(bs))) { String state = new String(bs, 0, len); if (state.equals("Success\n")) { //安裝成功後的操作 } } } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (in != null) { in.close(); } } catch (IOException e) { e.printStackTrace(); } } } }.start();

    關於system權限的獲取在介紹push方式的安裝時已做介紹。上面的代碼只給出了比較核心的部分,在實際實現中,對返回結果的處理同樣重要。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved