本文已参与「新人创作礼」活动,一起开启掘金创作之路。
如何使用AutoJs自动化地去获取我们所需要的题库数据?
这篇文章不会介绍如何去获取页面里的控件信息(代码里会有具体实现),主要是讲思路与实现的代码,整体体逻辑如下:- 在脚本内初始化我们需要的数据库,并获取手机的截图权限,用于后面的答题使用;
- 脚本自动获取题目与选项详情;
- 在数据库中查询答案并作答;
- 答题后截图,通过AutoJs的找图找色模块识别出正确答案;
- 判断作答是否正确,错误则通过截图获取正确答案并插入数据库;
- 若无提示题库问题全部回答正确则回到第②步,否则结束脚本。
在实现过程中遇到的问题与解决方式?
- 第①②点的实现较为简单,略过;
- 在第③点中,执行SQL语句查询数据库并不能保证一定可以获取到答案,当数据库中没有该数据时返回-1,脚本便会直接点击第一个选项作答;
- 在第④点中,由于我们不能保证程序的响应时间,有时候的截图时页面还没有显示正确答案,就等于此时的截图无效,所以在第④点截图时会有一个循环,当截图中获取到代表正确的色号才会继续往下走,保证截图的有效性;
- 第⑤点中,应用进入下一题的时间不大稳定,用固定的延时并不能很好的处理问题,所以使用循环去不断获取题目的信息和控件信息的状态,发现状态改变则代表程序已经进入下一题。
功能实现
第①点:打开答题App后,程序初始化,个别系统需要每次都申请截图权限,搞了个子线程自动同意一下。function mian(){
threads.start(function(){
for (let i = 0; i < 100; i++) {
if (textExists("立即开始")) {
click("立即开始");
threads.currentThread().interrupt();
}
}
});
if(!requestScreenCapture()){
toast("请允许截图权限后重试");
exit();
}
initDatabase();
startChallenge();
}
function initDatabase(){
db = SQLiteDatabase.openOrCreateDatabase(DB_PATH, null);
db.execSQL(CREATE_TABLE_CHALLENGE);
}
第②点:在答题页面中通过控件获取相应的题目与选项,在获取题目时,对一些题目相同答案不同的题目做了特殊处理,参考了别人的做法,在题目的后面连接了第一个选项的文字,以保证题目的唯一。
function getQuestion(){
var question = className("ListView").findOne(10000).parent().child(0).text();
if (question){
if (question.indexOf("选择词语的正确词形") != -1 || question.indexOf("选择正确的读音") != -1
|| question.indexOf("下列说法正确的是") != -1 || question.indexOf("下列不属于二十四史的是") != -1) {
question += getOptions()[0];
}
return question;
}
console.error("getQuestion error");
exit();
}
function getOptions(){
mOptionCount = 0;
listView = className("ListView").findOne(1000).children();
mOptions = new Array(listView.length);
listView.forEach(
child => {
mOptions[mOptionCount++] = child.child(0).child(1).text();
}
);
return mOptions;
}
第③④点:通过题目去数据库查找答案,若没查到答案,则默认点击第一个答案,标记变量Mark设为false,点击答案后截图,若mark为false则会循环截图找色去保证截图中存在正确答案。
function respondence(question){
var mark = true;
var answerIndex = getAnswer(question);
if (answerIndex < 0) {
mark = false;
answerIndex = 0;
}
className("ListView").findOne(1000).child(answerIndex).child(0).click();
delay(0.1);
img = captureScreen();
if (!mark)
//不用doWhile 是因为就算数据库查到答案也要截一个图 而那个图就不管它有没有答案了
while (getRightAnswer(img) == -1) {
img = captureScreen();
delay(0.05);
}
return mark;
}
function getAnswer(question){
var sql = "select "+ANSWER_INDEX+" from "+TABLE_CHALLENGE+" where "+QUESTION+" = '"+question+"';";
var cursor = db.rawQuery(sql,null);
if (cursor && cursor.moveToFirst()) {
return cursor.getInt(0);
}
return -1;
}
function getRightAnswer(image){
//首先找色
var point = findColor(image, "要找的颜色16进制代码");
if (point) {
//如果找色成功 返回坐标点 则获取listview的选项
childs = className("ListView").findOne().children();
//这里要细品
//循环获取选项的bound 若当前选项的bottom比坐标点的y坐标大 则为正确答案
for (let index = 0; index < childs.length; index++) {
bottom = childs.get(index).bounds().bottom;
if (bottom >= point.y) {
return index;
}
}
}
return -1;
}
第⑤⑥点:通过while循环判断阻塞住线程,若没出现错误弹窗或题目没有改变,就当是程序还没响应,若有错误弹窗或mark变量为false,则操作数据库insertOrUpdate,然后进入下一题(回到第②点),或程序结束。
function checkAnswerIsRight(mark,question,optionCount,options){
delay(0.5);
var tampQuestion;
while (!textExists("再来一局") && !textExists("分享就能复活")
&& ((tampQuestion = getQuestion()) ? question.equals(tampQuestion) : true));
if (textExists("再来一局") || textExists("分享就能复活") || !mark) {
var answerIndex = 0;
if (textExists("再来一局") || textExists("分享就能复活"))
answerIndex = getRightAnswer(img);
if (answerIndex != -1)
insertOrUpdate(question,optionCount,options,answerIndex);
else
console.log("answerIndex == -1 忽略题目 : "+question);
delay(0.1);
if (textExists("再来一局")) {
click("再来一局");
} else if (textExists("分享就能复活")) {
click("分享就能复活");
delayBack(0.3);
}
delay(random(0.1,0.3));
}
}
function insertOrUpdate(question,optionCount,options,answerIndex){
var sql = "select "+ANSWER_INDEX+" from "+TABLE_CHALLENGE+" where "+QUESTION+" = '"+question+"';";
var cursor = db.rawQuery(sql,null);
if (cursor && cursor.moveToFirst()) {
sql = "update "+TABLE_CHALLENGE+" set "+ANSWER_INDEX+" = "+answerIndex+" where "+QUESTION+" = '"+question+"';";
} else {
sql = "insert into "+TABLE_CHALLENGE+" values('"+question+"',"+optionCount+",'"+options+"',"+answerIndex+");";
}
console.log("SQL : "+sql);
db.execSQL(sql);
}
总结
整体的代码实现就是这样,不知道我的程序逻辑与代码实现有没有问题,如果大家有什么好的建议,可以在下面评论,如果兄弟们觉得不错的话,记得一键三连支持一下哈!
未经允许,请勿转载!