<think>
我们面对的问题分为两部分:PC端Python脚本通过ADB发送广播,以及Android端接收广播并处理。
根据引用[3],我们知道可以使用ADB Utils来封装设备操作命令。因此,PC端我们可以使用adb shell命令发送广播。
在Android端,我们需要一个BroadcastReceiver来接收广播,然后启动TestActivity(或通知它)来处理JSON并更新UI。
步骤:
1. PC端:使用Python脚本执行adb命令发送广播,广播中携带JSON数据。
- 命令格式:`adb shell am broadcast -a <action> --es <extra_key> <extra_string_value>`
但是注意,如果数据量较大,我们可以使用文件传输,但这里我们直接传送JSON字符串(注意转义)。
- 另一种方式:使用`--es`只能传递字符串,对于多行或复杂JSON,我们可以使用`--es`传递base64编码的字符串,或者使用临时文件。但最简单的方式是使用`--es`并确保字符串中不包含特殊字符(如空格、引号等)或进行适当的转义。
然而,ADB的`am broadcast`命令对于传递较长的数据有限制。我们可以使用`--es`传递一个较短的字符串,或者使用多个`--es`参数传递分段的数据,然后在接收端组合。但更合理的方法是使用文件:PC端先将JSON写入设备的临时文件,然后发送广播并传递文件路径,App接收广播后读取文件内容。
2. 考虑到数据可能较大,我们采用文件方式:
- PC端步骤:
a. 将JSON数据保存到本地一个临时文件。
b. 使用`adb push`将文件推送到设备上的某个位置(如`sdcard/`目录)。
c. 发送广播,携带文件路径作为额外数据。
- 设备端步骤:
a. 接收广播,从Intent中获取文件路径。
b. 读取文件内容(JSON字符串)。
c. 启动TestActivity(或者如果TestActivity已经运行,则发送消息给它)并传递JSON数据。
d. TestActivity解析JSON并更新UI。
3. 另一种更直接的方式(如果数据量不大,比如不超过几百KB):
- 我们可以通过base64编码将整个JSON字符串作为一行,然后通过`--es`传递。在接收端解码。
这里我们假设数据量不大,采用base64编码方式。
PC端Python脚本示例(发送广播):
```python
import subprocess
import base64
import json
# 要发送的JSON数据(示例)
data = {
"key1": "value1",
"key2": ["item1", "item2"],
"key3": {"nested": "object"}
}
json_str = json.dumps(data)
# 对JSON字符串进行base64编码(以避免特殊字符问题)
b64_str = base64.b64encode(json_str.encode('utf-8')).decode('utf-8')
# 构建adb命令
adb_command = [
'adb', 'shell', 'am', 'broadcast',
'-a', 'com.example.ACTION_TEST', # 自定义的action
'--es', 'json_data', b64_str # 传递base64字符串
]
# 执行命令
subprocess.run(adb_command)
```
在Android端,我们需要注册一个广播接收器(BroadcastReceiver)来接收这个广播。
4. Android端实现:
- 在AndroidManifest.xml中静态注册广播接收器(或者动态注册,这里以静态注册为例):
```xml
<receiver android:name=".MyBroadcastReceiver">
<intent-filter>
<action android:name="com.example.ACTION_TEST" />
</intent-filter>
</receiver>
```
- 实现广播接收器MyBroadcastReceiver:
```java
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if ("com.example.ACTION_TEST".equals(intent.getAction())) {
String base64Data = intent.getStringExtra("json_data");
if (base64Data != null) {
// 解码base64
byte[] decodedBytes = Base64.decode(base64Data, Base64.DEFAULT);
String jsonStr = new String(decodedBytes, StandardCharsets.UTF_8);
// 将JSON字符串传递给TestActivity
Intent activityIntent = new Intent(context, TestActivity.class);
activityIntent.putExtra("json_data", jsonStr);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 因为从非Activity上下文启动
context.startActivity(activityIntent);
}
}
}
}
```
- 在TestActivity中获取JSON数据并处理:
```java
public class TestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
// 获取传递过来的JSON字符串
Intent intent = getIntent();
if (intent != null) {
String jsonStr = intent.getStringExtra("json_data");
if (jsonStr != null) {
// 解析JSON并更新UI
updateUI(jsonStr);
}
}
}
private void updateUI(String jsonStr) {
// 解析JSON,然后更新UI
// 注意:这里在UI线程中执行,如果解析和更新操作耗时,应考虑异步。
}
}
```
5. 注意事项:
- 广播接收器必须在AndroidManifest.xml中声明,并且需要声明对应的权限(如果没有权限要求则不需要)。
- 由于广播接收器的onReceive方法在主线程中运行,因此不能做耗时操作。这里我们只是启动Activity,所以没问题。但是,如果JSON解析和更新UI很耗时,那么应该在TestActivity中放在子线程中处理(或者使用AsyncTask等),注意更新UI只能在主线程。
- 如果TestActivity已经启动,并且希望更新它而不是重新启动,则需要使用不同的启动方式(例如,使用startActivityForResult或者使用单例模式传递数据,但更常见的做法是使用广播接收器将数据直接发送给Activity,前提是Activity在运行)。我们可以通过让Activity注册一个动态的广播接收器(在onResume中注册,onPause中注销)来接收来自MyBroadcastReceiver的另一个广播(或者使用其他进程间通信方式,如Handler、LiveData等)。
6. 改进:为了避免每次发送广播都启动一个新的TestActivity,我们可以让TestActivity在运行的时候能够接收到数据更新。修改方案:
- 方案1:在MyBroadcastReceiver中,判断TestActivity是否处于活动状态,如果活动,则发送一个Intent给TestActivity(而不是启动新的),否则启动新的。
```java
// 在MyBroadcastReceiver中
if (isActivityRunning(context, TestActivity.class)) {
// 发送广播给TestActivity(使用动态注册的广播接收器)
Intent activityIntent = new Intent("com.example.ACTION_UPDATE_UI");
activityIntent.putExtra("json_data", jsonStr);
context.sendBroadcast(activityIntent);
} else {
// 启动新的TestActivity
Intent activityIntent = new Intent(context, TestActivity.class);
activityIntent.putExtra("json_data", jsonStr);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(activityIntent);
}
```
然后在TestActivity中动态注册一个广播接收器(用于接收ACTION_UPDATE_UI)来更新UI。
- 方案2:使用LocalBroadcastManager(已弃用)或者使用全局广播(注意安全)来发送给TestActivity。或者使用Handler、LiveData等更合适的方式(但需要Activity在运行)。
这里为了简单,我们采用方案1,但需要注意判断Activity是否运行的方法(需要权限`android.permission.GET_TASKS`,但在新版本中可能受限)。因此,我们可以通过一个标志在Application中记录TestActivity是否在前台。
7. 更健壮的方法:使用一个单例的“数据总线”来传递数据,或者使用LiveData(如果使用Android架构组件)。
考虑到问题的复杂性,我们先提供一个基础版本(每次收到广播都启动新的TestActivity),然后提供一个改进版本(动态更新已存在的TestActivity)。
现在,我们先完成基础版本的代码。
8. 基础版本已经可以工作,但是每次都会启动新的Activity。为了避免重复启动,我们可以设置Activity的启动模式为singleTask,并在TestActivity的onNewIntent方法中处理新的Intent。
修改TestActivity:
```java
public class TestActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
processIntent(getIntent());
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
processIntent(intent);
}
private void processIntent(Intent intent) {
String jsonStr = intent.getStringExtra("json_data");
if (jsonStr != null) {
updateUI(jsonStr);
}
}
}
```
然后在AndroidManifest.xml中设置TestActivity的launchMode为singleTask:
```xml
<activity android:name=".TestActivity"
android:launchMode="singleTask">
</activity>
```
同时修改MyBroadcastReceiver,启动TestActivity时不再添加FLAG_ACTIVITY_NEW_TASK(因为singleTask会自己管理),而是直接启动:
```java
Intent activityIntent = new Intent(context, TestActivity.class);
activityIntent.putExtra("json_data", jsonStr);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 仍然需要,因为从非Activity上下文启动
context.startActivity(activityIntent);
```
因为广播接收器的context是非Activity的上下文,所以启动Activity必须添加FLAG_ACTIVITY_NEW_TASK。
9. 这样,如果TestActivity已经在运行,则调用它的onNewIntent方法;如果不在,则创建新的。
因此,我们结合使用singleTask和onNewIntent来实现更新已存在的Activity。
综上所述,我们提供以下完整代码:
PC端Python脚本(使用base64编码):
```python
import subprocess
import base64
import json
# 要发送的JSON数据(示例)
data = {"key": "value"} # 替换为实际数据
json_str = json.dumps(data)
b64_str = base64.b64encode(json_str.encode('utf-8')).decode('utf-8')
adb_command = f'adb shell am broadcast -a com.example.ACTION_TEST --es json_data "{b64_str}"'
# 或者使用列表形式(避免shell转义问题):
# adb_command = ['adb', 'shell', 'am', 'broadcast', '-a', 'com.example.ACTION_TEST', '--es', 'json_data', b64_str]
subprocess.run(adb_command, shell=True) # 注意:使用字符串命令时shell=True,但要注意安全。如果b64_str包含特殊字符,建议用列表形式。
```
Android端:
AndroidManifest.xml:
```xml
<receiver android:name=".MyBroadcastReceiver">
<intent-filter>
<action android:name="com.example.ACTION_TEST" />
</intent-filter>
</receiver>
<activity android:name=".TestActivity"
android:launchMode="singleTask">
</activity>
```
MyBroadcastReceiver.java:
```java
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction() != null && intent.getAction().equals("com.example.ACTION_TEST")) {
String base64Data = intent.getStringExtra("json_data");
if (base64Data != null) {
byte[] decodedBytes = Base64.decode(base64Data, Base64.DEFAULT);
String jsonStr = new String(decodedBytes, StandardCharsets.UTF_8);
// 启动Activity(如果已存在则调用onNewIntent)
Intent activityIntent = new Intent(context, TestActivity.class);
activityIntent.putExtra("json_data", jsonStr);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(activityIntent);
}
}
}
}
```
TestActivity.java:
```java
public class TestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
processIntent(getIntent());
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
processIntent(intent);
}
private void processIntent(Intent intent) {
String jsonStr = intent.getStringExtra("json_data");
if (jsonStr != null) {
// 解析JSON并更新UI
updateUI(jsonStr);
}
}
private void updateUI(String jsonStr) {
// 这里解析JSON,然后更新UI
runOnUiThread(() -> {
// 更新UI的代码
});
}
}
```
注意:在updateUI中,我们假设解析JSON的操作比较快,所以直接在主线程解析。如果JSON很大,解析耗时,应该放在子线程解析,然后回到主线程更新UI。
10. 另外,如果JSON数据非常大,使用base64编码后通过adb的--es传递可能会超过命令行参数长度限制。这时,我们考虑使用文件传输:
PC端Python脚本(文件传输方式):
```python
import subprocess
import json
# 要发送的JSON数据
data = {"key": "value"} # 替换为实际数据
json_str = json.dumps(data)
# 将JSON字符串写入临时文件
with open('temp.json', 'w') as f:
f.write(json_str)
# 推送文件到设备
subprocess.run(['adb', 'push', 'temp.json', '/sdcard/temp.json'])
# 发送广播,传递文件路径
adb_command = [
'adb', 'shell', 'am', 'broadcast',
'-a', 'com.example.ACTION_TEST',
'--es', 'json_file', '/sdcard/temp.json'
]
subprocess.run(adb_command)
```
然后在MyBroadcastReceiver中读取文件:
```java
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("com.example.ACTION_TEST")) {
String filePath = intent.getStringExtra("json_file");
if (filePath != null) {
// 读取文件内容
String jsonStr = readFile(filePath);
// 然后启动TestActivity并传递jsonStr
// ... 同上,将jsonStr传递给TestActivity
}
}
}
private String readFile(String filePath) {
StringBuilder content = new StringBuilder();
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = br.readLine()) != null) {
content.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return content.toString();
}
```
注意:设备端需要读取sdcard的权限(在AndroidManifest.xml中声明`<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />`,并且在运行时请求该权限(如果目标API>=23)。
根据需求选择合适的方式。
以上代码实现了需求。请根据实际项目情况调整包名、权限、文件路径等。
</think>
### 实现方案:PC端发送ADB广播触发App更新UI
#### PC端Python脚本实现
```python
import subprocess
import json
# 1. 构造JSON数据
data = {
"message": "紧急更新",
"priority": 1,
"items": ["项目A", "项目B", "项目C"]
}
# 2. 将JSON转为base64(避免特殊字符问题)
json_str = json.dumps(data)
b64_data = subprocess.check_output(
["python", "-c", f"import base64; print(base64.b64encode('{json_str}'.encode()).decode())"],
text=True
).strip()
# 3. 发送ADB广播命令
cmd = f'adb shell am broadcast -a com.example.ACTION_UPDATE_UI \
--es json_data "{b64_data}" \
--ez force_refresh true \
--ef timeout 5.0'
subprocess.run(cmd, shell=True, check=True)
print("广播发送成功,数据大小:", len(json_str), "字节")
```
#### Android端实现步骤
##### 1. 广播接收器 (JsonBroadcastReceiver.java)
```java
public class JsonBroadcastReceiver extends BroadcastReceiver {
private static final String ACTION = "com.example.ACTION_UPDATE_UI";
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION.equals(intent.getAction())) {
// 获取base64编码的JSON数据
String b64Data = intent.getStringExtra("json_data");
boolean forceRefresh = intent.getBooleanExtra("force_refresh", false);
float timeout = intent.getFloatExtra("timeout", 3.0f);
// 解码JSON
byte[] decoded = Base64.decode(b64Data, Base64.DEFAULT);
String json = new String(decoded, StandardCharsets.UTF_8);
// 启动TestActivity处理
Intent activityIntent = new Intent(context, TestActivity.class);
activityIntent.putExtra("json_data", json);
activityIntent.putExtra("force_refresh", forceRefresh);
activityIntent.putExtra("timeout", timeout);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(activityIntent);
}
}
}
```
##### 2. AndroidManifest.xml 配置
```xml
<receiver android:name=".JsonBroadcastReceiver">
<intent-filter>
<action android:name="com.example.ACTION_UPDATE_UI" />
</intent-filter>
</receiver>
<activity
android:name=".TestActivity"
android:launchMode="singleTop" />
```
##### 3. TestActivity 处理逻辑 (TestActivity.java)
```java
public class TestActivity extends AppCompatActivity {
private TextView tvResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
tvResult = findViewById(R.id.tv_result);
processIntent(getIntent());
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
processIntent(intent);
}
private void processIntent(Intent intent) {
String jsonData = intent.getStringExtra("json_data");
if (jsonData != null) {
try {
JSONObject json = new JSONObject(jsonData);
updateUI(json);
// 性能监控(参考引用[1])
long renderStart = System.currentTimeMillis();
// ... UI渲染逻辑
Log.d("Perf", "UI渲染耗时:" + (System.currentTimeMillis() - renderStart) + "ms");
} catch (JSONException e) {
Log.e("JSON", "解析错误", e);
}
}
}
private void updateUI(JSONObject data) {
runOnUiThread(() -> {
try {
tvResult.setText(data.toString(2));
// 这里添加其他UI更新逻辑
} catch (JSONException e) {
tvResult.setText("数据格式错误");
}
});
}
}
```
#### 执行流程说明
1. **PC端发送**:
- 构造JSON数据并转为Base64
- 通过`am broadcast`发送携带参数的广播
- 参数说明:
- `-a`:广播Action
- `--es`:字符串类型参数
- `--ez`:布尔类型参数
- `--ef`:浮点类型参数
2. **App端接收**:
- 广播接收器捕获特定Action
- 解码Base64数据为JSON字符串
- 启动TestActivity(已存在则复用)
3. **TestActivity处理**:
- 解析JSON数据
- 在主线程更新UI组件
- 监控首屏渲染耗时(参考引用[1]性能指标)
#### 性能优化建议
1. **大文件传输优化**(>1MB数据):
```python
# 替代直接广播的方案
subprocess.run(f"adb push large_data.json /sdcard/", shell=True)
subprocess.run(f"adb shell am broadcast -a com.example.FILE_UPDATE -es file_path /sdcard/large_data.json", shell=True)
```
2. **防重复刷新**(添加时间戳校验):
```java
long serverTime = intent.getLongExtra("timestamp", 0);
if (System.currentTimeMillis() - serverTime > 5000) {
Log.w("Broadcast", "过期数据丢弃");
return;
}
```
3. **异常处理增强**:
```java
// 在广播接收器中
catch (Exception e) {
FirebaseCrashlytics.getInstance().recordException(e);
Intent fallback = new Intent(context, ErrorActivity.class);
fallback.putExtra("error_msg", "JSON处理异常");
context.startActivity(fallback);
}
```
#### 测试验证方法
1. 使用引用[2]中的测试框架:
```python
# test_cases/adb_broadcast_test.py
@pytest.mark.ci_smoke
def test_broadcast_flow(device):
# 触发PC端脚本
run_broadcast_script()
# 验证App响应
assert device.wait_activity("TestActivity", timeout=10)
assert device(text="紧急更新").exists
```
2. 性能监控(参考引用[1]):
```
adb logcat | grep "Perf"
```
---
### 相关问题