)
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函數配合使用。這兩個函數配合使用功能很強大前面一些函數完全可以通過這兩個函數配合使用來完成。這兩個函數也能完成應用啟動功能需要謹慎這兩個函數配合使用導致的內存溢出風險。