实现目标
定义一个时间,在到达时间的时候,手机自动打开京东商城,并进入秒杀界面
准备工作
- 先导入需要的jar包
足足33个jar包需要引用,而且这些jar包还不是从一个地儿down下来的,找了许久的资源,果断放弃了这个想法。
改用Maven,这个是我以前写java的时候get到的,没想到android还真有。网上查了查资料,还挺简单,就是在项目跟目录的build.gradle中加一句代码。
这就省大劲儿了。我把代码贴出来,供大家复制粘贴使!
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
- 配置权限和引用
AndroidManifest.xml中要加三个权限
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.example.xinxi.monkeytest">
<!--android:sharedUserId="android.uid.system"-->
<!-->-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.INTERNET"/>
<application android:name="ContextUtil"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
中间那个权限最关键,是个超级用户权限,有了它才可以让手机自己控制自己玩儿。
接下来是src目录中的build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "android.example.xinxi.monkeytest"
minSdkVersion 24
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:2.0.4'
testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'
implementation 'com.github.yhaolpz:FloatWindow:1.0.9'
implementation 'com.contrarywind:Android-PickerView:4.1.9'
implementation 'com.google.code.gson:gson:2.4'
}
这里需要注意几点,第一,com.android.support:appcompat-v7:28.0.0的版本号要与compileSdkVersion 的版本号对上,我的IDE能支持到29,但是com.android.support:appcompat-v7目前最大只有28.
第二,自动控制支持的最小安装版本是6.0,这里minSdkVersion 的版本最小可以写24。
第三,testInstrumentationRunner 一定要改成android.support.test.runner.AndroidJUnitRunner。
步入正题
代码量其实不高,拢共就四个文件需要写。
第一个,窗体界面文件activity_main.xml
一共就俩控件,一个时间框,一个按钮就够了。
第二个,逻辑主文件MainActivity.java
这个文件控制着主要逻辑,也是它使用Monkey命令来触发uiautomator工作的。
上代码:
package android.example.xinxi.monkeytest;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ComponentName;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.BounceInterpolator;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.bigkoo.pickerview.builder.TimePickerBuilder;
import com.bigkoo.pickerview.listener.OnTimeSelectChangeListener;
import com.bigkoo.pickerview.listener.OnTimeSelectListener;
import com.bigkoo.pickerview.view.TimePickerView;
import com.google.gson.Gson;
import com.yhao.floatwindow.FloatWindow;
import com.yhao.floatwindow.PermissionListener;
import com.yhao.floatwindow.Screen;
import com.yhao.floatwindow.ViewStateListener;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MainActivity extends AppCompatActivity {
private UiautomatorThread uiautomatorThread;
private Button startButton;
private Context context;
private static String TAG = "MonkeyTest";
private static String packageName = "android.example.xinxi.monkeytest.test";
private static String runClassName = "android.support.test.runner.AndroidJUnitRunner";
public volatile boolean exit = false;
public static String runtime = "";
private TimePickerView pvTime;
private TextView textView;
private TextView tb_textview;
private Button tb_btn;
private TextView start_time;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startButton = (Button) findViewById(R.id.button);
textView = (TextView) findViewById(R.id.textView);
tb_textview = (TextView) findViewById(R.id.tb_textview);
tb_btn = (Button) findViewById(R.id.tb_btn);
start_time = (TextView) findViewById(R.id.start_time);
initTimePicker();
}
/**
* 用于接收子线程中的数据
*/
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
tb_textview.setText(msg.obj.toString());
}
};
/**
* 点击RUN按钮
*/
public void runMyUiautomator(View v) {
String user_time = textView.getText().toString();
if(user_time.equals("请选择时间")){
showExitDialog("请选择时间");
return;
}
String tb_time = tb_textview.getText().toString();
if(tb_time.equals("淘宝时间差")){
showExitDialog("请获取淘宝时间差");
return;
}
long datetime = Long.parseLong(getTimeStamp(user_time)) * 1000;
long tb_time_stamp = Long.parseLong(tb_textview.getText().toString());
long targettime = datetime - tb_time_stamp;
start_time.setText(String.valueOf(targettime));
uiautomatorThread = new UiautomatorThread(targettime);
uiautomatorThread.start();
}
/**
* 点击选择时间按钮
* */
public void initTime(View v) {
pvTime.show(v);
}
/**
* 点击获取淘宝时间差
*/
public void getTBTime(View v){
getTBData();
}
/**
*格式化选择的时间
*/
private String getTime(Date date) {//可根据需要自行截取数据显示
Log.d("getTime()", "choice date millis: " + date.getTime());
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.format(date);
}
/**
* 提示框
*/
private void showExitDialog(String message){
new AlertDialog.Builder(this)
.setTitle("提示")
.setMessage(message)
.setPositiveButton("确定", null)
.show();
}
/**
* 初始化时间控件
*/
private void initTimePicker() {
//Dialog 模式下,在底部弹出
pvTime = new TimePickerBuilder(this, new OnTimeSelectListener() {
@Override
public void onTimeSelect(Date date, View v) {
textView.setText(getTime(date));
Log.i("pvTime", "onTimeSelect");
}
}).setTimeSelectChangeListener(new OnTimeSelectChangeListener() {
@Override
public void onTimeSelectChanged(Date date) {
Log.i("pvTime", "onTimeSelectChanged");
}
})
.setType(new boolean[]{true, true, true, true, true, true})
.isDialog(true) //默认设置false ,内部实现将DecorView 作为它的父控件。
.addOnCancelClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.i("pvTime", "onCancelClickListener");
}
})
.setItemVisibleCount(5) //若设置偶数,实际值会加1(比如设置6,则最大可见条目为7)
.setLineSpacingMultiplier(2.0f)
.isAlphaGradient(true)
.build();
Dialog mDialog = pvTime.getDialog();
if (mDialog != null) {
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
Gravity.BOTTOM);
params.leftMargin = 0;
params.rightMargin = 0;
pvTime.getDialogContainerLayout().setLayoutParams(params);
Window dialogWindow = mDialog.getWindow();
if (dialogWindow != null) {
dialogWindow.setWindowAnimations(com.bigkoo.pickerview.R.style.picker_view_slide_anim);//修改动画样式
dialogWindow.setGravity(Gravity.BOTTOM);//改成Bottom,底部显示
dialogWindow.setDimAmount(0.3f);
}
}
}
/**
* 将时间字符串转成时间戳
*/
public static String getTimeStamp(String user_time) {
String re_time = null;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d;
try {
d = sdf.parse(user_time);
long l = d.getTime();
String str = String.valueOf(l);
re_time = str.substring(0, 10);
}catch (ParseException e) {
}
return re_time;
}
/**
* 获取淘宝时间差
*/
public void getTBData(){
//HttpUrlConnection
/**
* 1.实例化一个url对象
* 2.获取HttpUrlConnection对象
* 3.设置请求连接属性
* 4.获取响应码,判断是否连接成功
* 5.读取输入流并解析
*/
//参数:你要访问的接口地址
new Thread(){
@Override
public void run() {
try {
URL url = new URL("http://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp");
HttpURLConnection coon = (HttpURLConnection) url.openConnection();
coon.setRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36");
coon.setRequestProperty("cookie","thw=cn");
coon.setRequestMethod("GET");
coon.setReadTimeout(6000);
//获取响应码
if(coon.getResponseCode() == 200){
//获取输入流
InputStream in = coon.getInputStream();
byte[] b = new byte[1024*512];
int len = 0;
//建立缓存流,保存所读取的字节数组
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((len = in.read(b)) > -1){
baos.write(b,0,len);
}
String msg = baos.toString();
//JSON数据的解析:
JSONObject obj = new JSONObject(msg);
JSONObject data = obj.getJSONObject("data");
String tb_time = data.getString("t");
//获取当前时间戳
long now_time_long = new Date().getTime();
long tb_time_long = Long.parseLong(tb_time);
long time_difference = tb_time_long - now_time_long;
Message message = handler.obtainMessage();
message.obj =time_difference;
//调用此方法,则会触发主线程中Handle对象里覆盖了的handleMessage方法
handler.sendMessage(message);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}
}.start();
}
/**
* 运行uiautomator是个费时的操作,不应该放在主线程,因此另起一个线程运行
*/
class UiautomatorThread extends Thread {
private final Object lock = new Object();
private boolean pause = false;
private long runtime;
public UiautomatorThread(long targettime) {
runtime = targettime;
}
@Override
public void run() {
super.run();
while (true){
long now_time_long = new Date().getTime();
if(now_time_long >= runtime){
String command = generateCommand("android.example.xinxi.monkeytest", "ExampleInstrumentedTest", "demo");
CMDUtils.CMD_Result rs = CMDUtils.runCMD(command, true, true);
break;
}
}
}
/**
* 生成命令
*
* @param pkgName uiautomator包名
* @param clsName uiautomator类名
* @param mtdName uiautomator方法名
* @return
*/
public String generateCommand(String pkgName, String clsName, String mtdName) {
String command = "am instrument -w -r -e debug false -e class "
+ pkgName + "." + clsName + "#" + mtdName + " "
+ pkgName + ".test/android.support.test.runner.AndroidJUnitRunner";
Log.e(TAG, command);
return command;
}
}
}
这里需要注意的是,uiautomator在实际运行的时候特别耗资源,尤其里面还有个while循环,所以我给它来了个子线程,跟后台自己跑去吧,哈哈哈…
第三个,是一个辅助文件CMDUtils.java
它的左右就是把monkey命令格式化一下子。
上代码:
package android.example.xinxi.monkeytest;
import android.util.Log;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
/**
* 执行命令
*/
public class CMDUtils {
private static final String TAG = "CMDUtils";
public static class CMD_Result {
public int resultCode;
public String error;
public String success;
public CMD_Result(int resultCode, String error, String success) {
this.resultCode = resultCode;
this.error = error;
this.success = success;
}
}
/**
* 执行命令
*
* @param command 命令
* @param isShowCommand 是否显示执行的命令
* @param isNeedResultMsg 是否反馈执行的结果
* @retrun CMD_Result
*/
public static CMD_Result runCMD(String command, boolean isShowCommand,
boolean isNeedResultMsg) {
if (isShowCommand)
Log.i(TAG, "runCMD:" + command);
CMD_Result cmdRsult = null;
int result;
Process process = null;
PrintWriter pw = null;
try {
process = Runtime.getRuntime().exec("su"); //获取root权限
pw = new PrintWriter(process.getOutputStream());
pw.println(command);
pw.flush();
result = process.waitFor();
if (isNeedResultMsg) {
StringBuilder successMsg = new StringBuilder();
StringBuilder errorMsg = new StringBuilder();
BufferedReader successResult = new BufferedReader(
new InputStreamReader(process.getInputStream()));
BufferedReader 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);
}
cmdRsult = new CMD_Result(result, errorMsg.toString(),
successMsg.toString());
}
} catch (Exception e) {
Log.e(TAG, "run CMD:" + command + " failed");
e.printStackTrace();
} finally {
if (pw != null) {
pw.close();
}
if (process != null) {
process.destroy();
}
}
return cmdRsult;
}
}
这里没啥注意的,直接复制粘贴就成。
第四个,也就是关键的自动测试文件,看好路径,是src目录下androidTest中的ExampleInstrumentedTest.java文件。
package android.example.xinxi.monkeytest;
import android.support.test.InstrumentationRegistry;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiObjectNotFoundException;
import android.support.test.uiautomator.UiSelector;
import org.junit.Test;
public class ExampleInstrumentedTest {
private UiDevice mDevice;
@Test
public void demo() throws UiObjectNotFoundException {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
UiObject x= mDevice.findObject(new UiSelector().text("京东"));
x.click();
sleep(5000);
UiObject y= mDevice.findObject(new UiSelector().text("京东秒杀"));
y.click();
}
public void sleep(int mint){
try{
Thread.sleep(mint);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
代码量不高,就是打开京东,进入京东秒杀界面。做实验而已嘛,何必较真呢。
这个文件写完以后可以单独跑一下,看一下效果。
最后:需要注意的是,编译后的文件会有两个apk,一个普通的apk和一个testapk,安装顺序一定时先安装apk再安装testapk,否则不能正常运行,貌似是找不到testapk中的那个测试方法。
给你们看看我实验的结果吧,反正我也成功了,哈哈哈…
最后,我也是刚开始学习android,希望大家与我沟通,咱们互相学习,共同进步。
下载源码