欢迎转载,转载请注明:http://blog.csdn.net/zhgxhuaa
A. 静默安装/卸载
B. 秒装/秒卸载
C. 卸载应用保存数据
D. 系统内置应用卸载
E. 卸载后清除残留数据
下面就从这几个方面做一下分析介绍。
在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
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进行安装。
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
|
<
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方法进行安装。
adb命令pm是Pms的Shell客户端,通过pm可以进行包相关的一些操作,包括安装和卸载。pm命令的用法如下:
pm的代码实现在Pm.java中,如下:
@/frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java
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中包安装的方式后,接下来探讨一些如何实现”静默安装“。所谓静默安装即跳过安装界面和进度条,在不被用户察觉的情况下载后台安装。下面针对上面的三种安装方式分别来分析如何实现静默安装。
在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);
}
|
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) {
}
}
|
这种方法也有一定的限制:
其次,应用需要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
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();
}
}
|