Autojs基础-app(应用)
1.前言初次看这部分内容感觉功能好强大尤其是应用启动部分感觉好方便。我只能说抱歉这部分内容很多获取的autojs自己的各种信息而不是当前应用的信息还有一些函数平时不会使用或者特殊情况使用不方便。我说过我自己写过一个完整的游戏项目这游戏是直板机都能玩的游戏可以在一个手机上挂5个游戏应用也就是所说的5开。由于我写脚本的游戏应用有两个活跃包名简单说应用活跃状态对应两个页面使用这部分中的启动方式会启动到第一个游戏页面而我想切换到第二个游戏页面。还一个原因是官方没有给出切换应用的函数只有启动应用的函数通过启动应用的函数去切换应用速度会很慢还一个就是可能存在内存溢出的风险。为了快速切换应用我通过root命令的方式进行切换这个命令代码的优化是非常痛苦的。我开始通过命令行切换应用速度也很快但是运行一段时间后出现了内存溢出导致手机卡死的情况。我反复排查一直以为图片处理导致的内存溢出。幸好因为兼容多个环境的原因我在脚本开发过程中从来不使用图片识别不会加载本地的图片一切图片都是加载内存里面的当然不是因为图片处理问题而导致的内存溢出。我通过打印各种信息发现我使用的root命令切换应用每次会生成一条记录。如果app没有启动生成一条记录来启动应用是正常的但是app运行后台的情况下它也会通过启动的方式来切换应用这样会导致内存占用越来越多最后直接卡死手机。如果不是在需要频繁切换5个应用的极端条件下你一定不会相信切换应用竟然会导致内存溢出。对于出现这种问题其实很好解决。现在一个手机上运行一个游戏的我们写脚本的时候只需要启动一次游戏应用就可以了或者进入游戏后再启动脚本。对于和我一样的特殊情况一个手机上有多个相同的游戏需要频繁的切换我也给出不会导致内存溢出的方式。如果在一个手机上频繁进行不同应用切换并且想快速切换也可以使用我给出的这种方式当然需要在root环境下完成操作。2.属性与函数1.概况app相关的所有属性或者函数都是基于app对象调用的app对象已经内置在全局变量中可以直接进行使用不需要自己创建或者销毁。获取属性时必须通过app对象获取调用函数可以通过app对象调用也可直接调用函数因为全局变量中包含app对象包含的所有函数。2.versionCode、versionName、autojs.versionCode与autojs.versionName按照正常versionCode和versionName对应当前的应用版本号和版本名称autojs.versionCode和autojs.versionName对应autojs的版本号和版本名称。但是我们脚本肯定得一直运行在autojs中导致四个函数都是获取autojs的版本号或版本名称。我们脚本开发过程中获取当前应用的信息可能有用获取autojs的信息根本没有任何作用因此我说这部分内容没有作用。console.log(版本号 app.versionCode); console.log(版本名称 app.versionName); console.log(autojs版本号 app.autojs.versionCode); console.log(autojs版本名称 app.autojs.versionName);3.launchApp、launch与launchPackagelaunchApp、launch和launchPackage均是启动应用均接收字符串类型的参数。launchApp通过应用名称启动应用launch和launchPackage均通过应用包名启动应用两者作用一样。需要注意这三种方式都是启动应用导致生成一条新记录。launchApp(按键精灵); // 相当于 app.launchApp(按键精灵); app.launch(com.cyjh.mobileanjian); // 相当于 launch(com.cyjh.mobileanjian); // 相当于 launchPackage(com.cyjh.mobileanjian); // 相当于 app.launchPackage(com.cyjh.mobileanjian);我觉得应用启动是最重要的内容我会详细讲下。如果你使用官方给出的这种方式启动应用是很慢的尤其是在第一次运行这部分代码时运行速度很慢。有没有快速通用的应用启动方式呢我给出一个使用无障碍方式快速万能启动应用的方式而且这种方式是模拟人为点击一定不会出现内存溢出。先返回主界面然后通过应用名称进行点击哪怕应用不在主界面的第一页也会自动点击的但是放在“应用分类夹”中的应用无法启动。// 返回主界面 Home(); // 加两秒延迟 sleep(2000); // 启动按键精灵可根据实际情况替换应用名 text(按键精灵).click();上述方式在以下情况下无法启动。可能有小伙伴说我习惯使用包名启动应用的方式但是我不知道应用包名怎么办我已经给大家封装了获取应用包名的函数。这个函数除了获取包名外还可以获取活跃包名活跃包名会用在root环境下快速切换应用。getHomePackageAndActivityPackage(按键精灵); // 获取包名和活跃包名 function getHomePackageAndActivityPackage(appName) { Home(); sleep(2000); let currentSelector className(android.widget.TextView).text(appName).findOne(10000); if (!currentSelector) { toastLog(获取包名失败); return; } currentSelector.click(); sleep(2000); let currentHomePackage currentPackage(); let currentActivityPackage currentActivity(); toastLog(包名 currentHomePackage); toastLog(活跃包名 currentActivityPackage); Home(); }注意有些小伙伴使用这个函数的时候可能会出现打印不是当前包名的情况这个不是代码有问题。有可能是你第一次执行相关代码autojs第一次启动时候会有个预加载过程导致执行速度过慢然后延迟加的不够会答应主机主界面的包信息。autojs有些代码第一次执行时候就是慢在我们分块学习过程中很容易出现这个问题大家知道就好了在项目中是不会出现的哪怕真的出现有很多方式可以预加载。我这是给大家提醒下出现问题有考虑方向后面尽量不会再进行这部分提示了。在root环境下如果需要快速切换应用的话可以通过以下方式完成。这个过程非常复杂首先就是启动和切换要采用不同的方式需要通过数据来判断使用哪种情况打开应用其次切换应用需要根据不同的Android版本有对应的数字最后每次启动后需要获取应用的任务id用于后面快速切换应用。为什么这么麻烦因为这种情况下哪怕是一个应用两个活跃页面也能保证切换到最终页面能够快速切换并且不会导致内存溢出。小伙伴们看到这里应该都头疼了因此如果不是特别需要我不推荐这种方式。不过因为在学习过程中这种方式我也会信息讲下过程如果觉得麻烦可以忽略。第一步获取任务id。最后需要的就是筛选后的id只有这个值会在后面进行使用。筛选后的数组一般先判断数组是否为空。如果为空代表应用没有启动方便后面调用启动相关的代码如果不为空将数据根据需求全局保存下每次启动只需要全局保存一次即可只要能自己能够分清每个应用对应的id即可具体保存在数组、对象还是什么里面可以根据自己的喜好进行保存。// 当前应用包名 let currentPackageName com.cyjh.mobileanjian; // 获取所有的任务id let taskIdArray getRecentTaskPackageTaskIds(); // 个别时候有两个任务id非常特殊的情况按照最后打开的时间降序排列 taskIdArray.sort(function (a, b) { return b.lastActiveTime - a.lastActiveTime; }); // 获取需要的包名id数组我这里以按键精灵的包名为例请根据实际情况筛选 let filterTaskIdArray taskIdArray.filter(function (item) { return item.packageName.indexOf(currentPackageName) -1; }); console.log(filterTaskIdArray); // 获取最近任务包名和任务id function getRecentTaskPackageTaskIds() { let cmd dumpsys activity activities; let result shell(cmd, true); let taskArray result.result.split(Task id #); let taskIdArray []; taskArray.forEach(currentTask { let currentTaskArray currentTask.split( ); let packageString currentTaskArray.find(function (item) { return item.indexOf(A) ! -1; }); let timestampString currentTaskArray.find(function (item) { return item.indexOf(lastActiveTime) ! -1; }); if (packageString) { let currentObj { id: currentTaskArray[0].replace(/\n/g, ), packageName: packageString.substring(packageString.indexOf() 1), lastActiveTime: Number(timestampString.substring(15)) } taskIdArray.push(currentObj); } }); return taskIdArray; }第二步如果筛选后的数组为空可以调用下面的启动应用代码。包名和活跃包名上面能够获取这个对于一个应用来说一般是不会改变的获取一次后全局赋值即可。对于shell命令对象我推荐个全局创建一个以后可以直接通过这个对象调用命令。但是这个命令有个缺陷就是权限不如直接通过shell调用的权限高脚本开发过程中我们可以优先使用这个全局对象执行shell命令如果权限不足导致无法执行可以再考虑直接通过shell命令调用。root命令更高有概率出现问题请严格按照过程执行。需要切换应用的调用切换应用代码需要启动应用的调用启动应用代码。// 当前应用包名 let currentPackageName com.cyjh.mobileanjian; // 当前活跃包名 let currentActivePackageName com.cyjh.mobileanjian.vip.activity.MainActivity; // 创建shell命令全局可以只创建一次 let currentShell new Shell(true); currentShell.exec(am start -n currentPackageName / currentActivePackageName -f 0x20000000 --ez REORDER true);第三步如果筛选后的数组不为空可以调用下面的切换应用代码。这部分重要的是不同版本的切换值比如Andorid 7的环境下的切换值为24这也是用的比较多的脚本开发环境。其他环境下的值需要自己找但是我尝试了下Andoid 9环境的雷电模拟器也没找到这个值也可能在Android 9中不支持这种切换方式了。// 创建shell命令全局可以只创建一次 let currentShell new Shell(true); let taskId 5; let cmd service call activity 24 i32 taskId i32 0; currentShell.exec(cmd);当然为了方便大家找这个值我给大家封装了个函数快速找这个值。但是我推荐大家在Android 7的环境下使用此种方式我感觉别的环境下大概率不支持。下面这个函数的参数第一个为第一步获取的任务id第二参数为开始查找的值第三个参数为结束查找的值。我一般推荐值为40以内这是执行shell命令对系统影响很大40以上的值容易导致崩溃。还有就是需要将代码中的TARGET_PKG填写获取的任务id对应包名我推荐是函数第二和第三个参数不用填写使用默认值即可。只需要将第一步中的包名和包名对应的任务id修改TARGET_PKG值或者通过参数传递就可以完成下面功能。这个函数主要是让大家参考下我如何找到的这个值。当然如果有小伙伴找到了其他环境下的这个值可以评论区留言提醒。// 查找操作id function getSwitchAppCallCode(taskId, startTaskId, endTaskId) { if (!startTaskId) { startTaskId 1; } if (!endTaskId) { endTaskId 40; } // 要切换到的目标包名和 TaskId let TARGET_PKG com.cyjh.mobileanjian; let TASK_ID taskId; let SAFE_CODES [24, 25, 26, 32, 33, 35]; // 成功后会保存在这里 let foundCode null; for (let code of SAFE_CODES) { let cmd service call activity code i32 TASK_ID i32 0; log(尝试 code code : cmd); let result shell(cmd, true); console.log(result); sleep(500); // 增加等待时间确保切换完成 if (currentPackage() TARGET_PKG) { foundCode code; break; } } if (!foundCode) { console.log(关键值未找到); // 尝试 code // 43有问题不要尝试 for (let code startTaskId; code endTaskId; code) { // 拼接命令字符串 let cmd service call activity code i32 TASK_ID i32 0; log(尝试 code code : cmd); // 执行命令true 表示以 root 权限执行 let result shell(cmd, true); console.log(result); // 等500ms 让切换发生 sleep(500); // 检查当前前台包名 let cur currentPackage(); log(当前包: cur); if (cur TARGET_PKG) { toast(找到有效 code code); foundCode code; break; } } } // 如果找到了就用该 code 再切一次并退出 if (foundCode ! null) { var finalCmd service call activity foundCode i32 TASK_ID i32 0; log(最终切换使用 code foundCode : finalCmd); shell(finalCmd, true); } else { toast(未在范围内找到有效 code请调整范围); } }4.getAppName通过应用包名获取应用名需要传递应用包名一个参数参数类型为字符串。一般情况下都是想通过应用名来获取应用包名通过应用包名大概率可以猜出所属的应用。在无法确定的情况下这个函数更多的是用于应用包名和应用名之前的验证。console.log(getAppName(com.cyjh.mobileanjian));5.openAppSetting打开app的设置页面需要传应用包名一个参数参数类型为字符串。返回是否操作成功返回类型为boolean一般会返回true如果打开失败或者未找到应用返回false。console.log(openAppSetting(com.cyjh.mobileanjian));6.viewFile与editFileviewFile和editFile函数分别用于在其他应用中查看或者编辑某个文件均需要传递文件路径一个参数参数类型均为字符串。如果没有找到打开能够打开文件的应用抛出ActivityNotException异常。如果这个类型的文件已经在系统中设置了默认打开方式会直接打开如果没有设置默认打开方式会弹出应用选择框。下面就以viewFile函数为例介绍下这个功能editFile函数类似。最好通过捕获异常的方式来调用这个函数真正有异常可以进行特殊处理而不是导致脚本停止。对于Android环境下的文件路径我在文件部分会进行介绍现在知道是一个完整的文件路径就好了。我想打开个特殊文件类型的文件或者不存在的文件来触发报错报错但是这两种方式都无法触发都会弹出类似的应用选择框。这就说明要出发这个报错是非常困难的文件管理器一般都是允许打开任何文件的没出现的文件类型顶多触发个文件选择。try { app.viewFile(/sdcard/com.py.test/test.txt); } catch (error) { console.log(error); }注意viewFile和editFile函数只能通过app对象调用没有在全局变量里面。我没有特殊提醒的函数代表app对象调用和直接调用函数两种方式均可以但是这两个函数只支持app对象调用方式。按照正常理解同属于app部分的函数调用方式应该一致但是就出现了不同情况。autojs会出现很多这种类似的问题如果后面有特殊情况我会提醒的。7.uninstall卸载应用需要传应用包名一个参数参数类型为字符串。只是弹出卸载应用的确认框最后卸载还需要自己确认。只能通过app对象调用无法直接调用。但是这个函数在Android 7的环境下可以生效但是在安卓9的环境下不生效。8.openUrl通过浏览器打开链接需要传链接一个参数参数类型为字符串。如果不以“http://”或者“https://”开头会自动拼接“http://”。如果没有浏览器会抛出ActivityNotException异常。只能通过app对象调用无法直接调用。app.openUrl(www.baidu.com); // 相当于 // app.openUrl(http://www.baidu.com);9.sendEmail至getInstalledApps后面这几个函数我很少使用我就不做过多介绍这些函数大概率都是通过app对象调用无法直接调用。小伙伴如果感兴趣可以研究下这部分内容尤其是intent和startActivity函数配合使用。这两个函数配合使用功能很强大前面一些函数完全可以通过这两个函数配合使用来完成。这两个函数也能完成应用启动功能需要谨慎这两个函数配合使用导致的内存溢出风险。
