Android中如何实现adb向应用发送特定指令并接收返回

2024-09-08 14:52

本文主要是介绍Android中如何实现adb向应用发送特定指令并接收返回,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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 broadcastam 是 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 是用户的问题。

实现原理

  1. 发送广播

    • 当你运行这个命令时,ADB 通过 USB、Wi-Fi 或其他通信方式与 Android 设备进行通信,并在设备上执行 am broadcast 命令。
    • am broadcast 代表通过 Android 的 Activity Manager 来发送一个广播,广播的 action 是由 -a 后指定的 com.example.broadcast.MY_ACTION
  2. 应用的监听机制

    • 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);
    
  3. 传递数据

    • 当广播被接收时,广播中的额外数据(如 "userQuestion": "value")也会通过 Intent 对象传递给接收器。应用可以在 onReceive() 方法中获取这些数据,并根据具体逻辑处理它们。

    例如,在 BroadcastReceiveronReceive 方法中:

    @Override
    public void onReceive(Context context, Intent intent) {if ("com.example.broadcast.MY_ACTION".equals(intent.getAction())) {String userQuestion = intent.getStringExtra("key");// 处理用户问题}
    }
    
  4. 系统调度广播

    • Android 系统会根据广播的 action 将广播调度给所有已经注册了相关接收器的应用。
    • 如果广播接收器是静态注册的,系统会启动相关应用的进程来接收广播。
    • 如果是动态注册的,只有当应用正在运行时,系统才会将广播传递给它。

总结

adb shell am broadcast 命令是通过 Android 系统的广播机制向应用程序发送特定的消息(广播),这些消息通过 Intent 的形式包含在广播中。应用程序通过 BroadcastReceiver 组件接收这些广播,并根据广播的 action 和附带的数据执行相应的操作。

应用场景

  • 测试广播接收器是否正常工作。
  • 向应用发送特定指令(如更新数据、触发特定操作)。
  • 在应用开发过程中,用于模拟特定场景下广播的发送。

1.2 通过 ADB 接收到应用的返回信息

1. 通过Logcat捕获日志输出

一种常见的方式是通过应用在接收到广播后的日志输出来获取应用的执行结果。你可以在应用中接收到广播后,使用 Log 类记录相关信息,然后通过 adb logcat 命令捕获这些日志。

步骤:
  1. 应用端广播接收器:在应用中接收到广播后,输出日志。

    @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);}
    }
    
  2. 通过ADB发送广播

    adb shell am broadcast -a com.example.broadcast.MY_ACTION --es "key" "value"
    
  3. 使用Logcat查看应用的返回结果

    adb logcat | grep MyBroadcastReceiver
    

    通过 adb logcat 过滤与应用相关的日志信息,查看应用的响应输出。

2. 通过广播回传结果(结果接收机制)

ADB 发送的广播可以通过 --receiver-permission 参数指定接收器的权限,同时接收器可以通过 setResultData() 等方法返回数据。

步骤:
  1. 应用端广播接收器:在接收器的 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);}
    }
    
  2. 通过ADB发送广播并等待返回

    adb shell am broadcast -a com.example.broadcast.MY_ACTION --es "key" "value" --receiver-permission android.permission.BROADCAST_STICKY
    
  3. 返回结果:如果广播接收器调用了 setResultData(),则 ADB 会返回相应的结果数据。你可以在终端上看到如下返回:

    Broadcast completed: result=0, data="Received question: value"
    

3. 通过文件共享传递结果

你可以通过应用将结果写入文件,然后通过 ADB 命令将文件从设备中导出读取。

步骤:
  1. 应用端接收广播并将结果写入文件

    @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();}}
    }
    
  2. 通过ADB发送广播

    adb shell am broadcast -a com.example.broadcast.MY_ACTION --es "key" "value"
    
  3. 通过ADB拉取文件

    adb pull /sdcard/Android/data/com.example/files/result.txt .
    

    通过 adb pull 命令将结果文件拉取到本地进行查看。

4. 通过Intent Service回传结果

如果你希望通过广播触发某个后台服务,并由该服务处理并回传结果,可以使用 IntentService 来处理逻辑,最后通过 ResultReceiver 回传数据。

步骤:
  1. 在应用端实现 IntentService 并使用 ResultReceiver

    public class MyIntentService extends IntentService {public MyIntentService() {super("MyIntentService");}@Overrideprotected 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);}
    }
    
  2. 在广播接收器中启动 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()) {@Overrideprotected void onReceiveResult(int resultCode, Bundle resultData) {String result = resultData.getString("result");Log.d("MyIntentService", result);}});context.startService(serviceIntent);}
    }
    
  3. 通过ADB发送广播

    adb shell am broadcast -a com.example.broadcast.MY_ACTION --es "key" "value"
    
  4. 通过 Logcat 查看IntentService的返回结果

    adb logcat | grep MyIntentService
    

5. 通过Content Provider回传结果

应用可以通过 ContentProvider 提供数据访问接口,ADB 可以通过 content 命令与 ContentProvider 交互,读取应用产生的结果数据。

步骤:
  1. 应用实现 ContentProvider 并在接收广播后插入数据

    @Override
    public void onReceive(Context context, Intent intent) {if ("com.example.broadcast.MY_ACTION".equals(intent.getAction())) {String userQuestion = intent.getStringExtra("key");// 插入结果到ContentProviderContentValues values = new ContentValues();values.put("result", "Processed question: " + userQuestion);context.getContentResolver().insert(MyContentProvider.CONTENT_URI, values);}
    }
    
  2. 通过ADB发送广播

    adb shell am broadcast -a com.example.broadcast.MY_ACTION --es "key" "value"
    
  3. 通过ADB查询 ContentProvider 获取结果

    adb shell content query --uri content://com.example.provider/results
    

    你可以通过 adb shell content 命令查询应用的 ContentProvider,获取应用插入的结果数据。

总结:

通过 adb shell am broadcast 发送广播指令后,你可以通过以下几种方式获取应用的返回信息:

  1. 通过 Logcat 查看日志
  2. 使用广播的 setResultData() 返回结果
  3. 通过文件共享,应用将结果写入文件后,使用 adb pull 拉取文件
  4. 使用 IntentServiceResultReceiver 机制异步回传数据
  5. 通过 ContentProvider 共享数据并使用 adb content 命令查询结果

这些方法可以根据应用的实际情况选择最适合的方式实现。

2 通过 ADB 使用 SocketHTTP 的方式与应用通信

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();}}}
}

你可以在应用的某个 ActivityService 中启动这个 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);}@Overridepublic 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);}
}

在应用的某个 ActivityService 中启动 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 方式

    1. 应用启动一个 Socket Server,监听端口。
    2. 使用 ADB 端口转发功能,将设备端口映射到本地端口。
    3. 通过本地 Socket 客户端与应用通信,并接收返回值。
  • HTTP 方式

    1. 应用启动一个 HTTP 服务端,监听 HTTP 请求。
    2. 使用 ADB 端口转发功能,将设备上的 HTTP 端口映射到本地端口。
    3. 通过浏览器、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.outPrintStream 的一个实例,它包装了 UNIX 标准输出流(stdout)。
    • 当你调用 System.out.println() 方法时,它将信息格式化为字符串,然后通过 PrintStream 将这些字符串写入 stdout
  • 输出重定向

    • 在 Android 设备上运行的应用是在一个 Linux-based 系统上的,每个应用都是一个独立的 Linux 进程。
    • 这些进程的 stdoutstderr 通常被重定向到了 /dev/null(一个丢弃所有写入数据的设备),但当通过 ADB 连接时,stdoutstderr 会被 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 操作系统内核包含一个日志驱动,负责管理日志设备。
    • 当应用通过 Log 类写日志时,日志消息被发送到内核的日志驱动,日志驱动将这些消息存储在一个或多个环形缓冲区中。每种类型的日志(如 “main”, “system”)都有自己的环形缓冲区。
  • 缓冲区和管理

    • 每个环形缓冲区都有固定的大小,当新的日志写入时,如果缓冲区已满,旧的日志将被新的日志覆盖。
    • 这些环形缓冲区是用户空间和内核空间之间的桥梁,用户空间的应用(或 adb logcat 命令)可以查询这些缓冲区来读取日志。
  • 日志的检索

    • 开发者通常使用 adb logcat 命令来读取和监控日志。
    • adb logcat 命令实际上是连接到这些环形缓冲区,根据指定的过滤条件(如日志级别、标签等)输出日志信息。
3. 稳定性和性能差异
  • System.out.println()

    • 优点:快速、直接,适合即时调试信息的输出。
    • 缺点:不适合长期日志记录,输出内容可能会在设备断开时丢失,不支持日志级别和过滤。
  • Logcat

    • 优点:支持日志级别和过滤,能够长时间记录日志,适合应用和系统的全面调试。
    • 缺点:由于依赖环形缓冲区和内核日志机制,处理速度可能较慢,且在缓冲区满时可能丢失日志。

总结

System.out.println() 提供了一种快速而直接的方式来输出调试信息,适合快速调试和测试。而 Logcat 提供了一个全面的日志系统,适合应用的深入调试和错误追踪,但其性能和稳定性可能受到环形缓冲区和内核日志机制的影响。理解这两种方法的内部工作原理有助于在开发过程中更有效地使用它们。

这篇关于Android中如何实现adb向应用发送特定指令并接收返回的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1148418

相关文章

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

zoj3820(树的直径的应用)

题意:在一颗树上找两个点,使得所有点到选择与其更近的一个点的距离的最大值最小。 思路:如果是选择一个点的话,那么点就是直径的中点。现在考虑两个点的情况,先求树的直径,再把直径最中间的边去掉,再求剩下的两个子树中直径的中点。 代码如下: #include <stdio.h>#include <string.h>#include <algorithm>#include <map>#

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time