介绍脱离PC机执行uiautomator2脚本
前面介绍过了python uiautomator2的大概情况,今天主要介绍一下怎么在脱离PC机的情况下执行uiautomator2的脚本。
现在说python uiautomator2的脚本怎么脱机执行。
要在Android上配置python运行环境,有以下几个可以选择:
1. Qpython
2. pydroid3
3. Termux
根据其他网友的踩坑经验,推荐使用Termux来配置环境,步骤如下:
1. 下载Termux,通过adb安装到手机上;
2. 启动Termux,在Termux里执行更新资源的命令:
pkg update
pkg updrade
3. 安装python:
pkg install python(安装完成后可以执行python,检查是否成功)
4. 安装运行UIAutomator2需要的库:
apt install libxml libxslt
apt install libjpeg-turbo
5. 安装UIAutomator2库:
pip install –upgrade –pre uiautomator2
虽然上面已经配置了很多东西,但是实际情况是即使已经完成了上面的配置,还不能马上执行脚本。
6. 需要在PC上初始化手机上的atx-agent,就是在连接了手机时,执行命令:
python –m uiautomator2 init
7. 由于步骤6默认安装的atx-agent位置是/data/local/tmp/atx-agent,而Termux访问不了这个文件,两种解决方案:
7.1 每次启动手机时都在PC端启动它,命令:
adb shell /data/local/tmp/atx-agent server –d #启动并后台运行
7.2 把atx-agent移动到Termux能访问的位置,比如$HOME下面,赋予权限755
上面两种方案的区别是通过adb启动,atx-agent获得的是root权限,可以唤起app而无需鉴权,如果在Termux启动,相当于在一个子系统启动,权限等同于Termux,不能操作其他app。
8. 完成以上的准备,就可以在Android执行python脚本了,假设脚本名称是test_script.py,且已经存储到了Android系统中,可以通过执行命令启动:
python test_script.py
最后提醒一点,在pc上执行和在Android上执行,ut.connect()的参数不一样,在Android上需要这样写:driver = ut.connect(“127.0.0.1:7912”)
看到这里是不是不想玩python uiautomator2脱机执行了?
明明我只是想刷一下小游戏的分数,或者想在年会上抢个小奖品,至于搞得这么麻烦吗?当然不至于,因为还有另一个方式,直接在Android项目中写uiautomator2的脚本。
下面就来介绍写在Android项目中的uiautomator2脚本脱机执行方式,当然这种实现方式也有个前提条件,给这个Android工程root权限或者一些特定的权限,最好是手机root。
来说一下实现思路:
第一步:新建一个Android工程
第二步:在布局文件上加一个Button控件,并注册监听事件
第三步:实现第二步中的监听事件具体逻辑,逻辑包括生成执行脚本的命令,执行命令
第四步:新建一个module,在里面写具体的执行脚本
下面分享一下代码,我这便是以kotlin展示。
这个是Android工程的activity文件,主要是实现android页面上那个执行按钮的逻辑:
package com.example.mytest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.Toast
import com.example.mytest.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private val TAG = "MainActivity"
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnRun.setOnClickListener{runMyUiautomator()}
}
private fun runMyUiautomator() {
Log.i(TAG, "runMyUiautomator: ")
UiautomatorThread().start()
Toast.makeText(this, "start run", Toast.LENGTH_SHORT).show()
}
class UiautomatorThread : Thread() {
override fun run() {
super.run()
val commond = generateCommand("com.example.mytestcase", "ExampleInstrumentedTest", "test1")
val cmdUtils = CMDUtils()
val res: CMDUtils.CMDResult = cmdUtils.execCommand(commond, false)
Log.i("UiautomatorThread", "run: " + res?.error + "--------" + res?.success)
}
fun generateCommand(pkgName: String, clsName: String, mtdNam: String): String {
val commond = "am instrument -w -m -e debug false -e class \'$pkgName.$clsName#$mtdNam\' $pkgName.test/androidx.test.runner.AndroidJUnitRunner"
Log.i("generateCommand: ", commond)
return commond
}
}
}
这个是执行脚本的工具类:
package com.example.mytest
import android.util.Log
import java.io.*
class CMDUtils {
private val TAG = "CMDUtil"
class CMDResult(var resultCode: Int = -1, var error: String = "error", var success: String = "success")
val tag = "CommandExecution"
val COMMAND_SU = "su"
val COMMAND_SH = "sh"
val COMMAND_EXIT = "exit\n"
val COMMAND_LINE_END = "\n"
fun execCommand(command: String, isRoot: Boolean): CMDResult {
val commandResult = CMDResult()
if (command.isEmpty()) {
return commandResult
}
var process: Process? = null
var os: DataOutputStream? = null
var successResult: BufferedReader? = null
var errorResult: BufferedReader? = null
var successMsg: StringBuilder? = null
var errorMsg: StringBuilder? = null
try {
process = Runtime.getRuntime().exec(if (isRoot) COMMAND_SU else COMMAND_SH)
os = DataOutputStream(process.outputStream)
os.write(command.toByteArray())
os.writeBytes(COMMAND_LINE_END)
os.flush()
os.writeBytes(COMMAND_EXIT)
os.flush()
commandResult.resultCode = process.waitFor()
// 获取错误信息
successMsg = StringBuilder()
errorMsg = StringBuilder()
successResult = BufferedReader(InputStreamReader(process.inputStream))
errorResult = BufferedReader(InputStreamReader(process.errorStream))
var s: String? = successResult.readLine()
while (s != null) {
successMsg.append(s)
s = successResult.readLine()
}
s = errorResult.readLine()
while (s != null) {
errorMsg.append(s)
s = errorResult.readLine()
}
commandResult.success = successMsg.toString()
commandResult.error = errorMsg.toString()
Log.i(tag, commandResult.resultCode.toString() + " | " + commandResult.success + " | " + commandResult.error)
} catch (e: IOException) {
val errMsg = e.message
when {
errMsg != null -> Log.e(tag, errMsg)
else -> e.printStackTrace()
}
} catch (e: Exception) {
val errMsg = e.message
when {
errMsg != null -> Log.e(tag, errMsg)
else -> e.printStackTrace()
}
} finally {
try {
os?.close()
successResult?.close()
errorResult?.close()
} catch (e: IOException) {
val errMsg = e.message
when {
errMsg != null -> Log.e(tag, errMsg)
else -> e.printStackTrace()
}
}
process?.destroy()
}
return commandResult
}
}
这个是真正的uiautomator2脚本,当然这里我是随便写的,脚本的意思是启动微信,点击一个空间,然后进行滑动:
package com.example.mytestcase
import android.content.Context
import android.content.Intent
import androidx.test.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiSelector
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleInstrumentedTest {
private lateinit var mDevice: UiDevice
@Test
fun test1() {
// Context of the app under test.
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
val packageName = "com.tencent.mm"
val activityName = "com.tencent.mm.ui.LauncherUI"
val context: Context = InstrumentationRegistry.getContext()
val intent: Intent? = context.packageManager.getLaunchIntentForPackage(packageName)
intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
context.startActivity(intent)
mDevice.findObject(UiSelector().resourceId("com.tencent.mm:id/a4k")).click()
mDevice.swipe(50, 500, 50, 50, 1)
}
}