前几天分享了pc端的monkey可视化工具,今天来分享一下如何用android实现monkey的运行,原理是执行shell命令,各种传参,该工具需要root授权
先附上两个效果图:
以下为代码:
1.MainActivity代码如下
package com.example.administrator.monkeyshareblog; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { Button button,button1; EditText editText1,editText2,editText3,editText4,editText5,editText6,editText7; TextView button_package; private Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getSupportActionBar().hide(); setContentView(R.layout.activity_main); initView(); Bundle bundle=getIntent().getExtras(); try{ String str = bundle.getString("packagename"); Log.i("packagew",str); button_package.setText(str); }catch (NullPointerException e){ String text = "选择一个应用"; button_package.setText(text); } button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(MainActivity.this,NewActivity.class); startActivity(intent); } }); //点击跳转到包选择界面 button_package.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //ShowChoise(); Intent intent = new Intent(MainActivity.this,PackageManageActivity.class); startActivity(intent); } }); String times=editText6.getText().toString(); Log.i("111",times+"=========="); //执行monkey按钮 button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new MonkeyThread().start(); } }); } //初始化控件 private void initView() { button= (Button) findViewById(R.id.button); button1=(Button)findViewById(R.id.button1); editText1= (EditText) findViewById(R.id.ed_1); editText2= (EditText) findViewById(R.id.ed_2); editText3=(EditText)findViewById(R.id.ed_3); editText4=(EditText)findViewById(R.id.ed_4); editText5=(EditText)findViewById(R.id.ed_5); editText6=(EditText)findViewById(R.id.ed_6); editText7=(EditText)findViewById(R.id.ed_7); button_package=(TextView) findViewById(R.id.button_package); } //monkey执行线程 class MonkeyThread extends Thread{ @Override public void run() { super.run(); String swipePercent = editText1.getText().toString(); String clickPrcent = editText2.getText().toString(); String systemClickPercent = editText3.getText().toString(); String startActivityPercent = editText4.getText().toString(); String seedNum = editText5.getText().toString(); String delayTime = editText6.getText().toString(); String times = editText7.getText().toString(); if (swipePercent.equals("") == false) { swipePercent = " --pct-motion " + swipePercent; } else { swipePercent = ""; } if (clickPrcent.equals("") == false) { clickPrcent = " --pct-touch " + clickPrcent; } else { clickPrcent = ""; } if (systemClickPercent.equals("") == false) { systemClickPercent = " --pct-syskeys " + systemClickPercent; } else { systemClickPercent = ""; } if (startActivityPercent.equals("") == false) { startActivityPercent = " --pct-appswitch " + startActivityPercent; } else { startActivityPercent = ""; } if (seedNum.equals("") == false) { seedNum = " -s " + seedNum; } else { seedNum = ""; } if (delayTime.equals("") == false) { delayTime = " --throttle " + delayTime; } else { delayTime = ""; } if (times.equals("") == false) { times = " " + times; String command = "monkey -p " + button_package.getText() + delayTime + swipePercent + systemClickPercent + startActivityPercent + clickPrcent + seedNum + times; Log.i("monkeyarg", "======" + command); ShellUtils.execCommand(command, true); } else { Looper.prepare(); Toast toast = Toast.makeText(getApplicationContext(), "请输入monkey事件次数", Toast.LENGTH_SHORT); toast.show(); Looper.loop(); } } } }2.MainActivity布局如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:layout_width="match_parent" android:layout_height="50dp" android:id="@+id/button_package" android:gravity="center" android:text="选择应用" android:background="@drawable/textview_border" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/ed_1" android:inputType="number" android:hint="输入monkey滑动事件比例"/> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/ed_2" android:inputType="number" android:hint="输入monkey点击事件比例"/> <EditText android:layout_width="match_parent" android:id="@+id/ed_3" android:layout_height="wrap_content" android:inputType="number" android:hint="输入monkey系统按钮事件比例"/> <EditText android:layout_width="match_parent" android:id="@+id/ed_4" android:layout_height="wrap_content" android:inputType="number" android:hint="输入monkey的activity启动比例"/> <EditText android:layout_width="match_parent" android:id="@+id/ed_5" android:layout_height="wrap_content" android:inputType="number" android:hint="输入monkey随机种子数"/> <EditText android:layout_width="match_parent" android:id="@+id/ed_6" android:layout_height="wrap_content" android:inputType="number" android:hint="输入monkey延时时间"/> <EditText android:layout_width="match_parent" android:id="@+id/ed_7" android:layout_height="wrap_content" android:inputType="number" android:hint="输入monkey的事件数(必填)"/> <Button android:text="启动monkey" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/button" /> <Button android:text="说明" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/button1" /> </LinearLayout>3.在drawable下添加一个样式文件textview_border.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="#ffffff" /> <stroke android:width="1dip" android:color="#4fa5d5"/> </shape>4.shell命令执行工具类如下
package com.example.administrator.monkeyshareblog; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.List; /** * Created by Administrator on 2017/7/13. * shell执行工具类 */ public class ShellUtils { public static final String COMMAND_SU = "su"; public static final String COMMAND_SH = "sh"; public static final String COMMAND_EXIT = "exit\n"; public static final String COMMAND_LINE_END = "\n"; private ShellUtils() { throw new AssertionError(); } /** * check whether has root permission * * @return */ public static boolean checkRootPermission() { return execCommand("echo root", true, false).result == 0; } /** * execute shell command, default return result msg * * @param command command * @param isRoot whether need to run with root * @return * @see ShellUtils#execCommand(String[], boolean, boolean) */ public static CommandResult execCommand(String command, boolean isRoot) { return execCommand(new String[] {command}, isRoot, true); } /** * execute shell commands, default return result msg * * @param commands command list * @param isRoot whether need to run with root * @return * @see ShellUtils#execCommand(String[], boolean, boolean) */ public static CommandResult execCommand(List<String> commands, boolean isRoot) { return execCommand(commands == null ? null : commands.toArray(new String[] {}), isRoot, true); } /** * execute shell commands, default return result msg * * @param commands command array * @param isRoot whether need to run with root * @return * @see ShellUtils#execCommand(String[], boolean, boolean) */ public static CommandResult execCommand(String[] commands, boolean isRoot) { return execCommand(commands, isRoot, true); } /** * execute shell command * * @param command command * @param isRoot whether need to run with root * @param isNeedResultMsg whether need result msg * @return * @see ShellUtils#execCommand(String[], boolean, boolean) */ public static CommandResult execCommand(String command, boolean isRoot, boolean isNeedResultMsg) { return execCommand(new String[] {command}, isRoot, isNeedResultMsg); } /** * execute shell commands * * @param commands command list * @param isRoot whether need to run with root * @param isNeedResultMsg whether need result msg * @return * @see ShellUtils#execCommand(String[], boolean, boolean) */ public static CommandResult execCommand(List<String> commands, boolean isRoot, boolean isNeedResultMsg) { return execCommand(commands == null ? null : commands.toArray(new String[] {}), isRoot, isNeedResultMsg); } /** * execute shell commands * * @param commands command array * @param isRoot whether need to run with root * @param isNeedResultMsg whether need result msg * @return <ul> * <li>if isNeedResultMsg is false, {@link CommandResult#successMsg} is null and * {@link CommandResult#errorMsg} is null.</li> * <li>if {@link CommandResult#result} is -1, there maybe some excepiton.</li> * </ul> */ public static CommandResult execCommand(String[] commands, boolean isRoot, boolean isNeedResultMsg) { int result = -1; if (commands == null || commands.length == 0) { return new CommandResult(result, null, null); } Process process = null; BufferedReader successResult = null; BufferedReader errorResult = null; StringBuilder successMsg = null; StringBuilder errorMsg = null; DataOutputStream os = null; try { process = Runtime.getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH); os = new DataOutputStream(process.getOutputStream()); for (String command : commands) { if (command == null) { continue; } // donnot use os.writeBytes(commmand), avoid chinese charset error os.write(command.getBytes()); os.writeBytes(COMMAND_LINE_END); os.flush(); } os.writeBytes(COMMAND_EXIT); os.flush(); result = process.waitFor(); // get command result if (isNeedResultMsg) { successMsg = new StringBuilder(); errorMsg = new StringBuilder(); successResult = new BufferedReader(new InputStreamReader(process.getInputStream())); errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream())); String s; while ((s = successResult.readLine()) != null) { successMsg.append(s); } while ((s = errorResult.readLine()) != null) { errorMsg.append(s); } } } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (os != null) { os.close(); } if (successResult != null) { successResult.close(); } if (errorResult != null) { errorResult.close(); } } catch (IOException e) { e.printStackTrace(); } if (process != null) { process.destroy(); } } return new CommandResult(result, successMsg == null ? null : successMsg.toString(), errorMsg == null ? null : errorMsg.toString()); } /** * result of command * <ul> * <li>{@link CommandResult#result} means result of command, 0 means normal, else means error, same to excute in * linux shell</li> * <li>{@link CommandResult#successMsg} means success message of command result</li> * <li>{@link CommandResult#errorMsg} means error message of command result</li> * </ul> * * @author <a href="http://www.trinea.cn" target="_blank">Trinea</a> 2013-5-16 */ public static class CommandResult { /** result of command **/ public int result; /** success message of command result **/ public String successMsg; /** error message of command result **/ public String errorMsg; public CommandResult(int result) { this.result = result; } public CommandResult(int result, String successMsg, String errorMsg) { this.result = result; this.successMsg = successMsg; this.errorMsg = errorMsg; } } }5.新建一个activity,命名为PackageManageActivity ,代码如下
package com.example.administrator.monkeyshareblog; import android.content.Intent; import android.os.Handler; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import java.util.ArrayList; import java.util.List; //包名选择activity public class PackageManageActivity extends AppCompatActivity { private ListView lv_app_list; private AppAdapter mAppAdapter; public Handler mHandler = new Handler(); List<MyAppInfo> appInfos=new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getSupportActionBar().hide(); setContentView(R.layout.activity_package_manage); lv_app_list = (ListView) findViewById(R.id.lv_app_list); mAppAdapter = new AppAdapter(); lv_app_list.setAdapter(mAppAdapter); initAppList(); //点击listView条目跳转回主界面,并传包名到主应用 lv_app_list.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { MyAppInfo myAppInfo=appInfos.get(position); Log.i("pacagename",myAppInfo.getAppName()); Intent intent=new Intent(PackageManageActivity.this,MainActivity.class); intent.putExtra("packagename",myAppInfo.getAppName()); Log.i("packagew1",myAppInfo.getAppName()); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } }); } @Override protected void onResume() { super.onResume(); } //获取app包名list并初始化界面 private void initAppList(){ new Thread(){ @Override public void run() { super.run(); //扫描得到APP列表 appInfos = ApkTool.scanLocalInstallAppList(PackageManageActivity.this.getPackageManager()); Log.i("pacagename11",appInfos.get(1).getAppName().toString()); mHandler.post(new Runnable() { @Override public void run() { mAppAdapter.setData(appInfos); } }); } }.start(); } class AppAdapter extends BaseAdapter { List<MyAppInfo> myAppInfos = new ArrayList<>(); public void setData(List<MyAppInfo> myAppInfos) { this.myAppInfos = myAppInfos; notifyDataSetChanged(); } public List<MyAppInfo> getData() { return myAppInfos; } @Override public int getCount() { if (myAppInfos != null && myAppInfos.size() > 0) { return myAppInfos.size(); } return 0; } @Override public Object getItem(int position) { if (myAppInfos != null && myAppInfos.size() > 0) { return myAppInfos.get(position); } return null; } @Override public long getItemId(int position) { return 0; } //listview绑定数据 @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder mViewHolder; MyAppInfo myAppInfo = myAppInfos.get(position); if (convertView == null) { mViewHolder = new ViewHolder(); convertView = LayoutInflater.from(getBaseContext()).inflate(R.layout.package_item, null); mViewHolder.iv_app_icon = (ImageView) convertView.findViewById(R.id.iv_app_icon); mViewHolder.tx_app_name = (TextView) convertView.findViewById(R.id.tv_app_name); convertView.setTag(mViewHolder); } else { mViewHolder = (ViewHolder) convertView.getTag(); } mViewHolder.iv_app_icon.setImageDrawable(myAppInfo.getImage()); mViewHolder.tx_app_name.setText(myAppInfo.getAppName()); return convertView; } class ViewHolder { ImageView iv_app_icon; TextView tx_app_name; } } }6.PackageManageActivity的布局代码如下
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:id="@+id/activity_package_manage" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/lv_app_list" android:layout_width="match_parent" android:layout_weight="1" android:layout_height="match_parent"></ListView> </LinearLayout>7.新添一个listView条目布局文件package_item
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:orientation="horizontal" android:layout_height="60dp"> <ImageView android:id="@+id/iv_app_icon" android:layout_weight="4" android:layout_width="match_parent" android:layout_height="match_parent" /> <TextView android:layout_weight="1" android:gravity="center_vertical" android:id="@+id/tv_app_name" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> </LinearLayout>