应用操作

本章您可以了解如何安装、启动和关闭应用、授予或撤销应用的权限、禁用或启用应用,以及重放任意活动(scheme、Activity)(含未导出的活动)等。

安装应用

您需要准备好安装的 apk,支持安卓单个 APK 以及 Split APK(如 xapk)。

单一 APK

对于单一 APK 文件,使用如下流程安装

>>> session = d.create_install_session()
>>> session.write("/path/to/file.apk")
session: 2033102247
name: "base.apk"
writtenBytes: 5130176
totalBytes: 5130176

>>> session.commit()
session: 2033102247
package: "com.example.apk"
legacyStatus: 1
statusMessage: "INSTALL_SUCCEEDED: Session installed"
finished: true
committed: true
progress: 1
success: true

>>>

Split APK (如 XAPK)

对于多 APK 的应用,您可能需要先解压总包,解压后,您通常会看到类似如下的文件集。

➜  apk_temp/ $ ls -l
total 350720
-rw-rw-r--  1 root  root    86M  1 26  1970 com.some.package.apk
-rw-rw-r--  1 root  root   772K  1 26  1970 config.ar.apk
-rw-rw-r--  1 root  root    68M  1 26  1970 config.arm64_v8a.apk
-rw-rw-r--  1 root  root   676K  1 26  1970 config.de.apk
-rw-rw-r--  1 root  root   478K  1 26  1970 config.en.apk

并通过如下方式安装,通常您需要安装主包、语言包、以及 native lib 包。

>>> session = d.create_install_session()
>>> session.write("com.example.app.apk", name="com.example.app.apk")

session: 1137194135
name: "com.example.app.apk"
writtenBytes: 89661970
totalBytes: 89661970

>>> session.write("config.de.apk", name="config.de.apk")
session: 1137194135
name: "config.de.apk"
writtenBytes: 692633
totalBytes: 90354603

>>> session.write("config.arm64_v8a.apk", name="config.arm64_v8a.apk")
session: 1137194135
name: "config.arm64_v8a.apk"
writtenBytes: 70978318
totalBytes: 161332921

>>> session.write("config.xxhdpi.apk", name="config.xxhdpi.apk")
session: 1137194135
name: "config.xxhdpi.apk"
writtenBytes: 6266292
totalBytes: 167599213

>>> session.commit()
session: 1137194135
package: "com.example.app"
legacyStatus: 1
statusMessage: "INSTALL_SUCCEEDED: Session installed"
finished: true
committed: true
progress: 0.900000036
success: true

>>>

如果安装中发生任何错误,将会直接触发异常,比如 Split APK 没有写入主包。

>>> session = d.create_install_session()
>>> session.write("config.de.apk", name="config.de.apk")
session: 1658145271
name: "config.de.apk"
writtenBytes: 692633
totalBytes: 692633

>>> session.commit()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  ....
  raise self.remote_exception(exception)
lamda.exceptions.InstallPackageFailed: INSTALL_FAILED_INVALID_APK: Full install must include a base package

会话参数

使用特定创建会话参数来实现 APK 的覆盖安装、降级安装、自动授权、安装到特定用户等。

参数名类型默认值描述
userint0安装到指定用户 ID
packagestrNone待安装的包名,可不填
request_downgradeboolFalse是否允许降级安装
grant_runtime_permissionsboolFalse是否授予运行时权限
installer_package_namestrNone自定义安装器名称(如 com.android.vending
dont_kill_appboolFalse安装时不关闭应用
replace_existingboolTrue覆盖已安装应用(在 root 模式下始终为 True,即使设置为 False
allow_testboolFalse是否允许安装测试应用

您可以像下面这样,在创建会话时使用如上参数。

session = d.create_install_session(request_downgrade=True)

其他情况

部分系统可能会拦截安装操作,或者在安装操作中掺杂应用检测弹窗等等,比如 Google Play 检测等。这时,您可能想自动化操作完成安装,这是可以的。 commit 提供了后台安装模式,下面的操作可以不用等待,手动查询安装状态。

>>> session = d.create_install_session()
>>> session.write("/path/to/file.apk")
session: 1079135981
name: "base.apk"
writtenBytes: 5130176
totalBytes: 5130176

>>> session.commit(wait=False)
session: 1079135981
status: -2147483648
legacyStatus: -2147483648
statusMessage: "Commit sent"
committed: true
progress: 0.8

>>> session.status()
session: 1079135981
package: "com.example.app"
status: -2147483648
legacyStatus: -2147483648
statusMessage: "Commit sent"
committed: true
progress: 0.900000036

>>> session.status()
session: 1079135981
package: "com.example.app"
legacyStatus: 1
statusMessage: "INSTALL_SUCCEEDED: Session installed"
finished: true
committed: true
progress: 1
success: true

这样,在安装开始后,您可以直接使用其他代码处理安装过程中产生的弹窗,当然,使用 Watcher 进行自动点击也是一个可行的方式。

列出安装的应用

获取设备上所有已安装应用的信息。

d.enumerate_installed_apps()
>>> d.enumerate_installed_apps()
[packageName: "com.android.uwb.resources"
label: "System UWB Resources"
uid: 10110
enabled: true
system: true
versionName: "T-initial"
, packageName: "com.android.adservices.api"
label: "Android System"
uid: 10105
enabled: true
system: true
versionName: "14"
...

列出运行的应用

获取系统上当前正在运行的应用相关信息。

d.enumerate_running_processes()
>>> d.enumerate_running_processes()
[packages: "com.android.launcher3"
processName: "com.android.launcher3"
uid: 10084
pid: 2360
label: "Quickstep"
, packages: "com.google.android.gms"
processName: "com.google.android.gms.persistent"
uid: 10123
pid: 2765
label: "Google Play services"
, packages: "com.instagram.android"
processName: "com.instagram.android"
uid: 10150
pid: 5529
label: "Instagram"
...
>>> result = d.enumerate_running_processes()
>>> print(result[0].processName)
com.android.launcher3

通过名称获取应用

使用应用的常用名来获取应用实例(不清楚 Package ID)。

app = d.get_application_by_name("微信")

通过包名获取应用

使用应用的 Package ID 来获取应用实例。

app = d.application("com.tencent.mm")

获取前台应用

获取当前正在前台运行的应用实例。

app = d.current_application()

获取多开应用

获取多开应用的实例(通常情况下,多开应用通过 user 区分,并且 uid 为 999)。

app = d.application("com.my.app", user=999)

启动应用

启动这个 APP。

app.start()

关闭应用

强制关闭此应用。

app.stop()

检查前台运行

检查应用当前是否正在前台运行。

app.is_foreground()

获取应用信息

获取应用的相关信息如版本等。

app.info()
>>> app.info()
packageName: "com.android.settings"
uid: 1000
enabled: true
processName: "com.android.settings"
sourceDir: "/system/product/priv-app/Settings/Settings.apk"
dataDir: "/data/user_de/0/com.android.settings"
firstInstallTime: 1230739200000
lastUpdateTime: 1230768000000
versionCode: 1276
versionName: "10"
>>> result = app.info()
>>> print(result.processName)
'com.android.settings'

检查是否安装

检查此应用是否已经安装在设备上。

app.is_installed()

卸载应用

从设备上卸载该应用。

app.uninstall()

发起应用活动

您可以重放系统级 Activity,调起任意应用的任意 Activity。它的可用参数如下,您需要注意,附加数据(extras)只支持 boolean, int, short, long, double, float 以及 string 类型。 flags 参数的定义请参考文档 developer.android.com/reference/android/content/Intent

from lamda.const import *
d.start_activity(action="*", category="*", component="*", extras={"boolean": False, "int": 1, "string": "abc", "float": 1.123}, flags=FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_CLEAR_TASK, data="*", debug=False)

现在我们拿 获取最近活动 接口的返回为例,您可以直接通过如下代码重放最后一条系统 Activity。

activity = d.get_last_activities(count=5)[-1]
d.start_activity(**activity)

如果您是要调起多开应用,如下代码会将 Activity 重放至多开应用。

d.start_activity(**activity, user=999)

下面给您一些示例供您参考,以下调用会拨打 10000 客服电话。

d.start_activity(action="android.intent.action.CALL", data="tel:10000")

以下调用,会为您启动 Settings 应用,这几乎等价于直接启动应用。

d.start_activity(action="android.intent.action.MAIN", category="android.intent.category.LAUNCHER", component="com.android.settings/.Settings")

以下调用,会以调试模式打开设置应用,如果您见过 Waiting for debugger,那么它可能对您有用。当然,您的设备或者 APP 需要是可调试的,它与上面的调用区别仅仅是多了一个 debug 参数。

d.start_activity(action="android.intent.action.MAIN", category="android.intent.category.LAUNCHER", component="com.android.settings/.Settings", debug=True)

以下调用,会为您直接跳转进入证书设置页面。

d.start_activity(action="com.android.settings.TRUSTED_CREDENTIALS")

列出应用权限

此接口可以列出应用所有声明的权限名称。

app.permissions()
>>> app.permissions()
['android.permission.REQUEST_NETWORK_SCORES', 'android.permission.WRITE_MEDIA_STORAGE', 'android.permission.WRITE_EXTERNAL_STORAGE', 'android.permission.READ_EXTERNAL_STORAGE', 'android.permission.WRITE_SETTINGS',...]

授予应用权限

此接口用于授予应用对应的系统权限,您应在 APP 未启动时使用此接口进行权限设置,在 APP 运行请求权限时使用并不会产生自动授予权限的效果。

from lamda.const import *
app.grant(PERMISSION_READ_PHONE_STATE, mode=GrantType.GRANT_ALLOW)

它也等于直接提供权限名称的完整字符串:

app.grant("android.permission.READ_PHONE_STATE", mode=GrantType.GRANT_ALLOW)

其中 grant 方法的 mode 参数,还支持 GrantType.GRANT_DENY,意思为明确拒绝该权限。以及 GrantType.GRANT_IGNOREGRANT_IGNORE 的意义较为特殊,其代表授予应用该权限,但是应用实际无法正常使用此权限,比如如果应用请求相机,使用此参数拒绝,应用的相机可能黑屏。

撤销应用权限

调用此接口可以撤销授予应用的权限,您同样应该在应用启动前进行调用。

from lamda.const import *
app.revoke(PERMISSION_READ_PHONE_STATE)

检查是否授予

此接口用于检查某项权限是否已经正常授予应用。

from lamda.const import *
app.is_permission_granted(PERMISSION_READ_PHONE_STATE)

清除应用缓存

此接口用于清除应用的缓存数据,通常不会对应用造成影响。

app.clear_cache()

清除应用数据

此接口用于清除应用的数据,注意此操作会清空应用所有数据,应用的信息如账号等将会丢失。

app.reset()

获取入口活动

您可以通过此接口查询该应用的启动 Activity(入口活动)。

app.query_launch_activity()
>>> app.query_launch_activity()
{'action': 'android.intent.action.MAIN', 'component': 'com.android.settings/com.android.settings.Settings', 'categories': ['android.intent.category.LAUNCHER']}

启用应用

此接口用于启用被禁用的应用,启用后您仍然可以正常使用应用。

app.enable()

禁用应用

此接口用于禁用应用,被禁用的应用将不会在应用列表中显示,您也无法使用,除非被启用。该功能可为您临时或彻底地冻结应用,该应用无法自启动,在设备上的应用安装较多时您可以适当禁用暂时不需要使用的应用以减少系统资源的使用。

app.disable()