用AutoJs获取学习强国挑战题库

1,272 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

如何使用AutoJs自动化地去获取我们所需要的题库数据?
  这篇文章不会介绍如何去获取页面里的控件信息(代码里会有具体实现),主要是讲思路与实现的代码,整体体逻辑如下:
  1. 在脚本内初始化我们需要的数据库,并获取手机的截图权限,用于后面的答题使用;
  2. 脚本自动获取题目与选项详情;
  3. 在数据库中查询答案并作答;
  4. 答题后截图,通过AutoJs的找图找色模块识别出正确答案;
  5. 判断作答是否正确,错误则通过截图获取正确答案并插入数据库;
  6. 若无提示题库问题全部回答正确则回到第②步,否则结束脚本。
在实现过程中遇到的问题与解决方式?
  1. 第①②点的实现较为简单,略过;
  2. 在第③点中,执行SQL语句查询数据库并不能保证一定可以获取到答案,当数据库中没有该数据时返回-1,脚本便会直接点击第一个选项作答;
  3. 在第④点中,由于我们不能保证程序的响应时间,有时候的截图时页面还没有显示正确答案,就等于此时的截图无效,所以在第④点截图时会有一个循环,当截图中获取到代表正确的色号才会继续往下走,保证截图的有效性;
  4. 第⑤点中,应用进入下一题的时间不大稳定,用固定的延时并不能很好的处理问题,所以使用循环去不断获取题目的信息和控件信息的状态,发现状态改变则代表程序已经进入下一题。
功能实现
第①点:打开答题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);
}

总结

   整体的代码实现就是这样,不知道我的程序逻辑与代码实现有没有问题,如果大家有什么好的建议,可以在下面评论,如果兄弟们觉得不错的话,记得一键三连支持一下哈!

本文链接juejin.cn/post/709889…

未经允许,请勿转载!

在这里插入图片描述