our Android app spawns a logcat shell process and then reads its result for processing.
However, when the app stops (e.g. when restarted after recompilation during development), the logcat process will keep running. Here's an example of this behaviour:
processes = new ArrayList<Process>();
try {
processes.add(Runtime.getRuntime().exec("logcat -v time"));
processes.add(Runtime.getRuntime().exec("logcat -v time"));
processes.add(Runtime.getRuntime().exec("logcat -v time"));
processes.add(Runtime.getRuntime().exec("logcat -v time"));
} catch (IOException e) {
// oh no!
}
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
for (Process p : processes) {
p.destroy();
}
};
});
Add this to the onCreate() method of a test app, start it, then force-stop it using the settings manager. The child processes will keep running, now with a parent-id of 1.
In How to kill logcat process initiated by Android application? it was suggested to use ProcessBuilder, but that wasn't a solution and the process will keep running, too.
In Stop child process when parent process stops it was suggsted to use a shutdown hook - that doesn't work either as demonstrated above.
Thanks!
What you could do is spwaning another script of which the sole purpose is to watch your Java program. Whenever it dies, kill all of its children too.
A fragile example:
int pid = android.os.Process.myPid();
String script = "while [ -d /proc/" + pid + " ];do sleep 1;done; killall logcat";
Process p = Runtime.getRuntime().exec("/system/bin/sh", "-c", script);
This assumes that your process does not run as root, thereby only killing its own logcat processes. In your shutdown function, you should first kill the other processes (logcat) and then run p.destroy(); to stop this killer script.
The script above can be improved by removing the use of killall. Instead, get the process IDs of your logcat processes using Reflection (this answer points to http://www.golesny.de/p/code/javagetpid) and pass them to kill.
Related
Recently, I am trying Android sub process with Runtime.getRuntime().exec(command) and found that I can destroy a NodeJS http server but cannot destroy a Go http server.
for node and go binary, it is available from Termux;
node http server: https://github.com/stallpool/halfbase/tree/master/nodejs/tinyserver/index.js
go http server: https://github.com/stallpool/halfbase/blob/master/golang/tinyserver/main.go
For node sub process, it can be started in an Android Service and p.waitFor(); when it is time, it can be killed by p.destroy()
However, for go sub process, it can be started but cannot be killed by p.destroy() even p. destroyForcibly(); in this article https://medium.com/honestbee-tw-engineer/gracefully-shutdown-in-go-http-server-5f5e6b83da5a , it makes sure a go server can be closed gracefully and I tried it but p.destroy() still does not work.
It is appreciated if anyone can light me a way to kill the process. thx!
just figured out a hack way; not elegant; guide me to a better solution if any!
Log.d("AppDebug", p.javaClass.getName())
// from above log
// we can know Android use "java.lang.UNIXProcess" as implementation of java.lang.Process
// to make sure the sub process is killed eventually
if (p.isAlive()) {
val klass = p.javaClass
if (klass.getName().equals("java.lang.UNIXProcess")) {
Log.d("AppDebug", "force terminate sub process ..")
try {
val f = klass.getDeclaredField("pid");
f.setAccessible(true);
val pid = f.getInt(p);
// XXX: buggy here, if getInt throw an error, the filed is exposed!
f.setAccessible(false);
android.os.Process.killProcess(pid);
Log.d("AppDebug", "force terminating done.")
} catch (e: Exception) {
Log.d("AppDebug", "force terminating failed.")
}
} else {
Log.d("AppDebug", "force terminating not supported.")
}
}
sorry for my misleading. currently I totally understand why my go server cannot be killed after I add some log about ps -ef before/after kill the process.
actually I use go run main.go to start the server; however, go run main.go will compile the code and generate a binary file in tmp folder; then it will spawn a child process (execute the binary); when I did p.destroy(), it merely kill the go process but the child server process remains there.
the correct solution is, get pid like above code; and use ps -o pid= --ppid=<pid> to get children tree and kill all processes for a cleanup.
I'm running a native background process from an android app with Runtime.exec().
It seems like every few seconds the process is put to sleep for like 200 ms.
It looks like the android operating system is just starving that process.
Is there some way I can change the process priority from a background process to some other type of priority?
Assume the application that's running the process can get super user access.
Edit:
The command I'm using to run the process:
String fullCommand = "export LD_LIBRARY_PATH=LD_LIBRARY_PATH:. && ./MyProc";
Runtime.getRuntime().exec(new String[]{"su", "-c ", fullCommand });
If you know the PID of the process, you can change its priority with renice
(taken from this answer).
You can call renice like this:
renice priority [[-p] pid ...] [[-g] pgrp ...] [[-u] user ...]
Trying using the .setPriority() method.
Thread t=new Thread();
t.setPriority(Thread.MAX_PRIORITY);
I need my app to perform some su commands programatically (phone is rooted).
When done using adb, the commands work.
For instance:
su -c "mkdir /sdcard/testdir" creates a directory called "testdir" in /sdcard.
When I call:
p = Runtime.getRuntime().exec("su -c \"mkdir /sdcard/testdir\"");
p.waitFor();
It just moves on and no change happens.
I tried reading the input:
DataInputStream dis = new DataInputStream(p.getInputStream());
while((temp = dis.readLine())!=null)
Log.d(ctx.TAG,"shell:"+temp);
But it reports nothing (loop does 0 iterations).
Has anyone ever faced this issue before? How can it be solved?
Needless to day, non-su commands do work programatically with this method.
Note: I gave mkdir as an example (I know it doesn't necessarily require su). I need a lot of varied commands to be performed under su
Thank you!
EDIT: when I call su -c "id" programatically, there's output that uid=0.
I can get stuck on a problem for days, and the moment I gather up the courage to ask about it on StackOverflow, it is solved within minutes.
The fix is:
p=Runtime.getRuntime().exec("su");
DataOutputStream dos = new DataOutputStream(p.getOutputStream());
dos.writeBytes("mkdir /sdcard/testdir\n");
dos.writeBytes("exit\n");
dos.flush();
dos.close();
p.waitFor();
Don't forget \n at the end of each command you write to the DataOutputStream, as it will not work without it.
You wrote that you "need varied commands to be performed under su". Note that the use of "Runtime.exec()" is discouraged by Chainfire, the developer of the most famous SuperSU root app.
It is tempting to use Runtime.getRuntime().exec("su -c [command]");, but you should be aware that [command] should be a single parameter, and thus may require quoting. Unfortunately both quoting the [command] parameter as well as passing the paramaters as separate variables to either Runtime.exec() or ProcessBuilder does not work consistently across all Android versions, and thus this construct should be avoided entirely. It is not impossible to do this right - but there's a high risk of problems.
See the How to SU Document. So you might want to follow his recommendation here:
3.2. Making the call
A common method to call su that avoids the known issues listed above is by creating an interactive shell and piping commands to it. This is done by calling Runtime.getRuntime().exec("su");, and retrieving input and output streams from the returned Process object. Doing this is a fairly straight-forward piece of code, but including the debug logs and checks it's a bit long to reproduce here.
The core code is located here: [libsuperuser :: Shell.java # GitHub]. Shell.run() is a generic call to run shell code, the following more specific (static) utility functions are the ones you will probably end up using:
List<String> Shell.SH.run(String command)
List<String> Shell.SH.run(List<String> commands)
List<String> Shell.SH.run(String[] commands)
List<String> Shell.SU.run(String command)
List<String> Shell.SU.run(List<String> commands)
List<String> Shell.SU.run(String[] commands)
The SH variants are used for a non-root shell, where the SU variants are used for a root shell. These calls return a List containing the output of the shell commands. If there was no output, the list is empty, but not null. The result is only null in case an error occured - including the user not granting your app su access. These are blocking calls.
Note that in debug compiles, all shell STDIN/STDOUT/STDERR will be logged to logcat, and these calls will (intentionally) crash your app if called from the main thread. The reason for this will be discussed in section 4. When to call su.
If you use double quotes, it will work:
su -c ""command with args""
You might be calling Runtime.getRuntime().exec() in main thread and p.waitFor() makes your main thread wait until it executes. Try calling in another thread, like the following snippet.
new Thread(){
#override
public void run(){
p = Runtime.getRuntime().exec("su -c \"mkdir /sdcard/testdir\"");
p.waitFor();
}.start();
}
My app uses a process in separate thread to run some commands and get the input from them:
process = Runtime.getRuntime().exec("su");
out = new DataOutputStream(process.getOutputStream());
The app sends commands to the process like this:
public void setCommands(String[] commands)
{
try{
for(String command : commands){
out.writeBytes(command + "\n");
}
out.writeBytes("exit\n"); //if I comment this line the commands get lost
out.flush();
}catch(IOException e){
e.printStackTrace();
}
}
The thread then reads the input from process with BufferedReaders and sends it to the main thread and it works fine for the first time. The problem is that I want to reuse the same process with multiple calls to setCommands(), but after the first call the OutputStream of the process gets closed with out.writeBytes("exit\n"); statement. If I comment this line it seems like the out.flush() starts to have no effect. Could somebody please explain to me why is this happening and how can this be done right?
First thing my app does is checking for "su" since it's necessary for the app to work. Even though it sometimes work, often after typing "killall packageName" in the terminal. I've done a simple test application and I can't get it to work every time.
Code where it happens:
String[] args = new String[] { "su" };
Log.v(TAG, "run(" + Arrays.toString(args) + ")");
FutureTask<Process> task = new FutureTask<Process>(new Callable<Process>() {
#Override
public Process call() throws Exception {
return Runtime.getRuntime().exec(args);
}
});
try {
Executors.newSingleThreadExecutor().execute(task);
return task.get(10, TimeUnit.SECONDS);
} catch (Throwable t) {
task.cancel(true);
throw new IOException("failed to start process within 10 seconds", t);
}
Complete project: https://github.com/chrulri/android_testexec
Since this app does nothing more than running exec() in the first place, I cannot close any previously opened file descriptors as mentioned in another stackoverflow question: https://stackoverflow.com/a/11317150/1145705
PS: I run Android 4.0.3 / 4.0.4 on different devices.
3c71 was right about open file descriptors. In my case, it was the AdMob SDK which caused the problems since it was sometimes (re-)loading the Ads from the web at the sime time I tried to call exec(..) leaving me hanging in a deadlock.
My solution is to fork a "su" process ONCE and reuse it for all commands and load the Ads AFTER forking that process.
To use Runtime.exec safely you should wait for the process to finish and consume the output and error streams, preferably concurrently (to prevent blocking):
http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html