-app(應(yīng)用))
1.前言初次看這部分內(nèi)容感覺(jué)功能好強(qiáng)大尤其是應(yīng)用啟動(dòng)部分感覺(jué)好方便。我只能說(shuō)抱歉這部分內(nèi)容很多獲取的autojs自己的各種信息而不是當(dāng)前應(yīng)用的信息還有一些函數(shù)平時(shí)不會(huì)使用或者特殊情況使用不方便。我說(shuō)過(guò)我自己寫(xiě)過(guò)一個(gè)完整的游戲項(xiàng)目這游戲是直板機(jī)都能玩的游戲可以在一個(gè)手機(jī)上掛5個(gè)游戲應(yīng)用也就是所說(shuō)的5開(kāi)。由于我寫(xiě)腳本的游戲應(yīng)用有兩個(gè)活躍包名簡(jiǎn)單說(shuō)應(yīng)用活躍狀態(tài)對(duì)應(yīng)兩個(gè)頁(yè)面使用這部分中的啟動(dòng)方式會(huì)啟動(dòng)到第一個(gè)游戲頁(yè)面而我想切換到第二個(gè)游戲頁(yè)面。還一個(gè)原因是官方?jīng)]有給出切換應(yīng)用的函數(shù)只有啟動(dòng)應(yīng)用的函數(shù)通過(guò)啟動(dòng)應(yīng)用的函數(shù)去切換應(yīng)用速度會(huì)很慢還一個(gè)就是可能存在內(nèi)存溢出的風(fēng)險(xiǎn)。為了快速切換應(yīng)用我通過(guò)root命令的方式進(jìn)行切換這個(gè)命令代碼的優(yōu)化是非常痛苦的。我開(kāi)始通過(guò)命令行切換應(yīng)用速度也很快但是運(yùn)行一段時(shí)間后出現(xiàn)了內(nèi)存溢出導(dǎo)致手機(jī)卡死的情況。我反復(fù)排查一直以為圖片處理導(dǎo)致的內(nèi)存溢出。幸好因?yàn)榧嫒荻鄠€(gè)環(huán)境的原因我在腳本開(kāi)發(fā)過(guò)程中從來(lái)不使用圖片識(shí)別不會(huì)加載本地的圖片一切圖片都是加載內(nèi)存里面的當(dāng)然不是因?yàn)閳D片處理問(wèn)題而導(dǎo)致的內(nèi)存溢出。我通過(guò)打印各種信息發(fā)現(xiàn)我使用的root命令切換應(yīng)用每次會(huì)生成一條記錄。如果app沒(méi)有啟動(dòng)生成一條記錄來(lái)啟動(dòng)應(yīng)用是正常的但是app運(yùn)行后臺(tái)的情況下它也會(huì)通過(guò)啟動(dòng)的方式來(lái)切換應(yīng)用這樣會(huì)導(dǎo)致內(nèi)存占用越來(lái)越多最后直接卡死手機(jī)。如果不是在需要頻繁切換5個(gè)應(yīng)用的極端條件下你一定不會(huì)相信切換應(yīng)用竟然會(huì)導(dǎo)致內(nèi)存溢出。對(duì)于出現(xiàn)這種問(wèn)題其實(shí)很好解決?,F(xiàn)在一個(gè)手機(jī)上運(yùn)行一個(gè)游戲的我們寫(xiě)腳本的時(shí)候只需要啟動(dòng)一次游戲應(yīng)用就可以了或者進(jìn)入游戲后再啟動(dòng)腳本。對(duì)于和我一樣的特殊情況一個(gè)手機(jī)上有多個(gè)相同的游戲需要頻繁的切換我也給出不會(huì)導(dǎo)致內(nèi)存溢出的方式。如果在一個(gè)手機(jī)上頻繁進(jìn)行不同應(yīng)用切換并且想快速切換也可以使用我給出的這種方式當(dāng)然需要在root環(huán)境下完成操作。2.屬性與函數(shù)1.概況app相關(guān)的所有屬性或者函數(shù)都是基于app對(duì)象調(diào)用的app對(duì)象已經(jīng)內(nèi)置在全局變量中可以直接進(jìn)行使用不需要自己創(chuàng)建或者銷(xiāo)毀。獲取屬性時(shí)必須通過(guò)app對(duì)象獲取調(diào)用函數(shù)可以通過(guò)app對(duì)象調(diào)用也可直接調(diào)用函數(shù)因?yàn)槿肿兞恐邪琣pp對(duì)象包含的所有函數(shù)。2.versionCode、versionName、autojs.versionCode與autojs.versionName按照正常versionCode和versionName對(duì)應(yīng)當(dāng)前的應(yīng)用版本號(hào)和版本名稱(chēng)autojs.versionCode和autojs.versionName對(duì)應(yīng)autojs的版本號(hào)和版本名稱(chēng)。但是我們腳本肯定得一直運(yùn)行在autojs中導(dǎo)致四個(gè)函數(shù)都是獲取autojs的版本號(hào)或版本名稱(chēng)。我們腳本開(kāi)發(fā)過(guò)程中獲取當(dāng)前應(yīng)用的信息可能有用獲取autojs的信息根本沒(méi)有任何作用因此我說(shuō)這部分內(nèi)容沒(méi)有作用。console.log(版本號(hào) app.versionCode); console.log(版本名稱(chēng) app.versionName); console.log(autojs版本號(hào) app.autojs.versionCode); console.log(autojs版本名稱(chēng) app.autojs.versionName);3.launchApp、launch與launchPackagelaunchApp、launch和launchPackage均是啟動(dòng)應(yīng)用均接收字符串類(lèi)型的參數(shù)。launchApp通過(guò)應(yīng)用名稱(chēng)啟動(dòng)應(yīng)用launch和launchPackage均通過(guò)應(yīng)用包名啟動(dòng)應(yīng)用兩者作用一樣。需要注意這三種方式都是啟動(dòng)應(yīng)用導(dǎo)致生成一條新記錄。launchApp(按鍵精靈); // 相當(dāng)于 app.launchApp(按鍵精靈); app.launch(com.cyjh.mobileanjian); // 相當(dāng)于 launch(com.cyjh.mobileanjian); // 相當(dāng)于 launchPackage(com.cyjh.mobileanjian); // 相當(dāng)于 app.launchPackage(com.cyjh.mobileanjian);我覺(jué)得應(yīng)用啟動(dòng)是最重要的內(nèi)容我會(huì)詳細(xì)講下。如果你使用官方給出的這種方式啟動(dòng)應(yīng)用是很慢的尤其是在第一次運(yùn)行這部分代碼時(shí)運(yùn)行速度很慢。有沒(méi)有快速通用的應(yīng)用啟動(dòng)方式呢我給出一個(gè)使用無(wú)障礙方式快速萬(wàn)能啟動(dòng)應(yīng)用的方式而且這種方式是模擬人為點(diǎn)擊一定不會(huì)出現(xiàn)內(nèi)存溢出。先返回主界面然后通過(guò)應(yīng)用名稱(chēng)進(jìn)行點(diǎn)擊哪怕應(yīng)用不在主界面的第一頁(yè)也會(huì)自動(dòng)點(diǎn)擊的但是放在“應(yīng)用分類(lèi)夾”中的應(yīng)用無(wú)法啟動(dòng)。// 返回主界面 Home(); // 加兩秒延遲 sleep(2000); // 啟動(dòng)按鍵精靈可根據(jù)實(shí)際情況替換應(yīng)用名 text(按鍵精靈).click();上述方式在以下情況下無(wú)法啟動(dòng)。可能有小伙伴說(shuō)我習(xí)慣使用包名啟動(dòng)應(yīng)用的方式但是我不知道應(yīng)用包名怎么辦我已經(jīng)給大家封裝了獲取應(yīng)用包名的函數(shù)。這個(gè)函數(shù)除了獲取包名外還可以獲取活躍包名活躍包名會(huì)用在root環(huán)境下快速切換應(yīng)用。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(); }注意有些小伙伴使用這個(gè)函數(shù)的時(shí)候可能會(huì)出現(xiàn)打印不是當(dāng)前包名的情況這個(gè)不是代碼有問(wèn)題。有可能是你第一次執(zhí)行相關(guān)代碼autojs第一次啟動(dòng)時(shí)候會(huì)有個(gè)預(yù)加載過(guò)程導(dǎo)致執(zhí)行速度過(guò)慢然后延遲加的不夠會(huì)答應(yīng)主機(jī)主界面的包信息。autojs有些代碼第一次執(zhí)行時(shí)候就是慢在我們分塊學(xué)習(xí)過(guò)程中很容易出現(xiàn)這個(gè)問(wèn)題大家知道就好了在項(xiàng)目中是不會(huì)出現(xiàn)的哪怕真的出現(xiàn)有很多方式可以預(yù)加載。我這是給大家提醒下出現(xiàn)問(wèn)題有考慮方向后面盡量不會(huì)再進(jìn)行這部分提示了。在root環(huán)境下如果需要快速切換應(yīng)用的話(huà)可以通過(guò)以下方式完成。這個(gè)過(guò)程非常復(fù)雜首先就是啟動(dòng)和切換要采用不同的方式需要通過(guò)數(shù)據(jù)來(lái)判斷使用哪種情況打開(kāi)應(yīng)用其次切換應(yīng)用需要根據(jù)不同的Android版本有對(duì)應(yīng)的數(shù)字最后每次啟動(dòng)后需要獲取應(yīng)用的任務(wù)id用于后面快速切換應(yīng)用。為什么這么麻煩因?yàn)檫@種情況下哪怕是一個(gè)應(yīng)用兩個(gè)活躍頁(yè)面也能保證切換到最終頁(yè)面能夠快速切換并且不會(huì)導(dǎo)致內(nèi)存溢出。小伙伴們看到這里應(yīng)該都頭疼了因此如果不是特別需要我不推薦這種方式。不過(guò)因?yàn)樵趯W(xué)習(xí)過(guò)程中這種方式我也會(huì)信息講下過(guò)程如果覺(jué)得麻煩可以忽略。第一步獲取任務(wù)id。最后需要的就是篩選后的id只有這個(gè)值會(huì)在后面進(jìn)行使用。篩選后的數(shù)組一般先判斷數(shù)組是否為空。如果為空代表應(yīng)用沒(méi)有啟動(dòng)方便后面調(diào)用啟動(dòng)相關(guān)的代碼如果不為空將數(shù)據(jù)根據(jù)需求全局保存下每次啟動(dòng)只需要全局保存一次即可只要能自己能夠分清每個(gè)應(yīng)用對(duì)應(yīng)的id即可具體保存在數(shù)組、對(duì)象還是什么里面可以根據(jù)自己的喜好進(jìn)行保存。// 當(dāng)前應(yīng)用包名 let currentPackageName com.cyjh.mobileanjian; // 獲取所有的任務(wù)id let taskIdArray getRecentTaskPackageTaskIds(); // 個(gè)別時(shí)候有兩個(gè)任務(wù)id非常特殊的情況按照最后打開(kāi)的時(shí)間降序排列 taskIdArray.sort(function (a, b) { return b.lastActiveTime - a.lastActiveTime; }); // 獲取需要的包名id數(shù)組我這里以按鍵精靈的包名為例請(qǐng)根據(jù)實(shí)際情況篩選 let filterTaskIdArray taskIdArray.filter(function (item) { return item.packageName.indexOf(currentPackageName) -1; }); console.log(filterTaskIdArray); // 獲取最近任務(wù)包名和任務(wù)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; }第二步如果篩選后的數(shù)組為空可以調(diào)用下面的啟動(dòng)應(yīng)用代碼。包名和活躍包名上面能夠獲取這個(gè)對(duì)于一個(gè)應(yīng)用來(lái)說(shuō)一般是不會(huì)改變的獲取一次后全局賦值即可。對(duì)于shell命令對(duì)象我推薦個(gè)全局創(chuàng)建一個(gè)以后可以直接通過(guò)這個(gè)對(duì)象調(diào)用命令。但是這個(gè)命令有個(gè)缺陷就是權(quán)限不如直接通過(guò)shell調(diào)用的權(quán)限高腳本開(kāi)發(fā)過(guò)程中我們可以?xún)?yōu)先使用這個(gè)全局對(duì)象執(zhí)行shell命令如果權(quán)限不足導(dǎo)致無(wú)法執(zhí)行可以再考慮直接通過(guò)shell命令調(diào)用。root命令更高有概率出現(xiàn)問(wèn)題請(qǐng)嚴(yán)格按照過(guò)程執(zhí)行。需要切換應(yīng)用的調(diào)用切換應(yīng)用代碼需要啟動(dòng)應(yīng)用的調(diào)用啟動(dòng)應(yīng)用代碼。// 當(dāng)前應(yīng)用包名 let currentPackageName com.cyjh.mobileanjian; // 當(dāng)前活躍包名 let currentActivePackageName com.cyjh.mobileanjian.vip.activity.MainActivity; // 創(chuàng)建shell命令全局可以只創(chuàng)建一次 let currentShell new Shell(true); currentShell.exec(am start -n currentPackageName / currentActivePackageName -f 0x20000000 --ez REORDER true);第三步如果篩選后的數(shù)組不為空可以調(diào)用下面的切換應(yīng)用代碼。這部分重要的是不同版本的切換值比如Andorid 7的環(huán)境下的切換值為24這也是用的比較多的腳本開(kāi)發(fā)環(huán)境。其他環(huán)境下的值需要自己找但是我嘗試了下Andoid 9環(huán)境的雷電模擬器也沒(méi)找到這個(gè)值也可能在Android 9中不支持這種切換方式了。// 創(chuàng)建shell命令全局可以只創(chuàng)建一次 let currentShell new Shell(true); let taskId 5; let cmd service call activity 24 i32 taskId i32 0; currentShell.exec(cmd);當(dāng)然為了方便大家找這個(gè)值我給大家封裝了個(gè)函數(shù)快速找這個(gè)值。但是我推薦大家在Android 7的環(huán)境下使用此種方式我感覺(jué)別的環(huán)境下大概率不支持。下面這個(gè)函數(shù)的參數(shù)第一個(gè)為第一步獲取的任務(wù)id第二參數(shù)為開(kāi)始查找的值第三個(gè)參數(shù)為結(jié)束查找的值。我一般推薦值為40以?xún)?nèi)這是執(zhí)行shell命令對(duì)系統(tǒng)影響很大40以上的值容易導(dǎo)致崩潰。還有就是需要將代碼中的TARGET_PKG填寫(xiě)獲取的任務(wù)id對(duì)應(yīng)包名我推薦是函數(shù)第二和第三個(gè)參數(shù)不用填寫(xiě)使用默認(rèn)值即可。只需要將第一步中的包名和包名對(duì)應(yīng)的任務(wù)id修改TARGET_PKG值或者通過(guò)參數(shù)傳遞就可以完成下面功能。這個(gè)函數(shù)主要是讓大家參考下我如何找到的這個(gè)值。當(dāng)然如果有小伙伴找到了其他環(huán)境下的這個(gè)值可以評(píng)論區(qū)留言提醒。// 查找操作id function getSwitchAppCallCode(taskId, startTaskId, endTaskId) { if (!startTaskId) { startTaskId 1; } if (!endTaskId) { endTaskId 40; } // 要切換到的目標(biāo)包名和 TaskId let TARGET_PKG com.cyjh.mobileanjian; let TASK_ID taskId; let SAFE_CODES [24, 25, 26, 32, 33, 35]; // 成功后會(huì)保存在這里 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); // 增加等待時(shí)間確保切換完成 if (currentPackage() TARGET_PKG) { foundCode code; break; } } if (!foundCode) { console.log(關(guān)鍵值未找到); // 嘗試 code // 43有問(wèn)題不要嘗試 for (let code startTaskId; code endTaskId; code) { // 拼接命令字符串 let cmd service call activity code i32 TASK_ID i32 0; log(嘗試 code code : cmd); // 執(zhí)行命令true 表示以 root 權(quán)限執(zhí)行 let result shell(cmd, true); console.log(result); // 等500ms 讓切換發(fā)生 sleep(500); // 檢查當(dāng)前前臺(tái)包名 let cur currentPackage(); log(當(dāng)前包: 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(未在范圍內(nèi)找到有效 code請(qǐng)調(diào)整范圍); } }4.getAppName通過(guò)應(yīng)用包名獲取應(yīng)用名需要傳遞應(yīng)用包名一個(gè)參數(shù)參數(shù)類(lèi)型為字符串。一般情況下都是想通過(guò)應(yīng)用名來(lái)獲取應(yīng)用包名通過(guò)應(yīng)用包名大概率可以猜出所屬的應(yīng)用。在無(wú)法確定的情況下這個(gè)函數(shù)更多的是用于應(yīng)用包名和應(yīng)用名之前的驗(yàn)證。console.log(getAppName(com.cyjh.mobileanjian));5.openAppSetting打開(kāi)app的設(shè)置頁(yè)面需要傳應(yīng)用包名一個(gè)參數(shù)參數(shù)類(lèi)型為字符串。返回是否操作成功返回類(lèi)型為boolean一般會(huì)返回true如果打開(kāi)失敗或者未找到應(yīng)用返回false。console.log(openAppSetting(com.cyjh.mobileanjian));6.viewFile與editFileviewFile和editFile函數(shù)分別用于在其他應(yīng)用中查看或者編輯某個(gè)文件均需要傳遞文件路徑一個(gè)參數(shù)參數(shù)類(lèi)型均為字符串。如果沒(méi)有找到打開(kāi)能夠打開(kāi)文件的應(yīng)用拋出ActivityNotException異常。如果這個(gè)類(lèi)型的文件已經(jīng)在系統(tǒng)中設(shè)置了默認(rèn)打開(kāi)方式會(huì)直接打開(kāi)如果沒(méi)有設(shè)置默認(rèn)打開(kāi)方式會(huì)彈出應(yīng)用選擇框。下面就以viewFile函數(shù)為例介紹下這個(gè)功能editFile函數(shù)類(lèi)似。最好通過(guò)捕獲異常的方式來(lái)調(diào)用這個(gè)函數(shù)真正有異??梢赃M(jìn)行特殊處理而不是導(dǎo)致腳本停止。對(duì)于Android環(huán)境下的文件路徑我在文件部分會(huì)進(jìn)行介紹現(xiàn)在知道是一個(gè)完整的文件路徑就好了。我想打開(kāi)個(gè)特殊文件類(lèi)型的文件或者不存在的文件來(lái)觸發(fā)報(bào)錯(cuò)報(bào)錯(cuò)但是這兩種方式都無(wú)法觸發(fā)都會(huì)彈出類(lèi)似的應(yīng)用選擇框。這就說(shuō)明要出發(fā)這個(gè)報(bào)錯(cuò)是非常困難的文件管理器一般都是允許打開(kāi)任何文件的沒(méi)出現(xiàn)的文件類(lèi)型頂多觸發(fā)個(gè)文件選擇。try { app.viewFile(/sdcard/com.py.test/test.txt); } catch (error) { console.log(error); }注意viewFile和editFile函數(shù)只能通過(guò)app對(duì)象調(diào)用沒(méi)有在全局變量里面。我沒(méi)有特殊提醒的函數(shù)代表app對(duì)象調(diào)用和直接調(diào)用函數(shù)兩種方式均可以但是這兩個(gè)函數(shù)只支持app對(duì)象調(diào)用方式。按照正常理解同屬于app部分的函數(shù)調(diào)用方式應(yīng)該一致但是就出現(xiàn)了不同情況。autojs會(huì)出現(xiàn)很多這種類(lèi)似的問(wèn)題如果后面有特殊情況我會(huì)提醒的。7.uninstall卸載應(yīng)用需要傳應(yīng)用包名一個(gè)參數(shù)參數(shù)類(lèi)型為字符串。只是彈出卸載應(yīng)用的確認(rèn)框最后卸載還需要自己確認(rèn)。只能通過(guò)app對(duì)象調(diào)用無(wú)法直接調(diào)用。但是這個(gè)函數(shù)在Android 7的環(huán)境下可以生效但是在安卓9的環(huán)境下不生效。8.openUrl通過(guò)瀏覽器打開(kāi)鏈接需要傳鏈接一個(gè)參數(shù)參數(shù)類(lèi)型為字符串。如果不以“http://”或者“https://”開(kāi)頭會(huì)自動(dòng)拼接“http://”。如果沒(méi)有瀏覽器會(huì)拋出ActivityNotException異常。只能通過(guò)app對(duì)象調(diào)用無(wú)法直接調(diào)用。app.openUrl(www.baidu.com); // 相當(dāng)于 // app.openUrl(http://www.baidu.com);9.sendEmail至getInstalledApps后面這幾個(gè)函數(shù)我很少使用我就不做過(guò)多介紹這些函數(shù)大概率都是通過(guò)app對(duì)象調(diào)用無(wú)法直接調(diào)用。小伙伴如果感興趣可以研究下這部分內(nèi)容尤其是intent和startActivity函數(shù)配合使用。這兩個(gè)函數(shù)配合使用功能很強(qiáng)大前面一些函數(shù)完全可以通過(guò)這兩個(gè)函數(shù)配合使用來(lái)完成。這兩個(gè)函數(shù)也能完成應(yīng)用啟動(dòng)功能需要謹(jǐn)慎這兩個(gè)函數(shù)配合使用導(dǎo)致的內(nèi)存溢出風(fēng)險(xiǎn)。