編輯:關於Android編程
Android通過PackageManagerService(後面簡稱Pms)進行包管理,其主要功能包括:用戶ID分配、包解析、包的安裝卸載等。本文不對Pms進行分析,主要目的是探討一下包安裝。在本文中主要探討包安裝的相關操作,卸載作為安裝的逆過程,實現類似,不再贅述。
在Android中APK的安裝有三種方式:
@/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 20public 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 7public 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 71public 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進行安裝。
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 21public 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 32public 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方法進行安裝。
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 57private 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中包安裝的方式後,接下來探討一些如何實現”靜默安裝“。所謂靜默安裝即跳過安裝界面和進度條,在不被用戶察覺的情況下載後台安裝。下面針對上面的三種安裝方式分別來分析如何實現靜默安裝。
在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 9package android.content.pm;
/**
* API for installation callbacks from the Package Manager.
* @hide
*/
oneway interface IPackageInstallObserver {
void packageInstalled(in String packageName, int returnCode);
}
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) {
}
}
這種方法也有一定的限制:
其次,應用需要system權限。
在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 39new 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 32new 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方式的安裝時已做介紹。上面的代碼只給出了比較核心的部分,在實際實現中,對返回結果的處理同樣重要。
Mac系統Android M源碼編譯並導入Android Studio查看
一. Mac OS X(10.11.4)編譯環境設置1.1 創建大小寫敏感的磁盤鏡像可以通過磁盤管理工具進行設置,也可以通過以下命令生成70g的鏡像文件sudo hdiu
Android之懶人框架 ButterKnife 8.4添加使用
ButterKnife是一個專注於Android系統的View注入框架,可以減少大量的findViewById以及 setOnClickListener代碼,可視化一鍵生
Android入門之Style與Theme用法實例解析
就目前的互聯網發展來看,已經有越來越多互聯網企業都在Android平台上部署其客戶端,並且為了提升用戶體驗,這些客戶端都做得布局合理而且美觀。本文所要介紹的Android
我的Android進階之旅------)Android Activity的singleTask加載模式和onActivityResult方法之間的沖突
今天調試一個bug的時候,情景如下:一個Activity A,需要用startActivityForResult方法開啟Activity B。Activity B的lau