背景
工作这么多年了,我常看到我身旁的程序员在弄需要访问一个文件夹启动个su的shell,chmod 777 somefolder,开放某个文件夹的读写权限。功能是实现了,但是想想,当执行root操作时,super user之类的防御软件弹出对话框警示,而用户选择了信任yourApp后,你把手机中某个文件夹的权限更改为777,然后任何其他app都可以无障碍地访问一些不该访问的文件时,你就不会觉得内疚?在很短的时间内更改某一个特定的文件的权限,访问完成后改回来,或许还可以接受,但是整个文件夹777,那也太夸张了。
其实只要在后台启动个shell,运行su,然后在这个shell上输入你的命令行就ok了。更何况已经有现成的库,有简单易懂的demo了。libsuperuser( http://su.chainfire.eu/)就是这样的一个库。
使用方式
方式一:普通的后台操作
List<String> Shell.SH.run(String command)
List<String> Shell.SH.run(List<String> commands)
List<String> Shell.SH.run(String[] commands)
方式二:root型操作
List<String> Shell.SU.run(String command)
List<String> Shell.SU.run(List<String> commands)
List<String> Shell.SU.run(String[] commands)
方式三:
Shell.Interactive.addCommand系列函数,支持回调函数OnCommandResultListener和OnCommandLineListener
private void sendRootCommand() {
rootSession.addCommand(new String[] { "id", "date", "ls -l /" }, 0,
new Shell.OnCommandResultListener() {
public void onCommandResult(int commandCode, int exitCode, List<String> output) {
if (exitCode < 0) {
reportError("Error executing commands: exitCode " + exitCode);
} else {
updateResultStatus(true, output);
appendLineToOutput("----------");
appendLineToOutput("ls -l /");
}
}
});
使用时注意要在辅助线程中调用,比如AsyncTask,再比如IntentService
功能实现的核心在下面代码段
public static List<String> run(String shell, String[] commands, String[] environment,
boolean wantSTDERR) {
...
// setup our process, retrieve STDIN stream, and STDOUT/STDERR
// gobblers
Process process = Runtime.getRuntime().exec(shell, environment);
DataOutputStream STDIN = new DataOutputStream(process.getOutputStream());
StreamGobbler STDOUT = new StreamGobbler(shellUpper + "-", process.getInputStream(),
res);
StreamGobbler STDERR = new StreamGobbler(shellUpper + "*", process.getErrorStream(),
wantSTDERR ? res : null);
// start gobbling and write our commands to the shell
// STDOUT和STDERR是两个工作线程,用来后台进程的输出
STDOUT.start();
STDERR.start();
try {
for (String write : commands) {
Debug.logCommand(String.format("[%s+] %s", shellUpper, write));
STDIN.write((write + "\n").getBytes("UTF-8"));
STDIN.flush();
}
STDIN.write("exit\n".getBytes("UTF-8"));
STDIN.flush();
} catch (IOException e) {
if (e.getMessage().contains("EPIPE")) {
// method most horrid to catch broken pipe, in which case we
// do nothing. the command is not a shell, the shell closed
// STDIN, the script already contained the exit command, etc.
// these cases we want the output instead of returning null
} else {
// other issues we don‘t know how to handle, leads to
// returning null
throw e;
}
}
// wait for our process to finish, while we gobble away in the
// background
process.waitFor();
// make sure our threads are done gobbling, our streams are closed,
// and the process is destroyed - while the latter two shouldn‘t be
// needed in theory, and may even produce warnings, in "normal" Java
// they are required for guaranteed cleanup of resources, so lets be
// safe and do this on Android as well
try {
STDIN.close();
} catch (IOException e) {
// might be closed already
}
STDOUT.join();
STDERR.join();
process.destroy();
// in case of su, 255 usually indicates access denied
if (SU.isSU(shell) && (process.exitValue() == 255)) {
res = null;
}
} catch (IOException e) {
// shell probably not found
res = null;
} catch (InterruptedException e) {
// this should really be re-thrown
res = null;
}
Debug.logCommand(String.format("[%s%%] END", shell.toUpperCase(Locale.ENGLISH)));
return res;
}
结语
我也碰到过一个同事在做应用市场app的时候,要实现“静默安装”的功能,apk下载完毕在后台静默安装app,就是也就是在su的shell中执行pm install及一些其它的辅助功能,然后捣整了好久才问我,为什么在DeviceA上行得通,在DeviceB上就不行了。我告诉他有成熟的实现方式了,回头一试,OK了。有成熟的实现方式就直接拿来用吧,或者阅读这些代码,自己提取关键代码。
原文:http://my.oschina.net/u/1445604/blog/517272