本文首要内容
- 静默装置
- apk装置流程简析
- installd进程含义
最近工作上遇到静默装置相关的内容,顺便学习一下apk装置的常识
静默装置
静默装置是指apk无感装置,不需求用户确认。目前一般来说有两种办法能够完成:
- 类似adb install指令
- 运用PackageManager的installPackage接口,需求权限且是体系运用才行
榜首种办法的示例代码为:
public static int installSilent(String filePath) {
File file = new File(filePath);
if (filePath == null || filePath.length() == 0 || file == null || file.length() <= 0 || !file.exists() || !file.isFile()) {
return 1;
}
String[] args = { "pm", "install", "-r", filePath };
ProcessBuilder processBuilder = new ProcessBuilder(args);
Process process = null;
BufferedReader successResult = null;
BufferedReader errorResult = null;
StringBuilder successMsg = new StringBuilder();
StringBuilder errorMsg = new StringBuilder();
int result;
try {
process = processBuilder.start();
successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String s;
while ((s = successResult.readLine()) != null) {
successMsg.append(s);
}
while ((s = errorResult.readLine()) != null) {
errorMsg.append(s);
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (successResult != null) {
successResult.close();
}
if (errorResult != null) {
errorResult.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (process != null) {
process.destroy();
}
}
if (successMsg.toString().contains("Success") || successMsg.toString().contains("success")) {
result = 0;
} else {
result = 2;
}
Log.d("test-test", "successMsg:" + successMsg + ", ErrorMsg:" + errorMsg);
return result;
}
运用installPackage接口,是需求体系运用,且需求申明权限的
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
有一点需求留意,在装置中pms会检查另一种权限,类似于运用能否发通知,假如isUserRestricted回来为true,装置会失败,那么需求调用相关接口,重新设置下
if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
try {
if (observer != null) {
observer.onPackageInstalled("", INSTALL_FAILED_USER_RESTRICTED, null, null);
}
} catch (RemoteException re) {
}
return;
}
apk装置流程简析
PackageManager是一个抽象类,运用调用pm装置apk,这中间会发生跨进程调用,因为pms是运行在system进程中的。
为了更方便用户调用,所以Android封装了pm类供用户调用。在ContextImpl中,获取pm,实质上是获得了pm的完成类,ApplicationPackageManager。
检查pm的installPackage类
public abstract void installPackage(
Uri packageURI, PackageInstallObserver observer,
int flags, String installerPackageName);
检查pm的子类ApplicationPackageManager,发现最终将调用pms的installPackage办法
public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,
int installFlags, String installerPackageName, VerificationParams verificationParams,
String packageAbiOverride, int userId) {
//权限检查,INSTALL_PACKAGES权限及其它权限检查,权限并不只有一种
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);
final int callingUid = Binder.getCallingUid();
enforceCrossUserPermission(callingUid, userId, true, true, "installPackageAsUser");
if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
try {
if (observer != null) {
observer.onPackageInstalled("", INSTALL_FAILED_USER_RESTRICTED, null, null);
}
} catch (RemoteException re) {
}
return;
}
//运用各参数,构建InstallParams,发送msg,交由handler处理
final File originFile = new File(originPath);
final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile);
final Message msg = mHandler.obtainMessage(INIT_COPY);
msg.obj = new InstallParams(origin, observer, installFlags,
installerPackageName, verificationParams, user, packageAbiOverride);
mHandler.sendMessage(msg);
}
installPackageAsUser办法中首要完成两件工作,1是权限检查,2是构建InstallParams,然后发送INIT_COPY的msg。
此处比较有意思,因为pms是跨进程调用,所以installPackageAsUser办法运行在binder线程中,为什么还要将耗时的工作交给mHandler(mHandler在pms的构造办法中初始化,也是运行在一个单独的线程中,不是主线程)呢?
客户端调用binder服务端办法,客户端是会堵塞的,假如服务端的办法耗时较长,不利于客户端的流畅性。所以尽管服务端自己的主线程没问题,但客户端有问题,个人也经常这样考虑,运用handler处理,handler是异步,彻底不会堵塞。不知道android的设计人员是不是也这样考虑的
void doHandleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
HandlerParams params = (HandlerParams) msg.obj;
int idx = mPendingInstalls.size();
//假如没有绑定IMediaContainerService服务,则先绑定服务
if (!mBound) {
//绑定服务
if (!connectToService()) {
params.serviceError();
return;
} else {
//绑定服务成功后,将params增加到mPendingInstalls行列当中
mPendingInstalls.add(idx, params);
}
} else {
mPendingInstalls.add(idx, params);
if (idx == 0) {
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
break;
}
}
}
在INIT_COPY的音讯处理中,先检查当前有没有绑定IMediaContainerService服务,当服务绑定成功的时候
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceConnected");
IMediaContainerService imcs =
IMediaContainerService.Stub.asInterface(service);
mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs));
}
mHandler将发送MCS_BOUND音讯,同时也会将params增加到mPendingInstalls行列当中。
mPendingInstalls增加元素比较有意思,先检查mPendingInstalls的size,然后在size方位增加新元素,当元素运用完今后,则删除0方位上的元素,这就保证了先入先出。
void doHandleMessage(Message msg) {
switch (msg.what) {
case MCS_BOUND: {
if (mContainerService == null) {
//出錯
} else if (mPendingInstalls.size() > 0) {
HandlerParams params = mPendingInstalls.get(0);
if (params != null) {
if (params.startCopy()) {
// 删除现已完成的使命
if (mPendingInstalls.size() > 0) {
mPendingInstalls.remove(0);
}
if (mPendingInstalls.size() == 0) {
if (mBound) {
//假如使命行列中没有使命了,则发送MCS_UNBIND,解绑服务
removeMessages(MCS_UNBIND);
Message ubmsg = obtainMessage(MCS_UNBIND);
sendMessageDelayed(ubmsg, 10000);
}
} else {
//假如使命行列中还有新使命,则继续发送MCS_BOUND音讯,循环履行新音讯
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
}
}
break;
}
}
}
MCS_BOUND音讯的处理中,调用startCopy办法,完成apk的复制。
final boolean startCopy() {
boolean res;
try {
if (++mRetries > MAX_RETRIES) {
//假如重试超过三次,则直接抛弃
mHandler.sendEmptyMessage(MCS_GIVE_UP);
handleServiceError();
return false;
} else {
//真正开始复制的当地
handleStartCopy();
res = true;
}
} catch (RemoteException e) {
mHandler.sendEmptyMessage(MCS_RECONNECT);
res = false;
}
//复制完成后后续事宜
handleReturnCode();
return res;
}
继续检查handleStartCopy办法。
之前绑定的服务,在此处首要有两个功能,一是解析apk中的基本信息,比如包名、版本号、装置方位等
pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags,
packageAbiOverride);
另外则是履行复制。当复制履行完今后,handleStartCopy办法也就告一段落了。我们继续看handleReturnCode办法
private void processPendingInstall(final InstallArgs args, final int currentStatus) {
// Queue up an async operation since the package installation may take a little while.
mHandler.post(new Runnable() {
public void run() {
mHandler.removeCallbacks(this);
// Result object to be returned
PackageInstalledInfo res = new PackageInstalledInfo();
res.returnCode = currentStatus;
res.uid = -1;
res.pkg = null;
res.removedInfo = new PackageRemovedInfo();
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
//预装置
args.doPreInstall(res.returnCode);
synchronized (mInstallLock) {
//装置
installPackageLI(args, res);
}
//完毕装置
args.doPostInstall(res.returnCode, res.uid);
}
if (!doRestore) {
//假如完成装置的msg,package add的广播将在此处发送
Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
mHandler.sendMessage(msg);
}
}
});
}
如上所示,最重要的4个步骤现已标明,预装置,装置运用以及完成装置,并发送package add等
其中doPreInstall和doPostInstall办法较简单,不再复述,首要检查installPackageLI办法。
installPackageLI办法十分长,它需求验证apk的签名文件,并且具体解析apk中的一切activity、service等信息并加以保存,办法十分十分的长
//搜集签名并验证
try {
pp.collectCertificates(pkg, parseFlags);
pp.collectManifestDigest(pkg);
} catch (PackageParserException e) {
res.setError("Failed collect during installPackageLI", e);
return;
}
//具体解析apk
PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanFlags,
System.currentTimeMillis(), user);
代码实在是太长了,读起来十分十分累,今后再具体解析
当handleReturnCode也完成后,mHandler将处理POST_INSTALL音讯,完成装置,发送package add 广播
installd进程含义
这一小节将彻底是个人的猜想,首先pms是运行在system的进程中的,而android中运用system的uid,并没有拜访运用程序目录的权限(不能拜访/data/data/包名 目录)。所以在pms之外还有一个进程存在,installd,它的代码方位是:
/frameworks/native/cmds/installd/installd.cpp
它也有install或许odex优化的办法,但在install办法中只看到创建data/data/包名 等目录,并没有其它操作。
这点需求后续去验证
installd与java端的installer类以socket通信。