写这个文章的主要目的是因为我没有跑起来,然后先看看里面具体如何实现的,然后明确我的问题是啥?如果不明白这个是啥可以看这里:
谷歌官方发布应用遍历工具 App Crawler
我画了一个简单的类图,方便查看
程序入口类是这个 androidx.test.tools.crawler.launcher.CrawlLauncher
, 差不多主要方法都在里面了 CrawlSetup
ExternalCrawlSetup
是工具的配置信息,基本所有的配置都在里面。
这里简单说一下逻辑:
launchCrawl
util.AppPackageNameExtractor
util.AdbExecutor
--ui-automator-mode
或 --instant-apps-mode
会对 crawler_app.apk
重新签名, 然后比较坑的是不会重新签名 sub 文件,好像会造成启动不起来apk
文件,一样会对 apk 重新签名installCrawlerAndApp(这里面会对是否有 dump 权限检查,在 23 以上)
sub
cleanupDevice
/sdcard/app_firebase_test_lab
/sdcard/robo_tmp_files
crawler_app.apk
最后就是开始遍历
public CrawlLauncher(String[] crawlParameters) {
this.crawlSetup.processCrawlParameters(crawlParameters);
this.appPackageNameExtractor = new AppPackageNameExtractor(this.crawlSetup);
this.apkSigner = new ApkSigner(this.crawlSetup);
this.crawlerRepacker = new CrawlerRepacker(this.crawlSetup, this.apkSigner);
this.adbExecutor = new AdbExecutor(this.crawlSetup);
this.deviceApiLevel = this.adbExecutor.getDeviceApiLevel();
}
public static void main(String[] args) throws CrawlerRepackingException, ApkSigningException {
new CrawlLauncher(args).launchCrawl(false);
}
public CrawlSetup getCrawlSetup() {
return this.crawlSetup;
}
public AdbExecutor getAdbExecutor() {
return this.adbExecutor;
}
public void launchCrawl(boolean isHostGuidedCrawl) throws ApkSigningException, CrawlerRepackingException {
Path signedRepackedCrawlerApkFilePath;
String appPackageName = this.crawlSetup.getAppPackageName();
if (appPackageName.isEmpty()) {
appPackageName = this.appPackageNameExtractor.extractAppPackageName();
}
Logger.atInfo().log("Preparing to crawl %s", appPackageName);
if (this.crawlSetup.isWakeDevice()) {
this.adbExecutor.wakeDevice();
}
if (!isHostGuidedCrawl) {
FileUtil.cleanupDirectory(this.crawlSetup.getOutputDirectoryPath());
}
this.crawlSetup.buildCrawler();
if (this.crawlSetup.isUiAutomatorMode() || this.crawlSetup.isInstantAppsMode()) {
signedRepackedCrawlerApkFilePath = this.crawlSetup.getCrawlerAppApkPath();
} else {
signedRepackedCrawlerApkFilePath = this.crawlerRepacker.repackAndSignCrawlerApp(appPackageName);
}
Optional<Path> appApkFilePath = Optional.empty();
if (!this.crawlSetup.getApkFilePath().isEmpty()) {
appApkFilePath = Optional.of(Paths.get(this.crawlSetup.getApkFilePath(), new String[0]));
if (!(this.crawlSetup.isUiAutomatorMode() || this.crawlSetup.isInstantAppsMode())) {
appApkFilePath = Optional.of(this.apkSigner.sign((Path) appApkFilePath.get()));
}
}
installCrawlerAndApp(appPackageName, signedRepackedCrawlerApkFilePath, appApkFilePath);
LogcatRecorder logcatRecorder = null;
if (!isHostGuidedCrawl) {
if (this.crawlSetup.isPauseBeforeCrawl()) {
Logger.atInfo().log("Press Enter to start the crawl", new Object[0]);
try {
System.in.read();
} catch (Exception e) {
}
}
logcatRecorder = new LogcatRecorder(this.adbExecutor, this.crawlSetup.getOutputDirectoryPath(), appPackageName);
logcatRecorder.start();
new VideocatRecorder(this.adbExecutor, this.crawlSetup, appPackageName, this.deviceApiLevel).start();
}
startCrawler(appPackageName);
this.adbExecutor.stopReadingInput();
if (!isHostGuidedCrawl) {
processLogcat(logcatRecorder);
processCrawlOutput();
}
}
private static /* synthetic */ boolean lambda$processLogcat$0(LogcatFinding finding) {
return finding.type() == LogcatFindingType.NON_SDK_API_USED;
}
private static /* synthetic */ boolean lambda$processLogcat$1(LogcatFinding f) {
return f.type() == LogcatFindingType.FATAL_EXCEPTION || f.type() == LogcatFindingType.NATIVE_CRASH;
}
private void installCrawlerAndApp(String appPackageName, Path crawlerApkFilePath, Optional<Path> appApkFilePath) {
boolean grantPermissionsOnInstall;
cleanupDevice(appPackageName);
if (this.deviceApiLevel >= 23) {
grantPermissionsOnInstall = true;
} else {
grantPermissionsOnInstall = false;
}
this.adbExecutor.installApp(grantPermissionsOnInstall, crawlerApkFilePath.toAbsolutePath().toString());
this.adbExecutor.execute("shell", "pm", "grant", ConfigConstants.CRAWLER_PACKAGE_ID, "android.permission.DUMP");
this.adbExecutor.installApp(grantPermissionsOnInstall, "-r", this.crawlSetup.getCrawlerStubappApk().getAbsolutePath());
if (appApkFilePath.isPresent()) {
this.adbExecutor.installApp(grantPermissionsOnInstall, ((Path) appApkFilePath.get()).toAbsolutePath().toString());
}
}
private void cleanupDevice(String appPackageName) {
this.adbExecutor.execute("shell", "am", "force-stop", appPackageName);
this.adbExecutor.execute("shell", "rm", "-rf", CrawlSetup.DEVICE_OUTPUT);
this.adbExecutor.execute("shell", "rm", "-rf", CrawlSetup.ROBO_TEMP_FILES);
this.adbExecutor.uninstallApp(ConfigConstants.CRAWLER_PACKAGE_ID);
if (!this.crawlSetup.getApkFilePath().isEmpty()) {
this.adbExecutor.uninstallApp(appPackageName);
}
}
private void startCrawler(String appPackageName) {
List<String> executionOptions = getExecutionOptions(appPackageName);
List startServiceCommand = new ArrayList(Arrays.asList(new String[]{"shell", "am", "startservice"}));
startServiceCommand.addAll(executionOptions);
startServiceCommand.add("-n");
startServiceCommand.add("androidx.test.tools.crawler/androidx.test.tools.crawler.controller.CrawlDriver");
this.adbExecutor.execute(startServiceCommand);
List instrumentCommand = new ArrayList(Arrays.asList(new String[]{"shell", "am", "instrument", "--no-window-animation", "-w", "-r"}));
instrumentCommand.addAll(executionOptions);
addExecutionOption("class", "androidx.test.tools.crawler.CrawlPlatform", instrumentCommand);
instrumentCommand.add("androidx.test.tools.crawler/androidx.test.runner.AndroidJUnitRunner");
Logger.atInfo().log("Crawl started.", new Object[0]);
this.adbExecutor.execute(instrumentCommand);
this.adbExecutor.execute("shell", "am", "instrument", "-w", "-r", "androidx.test.tools.crawler/.CrawlMonitor");
Logger.atInfo().log("Crawl finished.", new Object[0]);
}
private List<String> getExecutionOptions(String appPackageName) {
List<String> options = new ArrayList();
addExecutionOption(ConfigConstants.ROBO_V2_CRAWL_DURATION_FLAG, String.valueOf(this.crawlSetup.getCrawlTimeoutSeconds()), options);
addExecutionOption(ConfigConstants.ROBO_V2_APP_PACKAGE_FLAG, appPackageName, options);
addExecutionOption("appListener", "androidx.test.tools.crawler.SignaturePatchingCallback", options);
addExecutionOption("disableAnalytics", "true", options);
addExecutionOption(ConfigConstants.ROBO_V2_EXECUTION_ID_FLAG, UUID.randomUUID().toString(), options);
addExecutionOption("dataDir", this.crawlSetup.getOutputDirectoryPath().toAbsolutePath().toString(), options);
if (this.crawlSetup.isTestAccessibility()) {
addExecutionOption(ConfigConstants.ROBO_V2_TEST_ACCESSIBILITY, "true", options);
}
if (this.crawlSetup.isInstantAppsMode()) {
addExecutionOption(ConfigConstants.ROBO_V2_INSTANT_APPS_MODE, "true", options);
}
addExecutionOption(ConfigConstants.ROBO_V2_CRAWL_DRIVER_INITIALIZER_CLASS_FLAG, this.crawlSetup.getCrawlDriverInitializerClass(), options);
Properties experiments = this.crawlSetup.getExperiments();
for (String experimentName : experiments.stringPropertyNames()) {
addExecutionOption(experimentName, experiments.getProperty(experimentName), options);
}
return options;
}
private static void addExecutionOption(String optionName, String optionValue, List<String> options) {
options.add("-e");
options.add(optionName);
options.add(optionValue);
}
private void processCrawlOutput() {
this.adbExecutor.execute("pull", CrawlSetup.DEVICE_OUTPUT, this.crawlSetup.getOutputDirectoryPath().toAbsolutePath().toString());
Optional<File> crawlOutputsBinaryProtoFile = getCrawlOutputsBinaryProtoFile();
if (crawlOutputsBinaryProtoFile.isPresent()) {
Logger.atInfo().log("Converting output files", new Object[0]);
Appendable fileWriter;
try {
MessageOrBuilder crawlOutputs = CrawlOutputs.parseFrom(new FileInputStream((File) crawlOutputsBinaryProtoFile.get()));
fileWriter = Files.newBufferedWriter(Paths.get(this.crawlSetup.getCrawlOutputsTextProtoPath(), new String[0]), StandardCharsets.UTF_8, new OpenOption[0]);
TextFormat.print(crawlOutputs, fileWriter);
if (fileWriter != null) {
fileWriter.close();
}
} catch (Exception e) {
throw new RuntimeException("Failed to convert output files", e);
} catch (Throwable th) {
th.addSuppressed(th);
}
}
Logger.atInfo().log("The output directory is %s", this.crawlSetup.getOutputDirectoryPath().toAbsolutePath().toString());
}
private Optional<File> getCrawlOutputsBinaryProtoFile() {
File crawlOutputsBinaryProtoFile = new File(this.crawlSetup.getCrawlOutputsBinaryProtoPath());
int waitForCrawlOutputsAttempts = 30;
while (!crawlOutputsBinaryProtoFile.exists() && waitForCrawlOutputsAttempts > 0) {
Logger.atDebug().log("Waiting for crawl outputs proto file %s", crawlOutputsBinaryProtoFile);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Logger.atWarning().log("Interrupted while waiting for outputs proto file %s", crawlOutputsBinaryProtoFile);
}
waitForCrawlOutputsAttempts--;
}
if (crawlOutputsBinaryProtoFile.exists()) {
return Optional.of(crawlOutputsBinaryProtoFile);
}
Logger.atWarning().log("Timed out waiting for crawl outputs proto file %s", crawlOutputsBinaryProtoFile);
return Optional.empty();
}
最后放上反编译的代码,虽然关系没有了,但是还可以阅读的
链接: https://pan.baidu.com/s/17ijQDr83gClopV7sAagFhQ 提取码: gyjy 复制这段内容后打开百度网盘手机 App,操作更方便哦
然后顺便求一个测试开发工程师的岗位。 目标地点杭州。多年安卓开发经验,对测试相关信息有一定的了解,希望在质量领域做点事。