此时你有两种方式执行:ProcessBuilder与Runtime;两种创建方式各有千秋,至于区别详见:[Java][Android][Process] ProcessBuilder与Runtime区别
在Android中创建子进程执行命令的时候有着一定的限制:
1.JVM提供的内存有限。
2.底层缓冲区间大小有限。
3.在高并发情况下容易造成阻塞。
基于上几点在执行命令行时我们不得不谨慎操作,不能随便创建。
在上一篇文章中我提到:[Java][Android][Process] Process 创建+控制+分析 经验浅谈 了一些我的管理方式已经对实现的分析;其归根结底为:创建一个子进程的时候同时创建一个线程用于读取输出的流信息,在返回后及时退出;图示:
通过以上的控制方式能有效的解决掉底层ProcessManager线程死掉情况(出现等待IO缓冲区情况),当此线程死掉后子进程也将进入等待且永不退出。通过这样的情况能达到执行上万条命令不出现问题。但是经过我半个月的观察发现其并不是最稳定的方式,当程序中还有很多其他IO操作如(文件读写,网络请求等)出现的时候;我执行到接近2万条命令行后出现了同样的问题。
查询后得出,虽然我们启动了线程来读取多余的流数据,但是当线程很多或者请求很多的时候线程可能来不及立即启动,而此时IO却已经满了,那么将会进入IO临界区等待,也就是说线程其实不是万能的。庆幸的是在Android中有这样一种机制:服务。
Android服务是什么?我不多说百度一下就知道!
现在来说说我的新思路:
首先咱们创建一个服务,并设置服务为独立进程:android:process=".command.CommandService"
然后把实现部分放在我们开启的服务中,使用一个类来控制服务启动以及调用服务做任务,其任务就是把上面的部分放在服务中控制实现,如图:
这样看来是不是没有区别?其实不然,区别已经来了,第一由于是独立进程的服务,所以会有独立的JVM空间,同时该进程的IO与主线程IO不是同一个(逻辑上);所以如果在主进程中操作其他的流并不影响独立进程的流。
并且有一个特别重要的情况:当独立服务进程出现死掉(IO)等待情况,这时服务中的守护进程将会自动杀掉自己;然后等待重新启动后继续执行任务。
实现代码:
public CommandServiceImpl() {
//线程初始化
thread = new Thread(CommandServiceImpl.class.getName()) {
@Override
public void run() {
while (thread == this && !this.isInterrupted()) {
if (commandExecutors != null && commandExecutors.size() > 0) {
lock.lock();
LogUtil.i(TAG, "Executors Size:" + commandExecutors.size());
for (CommandExecutor executor : commandExecutors) {
if (executor.isTimeOut())
try {
killSelf();
} catch (RemoteException e) {
e.printStackTrace();
}
if (thread != this && this.isInterrupted())
break;
}
lock.unlock();
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thread.setDaemon(true);
thread.start();
}
/**
* 杀掉自己
*
* @throws RemoteException
*/
@Override
public void killSelf() throws RemoteException {
android.os.Process.killProcess(android.os.Process.myPid());
}
/**
* 执行命令
*
* @param params 命令
* @return 结果
* @throws RemoteException
*/
@Override
public String command(String params) throws RemoteException {
CommandExecutor executor = CommandExecutor.create(params);
lock.lock();
commandExecutors.add(executor);
lock.unlock();
String result = executor.getResult();
lock.lock();
commandExecutors.remove(executor);
lock.unlock();
return result;
}
此时由于服务杀掉自己没法在内存中保存当前队列任务,那任务是否就丢弃掉呢?这肯定是不允许的,我们没法在服务中控制但是可以在控制任务的:
代码如下:
package net.qiujuer.libraries.genius.command;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import net.qiujuer.libraries.genius.journal.LogUtil;
import net.qiujuer.libraries.genius.utils.GlobalValue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by Genius on 2014/8/13.
* 命令执行Model
*/
public class CommandModel {
private static final String TAG = CommandModel.class.getName();
//调用服务接口
private static ICommandInterface iService = null;
//服务链接类,用于实例化服务接口
private static ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iLock.lock();
iService = ICommandInterface.Stub.asInterface(service);
if (iService != null) {
try {
iCondition.signalAll();
} catch (Exception e) {
e.printStackTrace();
}
} else
bindService();
iLock.unlock();
LogUtil.i(TAG, "onServiceConnected");
}
@Override
public void onServiceDisconnected(ComponentName name) {
iService = null;
LogUtil.i(TAG, "onServiceDisconnected");
}
};
//锁
private static Lock iLock = new ReentrantLock();
//等待与唤醒
private static Condition iCondition = iLock.newCondition();
//执行参数
private String parameter;
//是否取消测试
private boolean isCancel;
/**
* 实例化
*
* @param params @param params 命令参数 eg: "/system/bin/ping", "-c", "4", "-s", "100","www.qiujuer.net"
*/
public CommandModel(String... params) {
//check params
if (params == null)
throw new NullPointerException();
//run
StringBuilder sb = new StringBuilder();
for (String str : params) {
sb.append(str);
sb.append(" ");
}
this.parameter = sb.toString();
}
/**
* 执行测试
*
* @param model ProcessModel
* @return 结果
*/
public static String command(CommandModel model) {
//检测是否取消测试
if (model.isCancel)
return null;
//check Service
if (iService == null) {
iLock.lock();
try {
iCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
iLock.unlock();
}
String result;
try {
result = iService.command(model.parameter);
} catch (Exception e) {
e.printStackTrace();
bindService();
result = command(model);
}
return result;
}
/**
* 启动并绑定服务
*/
private static void bindService() {
Context context = GlobalValue.getContext();
Intent intent = new Intent(context, CommandService.class);
context.startService(intent);
context.bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
/**
* 取消测试
*/
public void cancel() {
isCancel = true;
}
/**
* 静态初始化
*/
static {
bindService();
}
}
其中:
public static String command(CommandModel model) {
//检测是否取消测试
if (model.isCancel)
return null;
//check Service
if (iService == null) {
iLock.lock();
try {
iCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
iLock.unlock();
}
String result;
try {
result = iService.command(model.parameter);
} catch (Exception e) {
e.printStackTrace();
bindService();
result = command(model);
}
return result;
}
采用回调,就是为了完成任务执行的方法!
interface ICommandInterface {
void killSelf();
String command(String params);
}
命令执行者(服务中的实际功能实现):CommandExecutor.java
package net.qiujuer.libraries.genius.command;
import net.qiujuer.libraries.genius.journal.LogUtil;
import net.qiujuer.libraries.genius.utils.ToolUtil;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by Genius on 2014/8/13.
* 命令行执行命令
*/
class CommandExecutor {
private static final String TAG = CommandExecutor.class.getName();
//换行符
private static final String BREAK_LINE;
//错误缓冲
private static final byte[] BUFFER;
//缓冲区大小
private static final int BUFFER_LENGTH;
//创建进程时需要互斥进行
private static final Lock LOCK = new ReentrantLock();
//不能超过1分钟
private static final long TIMEOUT = 60000;
//ProcessBuilder
private static ProcessBuilder PRC;
final private Process process;
final private InputStream in;
final private InputStream err;
final private OutputStream out;
final private StringBuilder sbReader;
private BufferedReader bInReader = null;
private InputStreamReader isInReader = null;
private boolean isDone;
private long startTime;
/**
* 静态变量初始化
*/
static {
BREAK_LINE = "\n";
BUFFER_LENGTH = 128;
BUFFER = new byte[BUFFER_LENGTH];
LOCK.lock();
PRC = new ProcessBuilder();
LOCK.unlock();
}
/**
* 实例化一个CommandExecutor
*
* @param process Process
*/
private CommandExecutor(Process process) {
//init
this.startTime = System.currentTimeMillis();
this.process = process;
//get
out = process.getOutputStream();
in = process.getInputStream();
err = process.getErrorStream();
//in
if (in != null) {
isInReader = new InputStreamReader(in);
bInReader = new BufferedReader(isInReader, BUFFER_LENGTH);
}
sbReader = new StringBuilder();
//start read thread
Thread processThread = new Thread(TAG) {
@Override
public void run() {
startRead();
}
};
processThread.setDaemon(true);
processThread.start();
}
/**
* 执行命令
*
* @param param 命令参数 eg: "/system/bin/ping -c 4 -s 100 www.qiujuer.net"
*/
protected static CommandExecutor create(String param) {
String[] params = param.split(" ");
CommandExecutor processModel = null;
try {
LOCK.lock();
Process process = PRC.command(params)
.redirectErrorStream(true)
.start();
processModel = new CommandExecutor(process);
} catch (IOException e) {
e.printStackTrace();
} finally {
//sleep 100
ToolUtil.sleepIgnoreInterrupt(100);
LOCK.unlock();
}
return processModel;
}
/**
* 获取是否超时
*
* @return 是否超时
*/
protected boolean isTimeOut() {
return ((System.currentTimeMillis() - startTime) >= TIMEOUT);
}
//读取结果
private void read() {
String str;
//read In
try {
while ((str = bInReader.readLine()) != null) {
sbReader.append(str);
sbReader.append(BREAK_LINE);
}
} catch (Exception e) {
String err = e.getMessage();
if (err != null && err.length() > 0) {
LogUtil.e(TAG, "Read Exception:" + err);
}
}
}
/**
* 启动线程进行异步读取结果
*/
private void startRead() {
//while to end
while (true) {
try {
process.exitValue();
//read last
read();
break;
} catch (IllegalThreadStateException e) {
read();
}
ToolUtil.sleepIgnoreInterrupt(50);
}
//read end
int len;
if (in != null) {
try {
while ((len = in.read(BUFFER)) > 0) {
LogUtil.d(TAG, "Read End:" + len);
}
} catch (IOException e) {
String err = e.getMessage();
if (err != null && err.length() > 0)
LogUtil.e(TAG, "Read Thread IOException:" + err);
}
}
//close
close();
//destroy
destroy();
//done
isDone = true;
}
/**
* 获取执行结果
*
* @return 结果
*/
protected String getResult() {
//until startRead en
while (!isDone) {
ToolUtil.sleepIgnoreInterrupt(200);
}
//return
if (sbReader.length() == 0)
return null;
else
return sbReader.toString();
}
/**
* 关闭所有流
*/
private void close() {
//close out
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//err
if (err != null) {
try {
err.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//in
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (isInReader != null) {
try {
isInReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bInReader != null) {
try {
bInReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 销毁
*/
private void destroy() {
String str = process.toString();
try {
int i = str.indexOf("=") + 1;
int j = str.indexOf("]");
str = str.substring(i, j);
int pid = Integer.parseInt(str);
try {
android.os.Process.killProcess(pid);
} catch (Exception e) {
try {
process.destroy();
} catch (Exception ex) {
ex.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
本次的代码我已经打包到自己的类库中,并开源到GitHub;地址:Genius-Android
希望大家多多迁移我的项目,该类库中还带有一个自己开发的完整的日志系统,以后还会加入其他东西,比如图片处理相关(模糊等)