本文为稀土技术社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!

0x1、导言

【杰哥带你玩转Android自动化】学穿:ADB

Hi,我是杰哥,在上一节的《开篇漫谈》中说到这样一句话:

一切Android主动化框架和工具中 操作Android设备的功能完结 都根据 adb无障碍服务AccessibilityService

本节咱们来学习下前者,学习路线安排如下:

  • 简略了解下ADB的概念,是什么?由哪几部分组成?作业原理是咋样的?
  • ADB环境装备,以及两个常见问题的处理(adb端口占用,adb devices无法辨认设备)
  • 学习ADB指令的语法,把握一些主动化操作中巨常用的ADB指令;
  • 了解如安在Android App中履行adb指令;
  • 学习在PC端编写Python程序调用adb指令;
  • 实战:某作业软件打卡主动化;

就不废话水字数了,我直接开始~

【杰哥带你玩转Android自动化】学穿:ADB


0x2、ADB概念

① ADB是什么

ADB (Android Debug Bridge) ,译作 安卓调试桥,一个能让你 与Android设备进行通讯指令行工具

说人话便是:你能够经过它,在指令行输入指令操控Android设备。


② ADB架构

ADB是一种C/S架构的运用程序,由三个部分组成:

  • 服务端PC端的adb server → 运转在PC端的后台进程,用于:
  • 检测USB端口感知设备衔接与拔除;
  • 模仿器实例的发动与中止;
  • 将adb client的请求经过usb或tcp的办法发送到对应的adbd进程;
  • 客户端PC端的adb client → 首要用于发送指令
  • 解析像:push、shell、install等指令的参数,做必要预处理,然后转移为指令或数据,发送给adb server;
  • 看护进程手机端的adbd → 由init进程发动
  • 处理来自 adb server的指令行请求,获取对应Android设备的信息,再将成果返回给adb server;

作业原理

  • 发动adb客户端 → 检查是否有 adb服务端进程 在运转 → 没有的话发动一个;
  • adb服务端进程发动后会 与本地TCP端口5037绑定,并 监听adb客户端发出的指令
  • 服务端扫描 5555-5585 之间的 奇数端口 查找设备/模仿器,一旦发现 adbd进程 便会与相应端口树立衔接;
  • 注:每个adbd会占用两个PC端口,奇数 用于 adb衔接偶数 用于 指令行衔接,如5554和5555端口是一对;
  • adb服务端与一切设备均树立衔接后,你便能运用adb指令访问这些设备;

【杰哥带你玩转Android自动化】学穿:ADB

通讯流程

【杰哥带你玩转Android自动化】学穿:ADB

关于原理和过程,大约了解,心理有数就行,看不懂也不影响你后边的学习。当然假如你对更深层的原理或源码感兴趣,能够参阅下《adb和adbd剖析》,笔者就不往下卷了~

【杰哥带你玩转Android自动化】学穿:ADB


0x3、ADB环境装备

假如您是 尊贵的Android开发,运用 Android Studio,并装备过环境变量,就不必再配了。能够直接翻开指令行/终端键入 adb version 验证:

【杰哥带你玩转Android自动化】学穿:ADB

能够看到输出了:adb的版别信息可履行文件地点的途径

假如您不是Android开发也不打紧,到 官网 下个 SDK Platform-Tools

【杰哥带你玩转Android自动化】学穿:ADB

下载完结解压后能够看到:adb、fastboot等工具包

【杰哥带你玩转Android自动化】学穿:ADB

接着仿制下文件夹途径,在 体系环境变量PATH 中加上它,然后就能够在指令行里直接运用adb啦~

【杰哥带你玩转Android自动化】学穿:ADB

相同键入 adb version 验证:

【杰哥带你玩转Android自动化】学穿:ADB

能够看到adb版别信息和可履行文件的途径都发生了改动,阐明装备生效。


常见问题一:adb端口被占用

一般发生在Windows体系,假如你电脑装置了一些 XX手机帮手 的软件,那在履行adb指令时,很大约率会遇到这个问题:

【杰哥带你玩转Android自动化】学穿:ADB

阐明你的:5037端口被占用了,上面说过这个端口是留给adb server运用的,处理办法有两种:

办法一干掉占用进程 (主张)

键入:netstat -ano | findstr "5037",获取 占用端口的进程PID,如:

C:\Users\xxx>netstat -aon|findstr 5037
 TCP    127.0.0.1:5037         0.0.0.0:0              LISTENING       2908

键入:tasklist /fi "PID eq 2908",检查 进程PID对应的进程,如:

C:\Users\xxx>netstat -aon|findstr 5037
映像称号                       PID 会话名              会话#       内存运用
========================= ======== ================ =========== ============
xxx.exe                       2908 Console                    1     11,292 K

键入:taskkill /pid 2908 /f杀掉占用端口的进程,如:

C:\Users\xxx>taskkill /pid 2908 /f
成功: 已停止 PID 为 2908 的进程。

最终键入adb相关指令,如 adb devices,即可发动adb server进程,作用图如下:

【杰哥带你玩转Android自动化】学穿:ADB


办法二修正adb server端口

总有一些毒瘤进程,或许刚干死又重启了,办法一纷歧定能生效,打不过,躲得过,除了把毒瘤运用卸载外,还能够考虑下修正 adb server的端口号

主张选一个 五位的端口号(10000-65535),没那么简略重复,接着在 体系环境变量 中点击新建环境变量,变量名为 ANDROID_ADB_SERVER_PORT 变量值为端口号,如:

【杰哥带你玩转Android自动化】学穿:ADB

此刻关掉指令行再次翻开进入adb指令,能够看到端口号现已修正为10024了:

【杰哥带你玩转Android自动化】学穿:ADB

假如不是由于毒瘤,朴实想改下端口,能够键入 adb kill-server 把adb server干掉,装备完结后,再键入 adb start-server 发动 adb server。

Tips:Linux、Mac体系直接终端输入 export $ANDROID_ADB_SERVER_PORT = 自定义端口 即可设置。


常见问题二:adb devices无法辨认设备

问题描绘:手机连上电脑,键入adb devices,却没输出任何设备?

回答:确定手机 USB调试 开了吗?敞开办法如下 (不同手机体系或许存在差异,可自行查找关键字):

初次衔接,点击手机 设置体系信息 → 点击版别号多次直至出现 您已处于开发者模式 → 返回找到 开发者模式 → 找到 USB调试 敞开,然后手机一般会弹个 授权的对话框,授权就好。此刻再键入adb devices看看设备是否显示。

当然,假如你在授权的时候,手滑点了拒绝,此刻键入adb devices时,设备的状态会显示为 unauthorized,再次拔插手机,授权窗口都不会再弹了,处理办法如下:

  • adb kill-server 关掉adb服务,拔掉手机;
  • 找到并删去电脑中的两个装备文件:/用户名/.android/adbkey/用户名/.android/adbkey.pub
  • adb start-server 发动adb服务,再插手机,授权弹窗应该就出来了;

假如运用了上述办法仍是无法辨认,那或许是 USB接口的问题驱动问题,能够 换个手机或者换条线 试试,假如正常阐明不是USB接口问题。手机官网搜下对应手机型号的驱动,装置后试试。别的,重启试试 有时也包治百病~


0x4、ADB指令详解

adb完好指令语法如下

adb [-d|-e|-s <serialNumber>] <command>

假如 只要一个设备/模仿器衔接PC,不必加中括号里的参数,当有多个时,才需求经过这些参数 指定方针设备

  • -d → 指定当时仅有经过usb衔接的Android设备为指令方针;
  • -e → 指定当时仅有运转的模仿器为指令方针;
  • -s serialNumber → 指定对应serialNumber号的设备/模仿器为指令方针,最常用

上面 adb devices 输出的 8c8f689e 便是我当时衔接的手机 序列号,除了真机还有 模仿器无线衔接设备,如:

emulator-5554	device
10.129.164.6:5555	device

别的,adb指令 区分权限,有些指令需求 root权限 才干履行!

假如你手机现已Root了,想给adbd授予Root权限,下述办法二选一:

  • ① 键入 adb root,假如正常输出 restarting adbd as root,键入 adb shell
  • ② 键入 adb shell,输入 su

当然假如想取消adbd的root权限,也能够键入 adb unroot

对了,有些手机即便Root了,也或许无法让adbd以root权限履行,如三星的部分机型,会提示 adbd cannot run as root in production builds,能够先装置 adbd Insecure,然后再次测验。

接着罗列笔者觉得主动化操作最常用的指令,更多指令可到 ADB官方文档 或 mzlogin/awesome-adb 自行查阅~

① 检查前台Activity

指令:(能够借此拿到运用包名和当时Activity名)

# 进入Android终端的指令行模式
adb shell 
# 输出前台Activity
dumpsys activity activities | grep mResumedActivity

成果

【杰哥带你玩转Android自动化】学穿:ADB

② 发动运用/调起Activity

指令

adb shell
# 不指定Activity称号发动,即发动主Activity
monkey -p <packagename> -c android.intent.category.LAUNCHER 1
# 指定发动Activity名 (需求root权限)
am start -n <packagename>/<activity类名>

成果

【杰哥带你玩转Android自动化】学穿:ADB

③ 强行中止运用

指令

adb shell am force-stop <packagename>

成果

【杰哥带你玩转Android自动化】学穿:ADB

④ 模仿按键/输入/滑动/点击

指令 (完好Keycode列表可见官网:KeyEvent):

adb shell
# 模仿按键 (Home-3,返回-4,电源-26,亮屏-224、熄屏-223,切换运用-187,小键盘删去-67),如点击Home键:
input keyevent 3
# 在焦点处于某文本框时,运用input指令输入文本
input text Hello
# 滑动,从开始坐标点滑动到结束坐标点,如上滑(300,1000) → (300,500):
input swipe 300 1000 300 500
# 点击坐标点
input tap 500 500

Tipsadb默许不支撑Unicode编码所以无法input中文内容,即便你默许运用了支撑中文的输入法也不行,处理办法如下:

  • 下载装置 ADBKeyBoard;
  • ② 装置完后依次翻开:手机设置 → 语言和输入法 → 键盘 → 虚拟键盘 → 管理键盘 → 启用ADB keyboard;
  • 默许输入法 设置为ADB keyboard (比方原生体系点击右下角小键盘设置)

【杰哥带你玩转Android自动化】学穿:ADB

  • ④ 接着运用这个指令即可输入中文 (其他语言也能够):adb shell am broadcast -a ADB_INPUT_TEXT --es msg '中文输入'

⑤ 截图

指令

adb shell
# 截图
screencap -p /sdcard/sc.png
# 退出adb shell
exit
# 导出到电脑
adb pull /sdcard/sc.png

别的,还有一种一行指令截图并保存到电脑的办法:

# Mac
adb shell screencap -p | gsed "s/\r$//" > sc.png
# Linux和Windows
adb shell screencap -p | sed "s/\r$//" > sc.png
# Tips:上面的截图办法,Windows能获取图片,可是打不开,能够用下述办法获取:
adb exec-out screencap -p > sc.png

⑥ 检查分辨率

指令

adb shell wm size

成果

【杰哥带你玩转Android自动化】学穿:ADB


0x5、Android App中调用adb指令

不是很引荐 这种办法弄主动化,第一个是 权限问题,假如你的主动化设备是有 Root权限的真机或模仿器,在考虑这种吧。

别的一个问题是 保活问题,一般咱们的主动化场景,都是 守时去做一些工作,比方我想每天早上8点半主动打卡,那我需求写一个后台服务,然后让它 一向挂着, 到点触发主动打卡的操作。这个 保活 在PC上很简略完结,但在手机里却很难确保…

写个简略的调用示例吧,有需求的改改就能用,先是布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <androidx.appcompat.widget.AppCompatEditText
        android:id="@+id/et_input"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入adb指令" />
    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/bt_run"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="履行" />
    <androidx.appcompat.widget.AppCompatTextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="输出成果:" />
    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tv_output"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="" />
</LinearLayout>

Android中的调用代码:

class TestCmdActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test_cmd)
        findViewById<TextView>(R.id.et_input).text =
            "monkey -p com.tencent.mobileqq -c android.intent.category.LAUNCHER 1"
        findViewById<Button>(R.id.bt_run).setOnClickListener {
            val result = execCmd(findViewById<EditText>(R.id.et_input).text.toString())
            if (result != null) findViewById<TextView>(R.id.tv_output).text = result
        }
    }
    /**
     * 履行普通指令 (不需求adb shell)
     * */
    private fun execCmd(cmd: String?): String? {
        return if (cmd.isNullOrBlank()) {
            shortToast("指令不能为空")
            null
        } else {
            try {
                val sb = StringBuffer()
                val process = Runtime.getRuntime().exec(cmd)
                val inputStream = process.inputStream
                val bufferedReader = BufferedReader(InputStreamReader(inputStream))
                val buff = CharArray(1024)
                var ch: Int
                while (true) {
                    ch = bufferedReader.read(buff)
                    if (ch == -1) break
                    sb.append(buff, 0, ch)
                }
                process.waitFor()
                bufferedReader.close()
                return sb.toString()
            } catch (e: IOException) {
                e.toString()
            }
        }
    }
    /**
     * 履行需求Root权限的指令
     * */
    private fun execCmdRoot(cmd: String?): String? {
        return if (cmd.isNullOrBlank()) {
            shortToast("指令不能为空")
            null
        } else {
            val successMsg = StringBuffer()
            val errorMsg = StringBuffer()
            var process: Process? = null
            var successResult: BufferedReader? = null
            var errorResult: BufferedReader? = null
            var os: DataOutputStream? = null
            try {
                process = Runtime.getRuntime().exec("su")
                os = DataOutputStream(process.outputStream)
                os.write(cmd.toByteArray())
                os.writeBytes("\n")
                os.flush()
                val result = process.waitFor()
                successResult = BufferedReader(InputStreamReader(process.inputStream))
                errorResult = BufferedReader(InputStreamReader(process.errorStream))
                var s: String?
                while (true) {
                    s = successResult.readLine()
                    if (s == null) break
                    successMsg.append(s)
                }
                while (true) {
                    s = errorResult.readLine()
                    if (s == null) break
                    successMsg.append(s)
                }
                return successMsg.toString() + "\n\n" + errorMsg.toString()
            } catch (e: IOException) {
                e.toString()
            } finally {
                process?.destroy()
                os?.close()
                successResult?.close()
                errorResult?.close()
            }
        }
    }
}

运转成果如下

【杰哥带你玩转Android自动化】学穿:ADB

虽然操控台看到有输出,但实际上并没有引发QQ,说到底仍是权限的问题。本想试下su提权的,成果我手机的Magsik出问题了,获取su权限直接卡死…坑仍是挺多的,所以不太主张新手拿这个玩主动化哈~


0x6、Python调用adb指令 (引荐)

如题,便是编写Python脚原本调用adb指令,个人比较引荐这种办法,可玩性十分强,能够:

借助PC做守时使命 (保活)、运用PC强大性能进行图片处理、OCR辨认、信息上报、多机群控等;

不太懂Python?没关系,杰哥帮你把主动化adb指令都封装一波,你依照你的逻辑直接调办法就行,跟玩积木相同~

【杰哥带你玩转Android自动化】学穿:ADB

所谓的封装,中心便是经过 subprocess 模块调一下指令行而已,十分简略,直接给出完好代码:

import subprocess
import re
from enum import Enum
import time
import os
pkg_act_pattern = re.compile(".* (.*?)/(.*?) ", re.S)  # 获取包名和Activity名的正则
chinese_pattern = re.compile("[\u4e00-\u9fa5]", re.S)  # 挑选中文的正则
size_pattern = re.compile(r"(\d+)x(\d+)", re.S)  # 获取屏幕尺度的正则
t = time.time()
class KeyEvent(Enum):
    """
    按键事件的枚举
    """
    HOME = 3
    BACK = 4
    POWER = 26
    SCREEN_ON = 224
    SCREEN_OFF = 223
    SWITCH_APP = 187
    DELETE = 67
def start_cmd(cmd):
    """
    履行指令
    :param cmd: 指令字符串
    :return: 履行后的输出成果列表
    """
    print(cmd)
    proc = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE)
    return proc.stdout.readlines()
def start_app(package_name):
    """
    发动APP
    :param package_name: 运用包名
    :return: 履行成果字符串
    """
    return analysis_result(start_cmd(f'adb shell monkey -p %s -c android.intent.category.LAUNCHER 1' % package_name))
def kill_app(package_name):
    """
    杀掉APP
    :param package_name: 运用包名
    :return:
    """
    return analysis_result(start_cmd(f'adb shell am force-stop %s' % package_name))
def current_pkg_activity():
    """
    获取当时页面的包名和Activity类名
    :return:
    """
    result = analysis_result(start_cmd(f'adb shell dumpsys activity activities | grep mResumedActivity'))
    print(result)
    if result is not None and len(result) > 0:
        match_result = re.search(pkg_act_pattern, result)
        if match_result:
            return match_result.group(1), match_result.group(2)
    return None
def key_event(event):
    """
    模仿按键
    :param event: 按键类型
    :return:
    """
    return analysis_result(start_cmd(f'adb shell input keyevent %d' % event.value))
def input_text(text):
    """
    当焦点处于某文本框时,模仿输入文本
    :param text: 输入文本内容
    :return:
    """
    match_result = re.findall(chinese_pattern, text)
    # 判别是否包括中文
    if len(match_result) > 0:
        return analysis_result(start_cmd(f'adb shell am broadcast -a ADB_INPUT_TEXT --es msg %s' % text))
    # 不包括中文调用原指令
    return analysis_result(start_cmd(f'adb shell input text %s' % text))
def swipe(start_x, start_y, end_x, end_y):
    """
    滑动,从开始坐标点滑动到结尾坐标
    :param start_x: 开始坐标点x坐标
    :param start_y: 开始坐标点y坐标
    :param end_x: 结尾坐标点x坐标
    :param end_y: 结尾坐标点y坐标
    :return:
    """
    return analysis_result(start_cmd(f'adb shell input swipe %d %d %d %d' % (start_x, start_y, end_x, end_y)))
def click(x, y):
    """
    点击坐标点
    :param x:
    :param y:
    :return:
    """
    return analysis_result(start_cmd(f'adb shell input tap %d %d' % (x, y)))
def analysis_result(lines):
    """
    将履行成果列表转换外字符串输出
    :param lines: 履行成果列表
    :return:
    """
    result = ''
    for line in lines:
        result += line.decode(encoding='utf8')
    return result
def screenshot(save_dir=None):
    """
    获取手机截图,先截图后拉取(一步达成的办法好像有权限问题)
    :return: 截图文件的完好途径
    """
    sc_name = "%d.png" % (int(round(t * 1000)))
    start_cmd('adb shell screencap /sdcard/%s' % sc_name)
    sc_path = os.path.join(os.getcwd() if save_dir is None else save_dir, sc_name)
    start_cmd('adb pull /sdcard/%s %s' % (sc_name, sc_path))
    return sc_path
def screen_size():
    """
    获取屏幕分辨率
    :return: 屏幕的宽和高
    """
    size_result = re.search(size_pattern, analysis_result(start_cmd(f'adb shell wm size')))
    if size_result:
        return size_result.group(1), size_result.group(2)
if __name__ == '__main__':
    # 能够在这里写测验代码
    print(screen_size())

读者能够直接copy代码,在写测验代码那里,调下办法试试看~


0x7、实战:某作业软件打卡主动化

上面把常用的主动化操作都封装了,接着写个超简略的事例来练练手~

【杰哥带你玩转Android自动化】学穿:ADB

早上上班最怕啥?肯定是 迟到 啊,那最绝望的场景是什么?关于笔者来说莫过于:

9:00,明明现已到打卡范围了,但却由于没网一向Loading打不上,我也不知道是地铁的问题仍是我手机的问题,只能反复翻开封闭:飞行模式、定位、作业软件,直至时间变成了9:01,然后弹出 迟到是否打卡的提示,那一瞬间 千言万语化作一句话

【杰哥带你玩转Android自动化】学穿:ADB

每当此刻都会想,怎么改动这种状况?第一反响想到的是 改定位,TM直接在家里就打上卡,这不乐滋滋。

完结思路

  • ① 编写Xposed插件,Hook获取定位相关的API,直接返回公司邻近的经纬度;
  • ② 换个支撑 位置穿越 的手机,比方联想就支撑改动定位地点;

但这两种计划都 很有或许被检测出来,估计是 APP运转环境相关的检测 (手机Root了,App被Hook了等) + 风控,之前老东家用这个被人事正告过 (人事那儿能看到XX反常打卡,运用定位软件啥的)。

综上,这种完结办法不简略、不稳、还有高风险,妥妥滴扔掉,有没有简略、安稳、风险较低的计划呢?

当然有:敞开极速打卡 + 编写主动化脚本,敞开办法很简略,打卡 → 设置 → 极速打卡

【杰哥带你玩转Android自动化】学穿:ADB

如图,敞开极速打卡后,你在打卡范围内翻开作业软件,就能主动打卡。啧啧,那我直接:

手机一向接着电脑守时到点adb指令翻开作业软件

【杰哥带你玩转Android自动化】学穿:ADB

是的,便是这么简略,接着一步步来完结,难点的话就一个,守时使命,假如是Linux体系,直接用 CrontabCelery 就好,惋惜笔者的电脑是Windows,两种完结办法:

① 手动装备守时使命

便是手动添加守时使命,到点履行脚本,先把翻开作业软件的脚本写出来:

import adb_util
if __name__ == '__main__':
    # 翻开作业软件,然后履行这句代码,拿到运用的包名
    # print(adb_util.current_pkg_activity()[0])
    package_name = "作业运用的包名贴到这"
    adb_util.start_app(package_name)

开始菜单查找:使命计划程序 → 翻开后点击 → 使命计划程序库创建基本使命

【杰哥带你玩转Android自动化】学穿:ADB

填下使命称号和描绘,点下一步:

【杰哥带你玩转Android自动化】学穿:ADB

触发器挑选每天,点下一步:

【杰哥带你玩转Android自动化】学穿:ADB

修改触发时间,点下一步:

【杰哥带你玩转Android自动化】学穿:ADB

挑选发动程序,点下一步:

【杰哥带你玩转Android自动化】学穿:ADB

挑选解释器地点途径,填写打卡脚本途径,点下一步:

【杰哥带你玩转Android自动化】学穿:ADB

然后能够看到使命的摘要,点击完结:

【杰哥带你玩转Android自动化】学穿:ADB

接着就能够在使命列表看到咱们新建的使命了:

【杰哥带你玩转Android自动化】学穿:ADB

能够右键运转试试运转作用~

【杰哥带你玩转Android自动化】学穿:ADB

接着只需把手机插上,静待明天到点主动打卡~ (记住关掉锁屏、屏幕保持常亮、亮度调最低)


② 运用schedule库

schedule是一个轻量级的使命调度库,能够完结每分钟、每小时、每天、周几、特定日期的守时使命。

直接指令行键入 pip install schedule 装置模块,然后写出守时代码:

import adb_util
import schedule
import time
package_name = "作业软件包名"
def clock_in():
    adb_util.start_app(package_name)
if __name__ == '__main__':
    schedule.every().day.at("08:30").do(clock_in)
    while True:
        schedule.run_pending()
        time.sleep(1)

竟简略如斯!!!别的,以 python.exe xxx.py 办法发动脚本会弹出一个黑色的指令行操控行窗口,关掉的话会导致程序中止运转。假如觉得碍眼,其实能够运用 pythonw.exe xxx.py,以标准WIN32 GUI办法发动,无窗口的Python可履行程序,代码在后台履行。

封闭的话,直接翻开 使命管理器,定位到 Python,然后完结进程即可~

【杰哥带你玩转Android自动化】学穿:ADB


0x8、小结

不知不觉就到文尾,读者是否还意犹未尽?本节显示科普了一波ADB相关的姿态,然后带着我们写了一个主动打卡的jio本。

【杰哥带你玩转Android自动化】学穿:ADB

虽然 粗陋勉强能用,不过问题也是多多,比方这些:

  • 每天8.30按时打卡,这也太假了吧?
  • adb指令的办法发动,作业软件会不会检测到,然后判定为反常打卡?
  • 守时履行使命,可是打卡成没成功我不知道啊,假如公司断电、断网了?
  • 我下班也要看作业软件,回信息,一向放公司挂着不太实际,能不能搞个主动登录啊?
  • …等等

行吧,那下一节就结合文字OCR和其它工具,来完善这个打卡jio本,让它变得更高大上一些~

声明:主动打卡脚本只是个练手事例,假如真的拿来运用 形成的结果运用人自担,笔者自己宁愿迟到也不会用,做人仍是要诚实,常常迟到的同学主张早上早点出门,早上地铁不挤仍是很香的~


参阅文献

  • ADB官方文档

  • 图解ADB作业原理

  • ADB详解