此时你有两种方式执行: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(); }
<span style="white-space:pre"> </span>/** * 杀掉自己 * * @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; }采用回调,就是为了完成任务执行的方法!
独立进程服务接口:ICommandInterface.aidl
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(); } } }
好了本次采用暴力方式快要结束了,对于执行命令参数可以说是比较完美的执行方式了,采用这样的方式我执行Ping测试达到10万次,并发达到500个任务同时执行没有问题,测试的设备为:魅族M9.
本次的代码我已经打包到自己的类库中,并开源到GitHub;地址:Genius-Android
希望大家多多迁移我的项目,该类库中还带有一个自己开发的完整的日志系统,以后还会加入其他东西,比如图片处理相关(模糊等)