无论是在Java或者Android中执行命令行语句殊途同归都是创建一个子进程执行调用可执行文件执行命令,类似于Windows中的CMD一样。
此时你有两种方式执行: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
希望大家多多迁移我的项目,该类库中还带有一个自己开发的完整的日志系统,以后还会加入其他东西,比如图片处理相关(模糊等)