作者: 夏至,欢迎转载,也请保留这份申明。
先看效果:
静默安装的思路就是一个,就是用 adb install -r (apk路径) 或者 pm install -r (apk路径),用这种方式安装apk,是不会提示用户安装界面,所以,我们的思路就是在代码实现上诉的命令即可。
不过, 上诉命令需要 root 权限,然而,很多手机在出厂的时候,是做了定制的,即很多 root 是不开放的;而这个时候,我们就需要做判断了。
//获取超级权限
Process process = Runtime.getRuntime().exec("su");
dataOutputStream = new DataOutputStream(process.getOutputStream());
dataOutputStream.flush();
dataOutputStream.close();
int value = process.waitFor();
如果你在测试上面代码的时候,发现可以弹出以下的图片的时候,则表示你的手机是可以被root的,即我们可以以第三方应用的方式,实现静默安装。
但大部分时候,我们用上面的命令去测试的时候,会提示 “su: uid 10032 not allowed to su” 这样的错误
为什么会这样呢,首先,我们从源码上看, /system/extras/su.c 中有这样一段话:
if (myuid != AID_ROOT && myuid != AID_SHELL) {
fprintf(stderr,"su: uid %d not allowed to su\n", myuid);
return 1;
}
可以看到,只有 uid 为 root 或者 shell 的时候,才会有权限去执行我们的 su 指令。
那么,这里就有个办法了,就是把上面那段话屏蔽掉;然后用 mmm 编译的方式,把新生成的 su 替换掉原先的 /system/xbin/su 就可以了。
然后,如果这时,有点平台就可以了,有点平台则继续会提示 我们这个进程没有权限。那怎么办?
别急,我们只要将上面的 “su” 换成 “sh” ,然后把该命令换成 系统应用就可以了。所以,我们测试平台的代码应该如下:
/**
* 判断系统是否拥有su权限或者sh权限
* @return
*/
private boolean hasRootRight(){
if (new File("/system/xbin/su").exists() || new File("/system/bin/su").exists()){
DataOutputStream dataOutputStream = null;
try {
//获取超级权限
Process process = Runtime.getRuntime().exec("su");
dataOutputStream = new DataOutputStream(process.getOutputStream());
dataOutputStream.flush();
dataOutputStream.close();
int value = process.waitFor();
if (value != 0) {// 0 表示拥有 su 权限,如果没有 su 权限
//获取 sh 权限,注意,如果没有su权限,则用sh的时候,则需要系统应用才可行,即在 /system/app下
process = Runtime.getRuntime().exec("sh");
dataOutputStream = new DataOutputStream(process.getOutputStream());
dataOutputStream.flush();
dataOutputStream.close();
value = process.waitFor();
}
return returnResult(value);
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
然后,我们的安装代码如下:
/**
*
* @param apkPath
* @return
*/
public void install(final String apkPath, final InstallListener listener){
if (hasRootRight()){ //有权限则静默安装
new Thread(new Runnable() { //安装时耗时的,需要用线程
@Override
public void run() {
slientInstall(apkPath,listener);
}
}).start();
}else{ //没有权限则普通模式安装
File file = new File(apkPath);
if (!file.exists()){
return ;
}
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.fromFile(file),"application/vnd.android.package-archive");
mContext.startActivity(intent);
}
}
其中 slientInstall :
public void slientInstall(String apkPath,final InstallListener listener){
if (TextUtils.isEmpty(apkPath)){
listener.fail("请选择安装包");
return;
}
DataOutputStream dataOutputStream = null; //将Java基本数据类型写入数据输出流中
BufferedReader br = null;
try {
//获取超级权限,如果没有su权限,则用sh,但要把应用改成系统应用
Process process = Runtime.getRuntime().exec("su");
dataOutputStream = new DataOutputStream(process.getOutputStream());
//执行 pm slientInstall -r (apk路径)指令
String common = "pm install -r "+apkPath+"\n";
dataOutputStream.write(common.getBytes(Charset.forName("utf-8"))); //保持统一,用utf-8类型
dataOutputStream.flush();
dataOutputStream.writeBytes("exit\n");
dataOutputStream.flush();
process.waitFor(); //等待安装完毕
//获取失败的参数
br = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String msg = null;
String line;
while((line = br.readLine()) != null ){
msg += line;
}
Log.d("zsr", "slientInstall: "+msg);
if (msg.contains("Failure") || msg.contains("su")||msg.contains("kill")){
final String finalMsg = msg;
mHandler.post(new Runnable() { //这里用 ui线程,这样,就可以在 callback更新UI 了
@Override
public void run() {
listener.fail(finalMsg);
}
});
} else{
mHandler.post(new Runnable() {
@Override
public void run() {
listener.success();
}
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (dataOutputStream != null){
dataOutputStream.close();
}
if (br != null){
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
主函数的安装函数如下:
public void install(View view){
// Toast.makeText(this, "有root权限", Toast.LENGTH_SHORT).show();
mProgressBar.setVisibility(View.VISIBLE);
mSlientInstall.install(apkPath, new InstallListener() {
@Override
public void success() {
mProgressBar.setVisibility(View.GONE);
Log.d(TAG, "install success ");
Toast.makeText(MainActivity.this, "安装成功", Toast.LENGTH_SHORT).show();
}
@Override
public void fail(String errormsg) {
Log.d(TAG, "fail: ");
mProgressBar.setVisibility(View.GONE);
Toast.makeText(MainActivity.this, "安装失败: "+errormsg, Toast.LENGTH_SHORT).show();
}
});
}
别忘记了再 androidmanifest.xml 加上:
android:sharedUserId="android.uid.system"
然后,用 adb push “apk路径” /system/app 下就可以了,如果提示没权限,你则需要用adb chmod 777 改变system和app的权限了。