1 ADB发送命令给应用
1.1 发送自定义广播给系统或应用
adb shell am broadcast
是 Android Debug Bridge (ADB) 中用于向 Android 系统发送广播的命令。通过这个命令,开发者可以发送自定义广播给系统或应用,触发应用中的广播接收器(BroadcastReceiver)。广播机制是 Android 的一种组件通信方式,应用可以监听广播来执行特定的操作。
命令解析
命令的格式如下:
adb shell am broadcast -a com.example.broadcast.MY_ACTION --es "key" "value"
各个部分的解释如下:
adb shell
:进入设备的命令行环境,执行后续的 shell 命令。am broadcast
:am
是 Android Activity Manager 的缩写,它管理 Android 的核心系统组件(如 Activity、Service、Broadcast等)。broadcast
表示通过am
命令来发送广播。-a com.example.broadcast.MY_ACTION
:-a
代表广播的动作(action),这也是应用监听广播的主要方式。这里com.example.broadcast.MY_ACTION
是一个自定义的广播动作名。--es "key" "value"
:--es
表示添加额外的数据,key
是数据的键,value
是对应的值。在这个例子中,key
是"userQuestion"
,value
是用户的问题。
实现原理
-
发送广播:
- 当你运行这个命令时,ADB 通过 USB、Wi-Fi 或其他通信方式与 Android 设备进行通信,并在设备上执行
am broadcast
命令。 am broadcast
代表通过 Android 的 Activity Manager 来发送一个广播,广播的action
是由-a
后指定的com.example.broadcast.MY_ACTION
。
- 当你运行这个命令时,ADB 通过 USB、Wi-Fi 或其他通信方式与 Android 设备进行通信,并在设备上执行
-
应用的监听机制:
- Android 应用可以通过注册一个
BroadcastReceiver
来监听特定的广播action
。如果应用注册了监听com.example.broadcast.MY_ACTION
的广播接收器,系统在接收到该广播时就会自动将其传递给应用。
广播接收器的注册方式有两种:
- 静态注册:在
AndroidManifest.xml
中声明接收器。 - 动态注册:在运行时通过代码调用
registerReceiver()
方法注册。
例如,在
AndroidManifest.xml
中声明一个广播接收器:<receiver android:name=".MyBroadcastReceiver"> <intent-filter> <action android:name="com.example.broadcast.MY_ACTION" /> </intent-filter> </receiver>
或在代码中动态注册:
IntentFilter filter = new IntentFilter("com.example.broadcast.MY_ACTION"); registerReceiver(myBroadcastReceiver, filter);
- Android 应用可以通过注册一个
-
传递数据:
- 当广播被接收时,广播中的额外数据(如
"userQuestion": "value"
)也会通过Intent
对象传递给接收器。应用可以在onReceive()
方法中获取这些数据,并根据具体逻辑处理它们。
例如,在
BroadcastReceiver
的onReceive
方法中:@Override public void onReceive(Context context, Intent intent) { if ("com.example.broadcast.MY_ACTION".equals(intent.getAction())) { String userQuestion = intent.getStringExtra("key"); // 处理用户问题 } }
- 当广播被接收时,广播中的额外数据(如
-
系统调度广播:
- Android 系统会根据广播的
action
将广播调度给所有已经注册了相关接收器的应用。 - 如果广播接收器是静态注册的,系统会启动相关应用的进程来接收广播。
- 如果是动态注册的,只有当应用正在运行时,系统才会将广播传递给它。
- Android 系统会根据广播的
总结
adb shell am broadcast
命令是通过 Android 系统的广播机制向应用程序发送特定的消息(广播),这些消息通过 Intent
的形式包含在广播中。应用程序通过 BroadcastReceiver
组件接收这些广播,并根据广播的 action
和附带的数据执行相应的操作。
应用场景
- 测试广播接收器是否正常工作。
- 向应用发送特定指令(如更新数据、触发特定操作)。
- 在应用开发过程中,用于模拟特定场景下广播的发送。
1.2 通过 ADB 接收到应用的返回信息
1. 通过Logcat捕获日志输出
一种常见的方式是通过应用在接收到广播后的日志输出来获取应用的执行结果。你可以在应用中接收到广播后,使用 Log
类记录相关信息,然后通过 adb logcat
命令捕获这些日志。
步骤:
-
应用端广播接收器:在应用中接收到广播后,输出日志。
@Override public void onReceive(Context context, Intent intent) { if ("com.example.broadcast.MY_ACTION".equals(intent.getAction())) { String userQuestion = intent.getStringExtra("key"); // 执行相应的操作 Log.d("MyBroadcastReceiver", "Received broadcast: " + userQuestion); } }
-
通过ADB发送广播:
adb shell am broadcast -a com.example.broadcast.MY_ACTION --es "key" "value"
-
使用Logcat查看应用的返回结果:
adb logcat | grep MyBroadcastReceiver
通过
adb logcat
过滤与应用相关的日志信息,查看应用的响应输出。
2. 通过广播回传结果(结果接收机制)
ADB 发送的广播可以通过 --receiver-permission
参数指定接收器的权限,同时接收器可以通过 setResultData()
等方法返回数据。
步骤:
-
应用端广播接收器:在接收器的
onReceive()
中设置返回数据。@Override public void onReceive(Context context, Intent intent) { if ("com.example.broadcast.MY_ACTION".equals(intent.getAction())) { String userQuestion = intent.getStringExtra("key"); // 设置返回数据 setResultData("Received question: " + userQuestion); } }
-
通过ADB发送广播并等待返回:
adb shell am broadcast -a com.example.broadcast.MY_ACTION --es "key" "value" --receiver-permission android.permission.BROADCAST_STICKY
-
返回结果:如果广播接收器调用了
setResultData()
,则 ADB 会返回相应的结果数据。你可以在终端上看到如下返回:Broadcast completed: result=0, data="Received question: value"
3. 通过文件共享传递结果
你可以通过应用将结果写入文件,然后通过 ADB 命令将文件从设备中导出读取。
步骤:
-
应用端接收广播并将结果写入文件:
@Override public void onReceive(Context context, Intent intent) { if ("com.example.broadcast.MY_ACTION".equals(intent.getAction())) { String userQuestion = intent.getStringExtra("key"); // 将数据写入文件 try { File file = new File(context.getExternalFilesDir(null), "result.txt"); FileWriter writer = new FileWriter(file); writer.write("Received question: " + userQuestion); writer.close(); } catch (IOException e) { e.printStackTrace(); } } }
-
通过ADB发送广播:
adb shell am broadcast -a com.example.broadcast.MY_ACTION --es "key" "value"
-
通过ADB拉取文件:
adb pull /sdcard/Android/data/com.example/files/result.txt .
通过
adb pull
命令将结果文件拉取到本地进行查看。
4. 通过Intent Service回传结果
如果你希望通过广播触发某个后台服务,并由该服务处理并回传结果,可以使用 IntentService 来处理逻辑,最后通过 ResultReceiver
回传数据。
步骤:
-
在应用端实现
IntentService
并使用ResultReceiver
:public class MyIntentService extends IntentService { public MyIntentService() { super("MyIntentService"); } @Override protected void onHandleIntent(Intent intent) { String userQuestion = intent.getStringExtra("key"); ResultReceiver receiver = intent.getParcelableExtra("receiver"); // 模拟处理 Bundle bundle = new Bundle(); bundle.putString("result", "Processed question: " + userQuestion); receiver.send(0, bundle); } }
-
在广播接收器中启动
IntentService
并设置ResultReceiver
:@Override public void onReceive(Context context, Intent intent) { if ("com.example.broadcast.MY_ACTION".equals(intent.getAction())) { Intent serviceIntent = new Intent(context, MyIntentService.class); serviceIntent.putExtra("key", intent.getStringExtra("key")); serviceIntent.putExtra("receiver", new ResultReceiver(new Handler()) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { String result = resultData.getString("result"); Log.d("MyIntentService", result); } }); context.startService(serviceIntent); } }
-
通过ADB发送广播:
adb shell am broadcast -a com.example.broadcast.MY_ACTION --es "key" "value"
-
通过
Logcat
查看IntentService的返回结果:adb logcat | grep MyIntentService
5. 通过Content Provider回传结果
应用可以通过 ContentProvider
提供数据访问接口,ADB 可以通过 content
命令与 ContentProvider
交互,读取应用产生的结果数据。
步骤:
-
应用实现
ContentProvider
并在接收广播后插入数据:@Override public void onReceive(Context context, Intent intent) { if ("com.example.broadcast.MY_ACTION".equals(intent.getAction())) { String userQuestion = intent.getStringExtra("key"); // 插入结果到ContentProvider ContentValues values = new ContentValues(); values.put("result", "Processed question: " + userQuestion); context.getContentResolver().insert(MyContentProvider.CONTENT_URI, values); } }
-
通过ADB发送广播:
adb shell am broadcast -a com.example.broadcast.MY_ACTION --es "key" "value"
-
通过ADB查询
ContentProvider
获取结果:adb shell content query --uri content://com.example.provider/results
你可以通过
adb shell content
命令查询应用的ContentProvider
,获取应用插入的结果数据。
总结:
通过 adb shell am broadcast
发送广播指令后,你可以通过以下几种方式获取应用的返回信息:
- 通过
Logcat
查看日志。 - 使用广播的
setResultData()
返回结果。 - 通过文件共享,应用将结果写入文件后,使用
adb pull
拉取文件。 - 使用
IntentService
和ResultReceiver
机制异步回传数据。 - 通过
ContentProvider
共享数据并使用adb content
命令查询结果。
这些方法可以根据应用的实际情况选择最适合的方式实现。
2 通过 ADB 使用 Socket 或 HTTP 的方式与应用通信
2.1 通过 Socket 方式与应用通信
原理:
- 应用需要在本地启动一个 Socket Server,监听某个端口,等待接收来自客户端的指令。
- 然后,通过 ADB 使用端口转发(Port Forwarding),使得开发者可以在本地通过 Socket 客户端发送指令到应用,并接收返回结果。
步骤:
1.1 应用端:创建一个 Socket 服务端
应用需要在某个端口上监听客户端连接,并处理收到的消息。你可以使用 Java 的 ServerSocket
来实现。
import java.io.*;
import java.net.*;
public class SocketServer extends Thread {
private ServerSocket serverSocket;
public SocketServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void run() {
while (true) {
try {
Socket server = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(server.getInputStream()));
PrintWriter out = new PrintWriter(server.getOutputStream(), true);
String clientInput;
while ((clientInput = in.readLine()) != null) {
System.out.println("Received: " + clientInput);
// 返回结果给客户端
out.println("Processed: " + clientInput);
}
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
你可以在应用的某个 Activity
或 Service
中启动这个 SocketServer
,例如监听 localhost:12345
。
1.2 ADB 端口转发
使用 ADB 将设备上的 Socket 端口转发到本地,方便你在本地通过 Socket 客户端与应用通信。
adb forward tcp:12345 tcp:12345
该命令会将本地的 12345
端口映射到设备上的 12345
端口。
1.3 在本地通过 Socket 客户端发送指令
你可以使用任意的 Socket 客户端工具(如 Python、Java 等)来连接设备,并发送消息。以下是 Python 的一个简单示例:
import socket
# 连接到localhost:12345 (已通过ADB转发到设备)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', 12345))
# 发送消息
sock.sendall(b'Hello from client\n')
# 接收应用返回的消息
response = sock.recv(1024)
print("Received from server: ", response.decode())
sock.close()
在这个过程中,消息会被通过 ADB 转发给设备上的应用的 Socket 服务,应用处理后会将结果返回给客户端。
2.2 通过 HTTP 方式与应用通信
原理:
- 应用可以作为一个 HTTP 服务器,监听某个端口,等待 HTTP 请求。
- 使用 ADB 端口转发将设备上的 HTTP 端口映射到本地,用户可以通过浏览器、cURL、Postman 或任何 HTTP 客户端向应用发送请求,并接收返回结果。
步骤:
2.1 应用端:创建一个 HTTP 服务
应用需要实现一个 HTTP 服务器,可以使用 Android 中的 NanoHTTPD
库等轻量级 HTTP 服务器工具,来监听 HTTP 请求并处理数据。
首先,将 NanoHTTPD
库添加到项目中:
implementation 'org.nanohttpd:nanohttpd:2.3.1'
然后,实现一个简单的 HTTP 服务器:
import fi.iki.elonen.NanoHTTPD;
public class MyHTTPServer extends NanoHTTPD {
public MyHTTPServer(int port) {
super(port);
}
@Override
public Response serve(IHTTPSession session) {
String msg = "<html><body><h1>Hello from HTTP Server</h1>\n";
String clientInput = session.getParms().get("input");
msg += "<p>Received input: " + clientInput + "</p>";
return newFixedLengthResponse(msg);
}
}
在应用的某个 Activity
或 Service
中启动 HTTP 服务器:
MyHTTPServer server = new MyHTTPServer(8080);
server.start();
2.2 ADB 端口转发
使用 ADB 将设备上的 HTTP 端口映射到本地端口:
adb forward tcp:8080 tcp:8080
这个命令会将本地的 8080
端口映射到设备的 8080
端口。
2.3 通过 HTTP 客户端发送请求
现在,你可以在本地通过浏览器、cURL、Postman 或其他 HTTP 客户端工具向应用的 HTTP 服务器发送请求。
-
通过浏览器:
在浏览器中访问http://127.0.0.1:8080?input=HelloFromClient
,浏览器会显示来自应用的响应。 -
通过
cURL
:
你也可以使用cURL
发送请求:curl "http://127.0.0.1:8080?input=HelloFromClient"
应用将处理请求,并返回包含输入信息的响应:
<html>
<body>
<h1>Hello from HTTP Server</h1>
<p>Received input: HelloFromClient</p>
</body>
</html>
3. 总结
-
Socket 方式:
- 应用启动一个
Socket Server
,监听端口。 - 使用 ADB 端口转发功能,将设备端口映射到本地端口。
- 通过本地 Socket 客户端与应用通信,并接收返回值。
- 应用启动一个
-
HTTP 方式:
- 应用启动一个 HTTP 服务端,监听 HTTP 请求。
- 使用 ADB 端口转发功能,将设备上的 HTTP 端口映射到本地端口。
- 通过浏览器、cURL 或其他 HTTP 客户端工具与应用通信,并接收返回值。
这两种方式可以实现通过 ADB 与应用进行交互,适用于不同的场景,具体选择哪种方式取决于需求和使用的技术栈。
3 对比 System.out.println() 和 Logcat
System.out.println() / System.err.println():输出是直接流到当前的 ADB shell 会话或 IDE 调试控制台中,通常只在当前会话期间有效。如果通过 ADB 执行命令运行应用,你可以直接在 ADB 控制台中看到 System.out 的输出。
Log.d() / Log.i() 等:通过 adb logcat 查看,并可以通过日志级别过滤和保存。可以随时在设备中查看历史日志,甚至可以将设备中的日志导出到文件中。
3.1 System.out.println()` 的实现原理
System.out.println()
是 Java 标准库中提供的一种基础方法,用于向标准输出(stdout)写入信息。在 Android 系统中,这个标准输出通常被重定向到了设备的控制台输出,也就是连接设备的 ADB shell。其具体工作流程如下:
-
标准输出流(
stdout
):- 在 Java 中,
System.out
是PrintStream
的一个实例,它包装了 UNIX 标准输出流(stdout
)。 - 当你调用
System.out.println()
方法时,它将信息格式化为字符串,然后通过PrintStream
将这些字符串写入stdout
。
- 在 Java 中,
-
输出重定向:
- 在 Android 设备上运行的应用是在一个 Linux-based 系统上的,每个应用都是一个独立的 Linux 进程。
- 这些进程的
stdout
和stderr
通常被重定向到了/dev/null
(一个丢弃所有写入数据的设备),但当通过 ADB 连接时,stdout
和stderr
会被 ADB 捕获并重定向到 ADB 的控制台。
-
即时性:
- 由于
stdout
输出的重定向,通过 ADB 运行的应用会将System.out.println()
的输出直接显示在开发者的终端或 IDE 的控制台上。 - 这个过程非常快速,因为它几乎没有任何中间处理,直接通过系统的 I/O 操作进行数据传输。
- 由于
3.2 Logcat 的实现原理
与 System.out.println()
直接操作标准输出流不同,Logcat 是 Android 特有的一个复杂的日志系统,设计用来收集和查看系统以及应用程序的各种日志信息。它的工作原理如下:
-
日志消息的产生:
- 在 Android 应用中,开发者通过调用
Log
类(如Log.d()
,Log.i()
, 等)来记录日志。 - 这些方法最终会调用 Android 的
native
日志接口,该接口封装了向日志设备(如/dev/log
或/dev/logger
)的写操作。
- 在 Android 应用中,开发者通过调用
-
内核中的日志驱动:
- Android 操作系统内核包含一个日志驱动,负责管理日志设备。
- 当应用通过
Log
类写日志时,日志消息被发送到内核的日志驱动,日志驱动将这些消息存储在一个或多个环形缓冲区中。每种类型的日志(如 “main”, “system”)都有自己的环形缓冲区。
-
缓冲区和管理:
- 每个环形缓冲区都有固定的大小,当新的日志写入时,如果缓冲区已满,旧的日志将被新的日志覆盖。
- 这些环形缓冲区是用户空间和内核空间之间的桥梁,用户空间的应用(或
adb logcat
命令)可以查询这些缓冲区来读取日志。
-
日志的检索:
- 开发者通常使用
adb logcat
命令来读取和监控日志。 adb logcat
命令实际上是连接到这些环形缓冲区,根据指定的过滤条件(如日志级别、标签等)输出日志信息。
- 开发者通常使用
3. 稳定性和性能差异
-
System.out.println()
:- 优点:快速、直接,适合即时调试信息的输出。
- 缺点:不适合长期日志记录,输出内容可能会在设备断开时丢失,不支持日志级别和过滤。
-
Logcat:
- 优点:支持日志级别和过滤,能够长时间记录日志,适合应用和系统的全面调试。
- 缺点:由于依赖环形缓冲区和内核日志机制,处理速度可能较慢,且在缓冲区满时可能丢失日志。
总结
System.out.println()
提供了一种快速而直接的方式来输出调试信息,适合快速调试和测试。而 Logcat 提供了一个全面的日志系统,适合应用的深入调试和错误追踪,但其性能和稳定性可能受到环形缓冲区和内核日志机制的影响。理解这两种方法的内部工作原理有助于在开发过程中更有效地使用它们。