Android中使用GRPC简明教程
引言
Android作为一个开发平台,本身是使用java进行封装的,因此java可以调用的库,在Android中同样可以进行调用,这样就使得Android设备具有丰富的功能,可以进行各种类型的开发。
这篇文章就介绍如何在Android设备中使用GRPC进行通信。
环境搭建
工欲善其事,必先利其器。首先我们先来进行开发环境的搭建。这里先要强调一下,Android开发中使用的项目管理工具Gradle对于版本的要求非常严格,如果不使用正确的版本号,可能导致程序报错,因此这一点需要特别注意。
我们在创建完一个项目后,需要修改一些文件的信息,具体需要修改的文件信息如下

对于上面的修改我们一个一个来看。
修改项目的setting.gradle信息
这个文件里面指定了gradle去哪个仓库中去找插件和第三方依赖库,我以及项目引入的模块信息。
我找到的一个可行的配置信息如下
pluginManagement {repositories {gradlePluginPortal()google()mavenCentral()}
}
dependencyResolutionManagement {repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories {google()mavenCentral()maven { url 'https://jitpack.io' }maven { url 'https://repo.eclipse.org/content/repositories/paho-releases/'}}
}
rootProject.name = "gprc_learn"
include ':app'
 
修改项目的build.gralde信息
项目目录下的build.gradle文件主要指定了项目中需要引入的插件,当然在这个文件中主要是下载插件,我们需要到具体的模块的build.gralde中去引入插件。
在这个项目中,主要指定gradle插件和protobuf插件,我找到的一个可行配置如下
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {repositories {maven{ url 'https://maven.aliyun.com/repository/jcenter'}maven { url 'https://maven.aliyun.com/repository/google' }maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }maven { url 'https://maven.aliyun.com/repository/public' }google()mavenCentral()}dependencies {classpath "com.android.tools.build:gradle:7.2.0"classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.17"}
}task clean(type: Delete) {delete rootProject.buildDir
} 
修改gradle版本号
这一步需要和你引入的gradle插件相关联,插件的版本和你引入的gradle版本必须要匹配才行,我引入的插件版本是7.2.0,引入的gralde版本是7.4。
修改gradle版本一共有两种方式,第一种就是在project structure中进行修改。

第二种方法就是直接在配置文件中进行修改

你需要哪个版本的gradle就直接在配置文件中指定对应版本的压缩包。
这两种修改方式都是等效的。
修改模块的build.gradle信息
模块的build.gradle中引入了插件,同时对插件做了一些配置,最最重要的就是引入第三方库。
我的配置信息如下
plugins {id 'com.android.application'id 'com.google.protobuf'
}android {namespace 'com.example.grpc_learn'compileSdk 32defaultConfig {applicationId "com.example.grpc_learn"minSdk 29targetSdk 32versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}configurations.all {resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.1'exclude group: 'com.google.guava', module: 'listenablefuture'}sourceSets {main {proto {srcDir 'src/main/proto'}}}packagingOptions {pickFirst 'META-INF/INDEX.LIST'pickFirst 'META-INF/LICENSE'pickFirst 'META-INF/io.netty.versions.properties'}
}protobuf {protoc {artifact = 'com.google.protobuf:protoc:3.17.2'}plugins {grpc {artifact = 'io.grpc:protoc-gen-grpc-java:1.39.0' // CURRENT_GRPC_VERSION}}generateProtoTasks {all().each { task ->task.builtins {java { option 'lite' }}task.plugins {grpc {option 'lite' }}}}
}dependencies {implementation 'androidx.appcompat:appcompat:1.4.1'implementation 'com.google.android.material:material:1.5.0'implementation 'androidx.constraintlayout:constraintlayout:2.1.3'testImplementation 'junit:junit:4.13.2'androidTestImplementation 'androidx.test.ext:junit:1.1.3'androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'implementation 'io.grpc:grpc-netty:1.39.0'implementation 'io.grpc:grpc-okhttp:1.39.0' // CURRENT_GRPC_VERSIONimplementation 'io.grpc:grpc-protobuf-lite:1.39.0' // CURRENT_GRPC_VERSIONimplementation 'io.grpc:grpc-stub:1.39.0' // CURRENT_GRPC_VERSIONimplementation 'org.apache.tomcat:annotations-api:6.0.53'} 
模块编译的时候会根据这个文件指定的信息进行操作。这里最好根据你自己的配置文件,然后对比看看和上述文件有哪些缺失的信息,一般只需要添加缺失的信息即可,如果完全照搬上面的内容可能导致项目报错,因为里面记录了你本身的项目信息,可能和我的项目信息产生冲突。
在main目录下创建proto目录
我们需要创建一个和java目录同级的proto文件夹,里面存放proto文件,这样做是因为在build.gradle文件中指定了去proto文件夹中找到*.proto文件,并且编译成java代码。
测试一下
做完上述的几个步骤后,我们可以编写一个简单的grpc通信模型,测试一下环境是否搭建成功。
首先在proto文件夹下编写hello.proto文件
syntax = "proto3";option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";package helloworld;// The greeting service definition.
service Greeter {// Sends a greetingrpc SayHello (HelloRequest) returns (HelloReply) {}
}// The request message containing the user's name.
message HelloRequest {string name = 1;
}// The response message containing the greetings
message HelloReply {string message = 1;
} 
然后编译项目,我们可以在build目录下看到对应的java文件

最后,我们可以使用一段简单的grpc通信代码看看是否可以正常通信,我们直接修改MainActivity文件即可
public class MainActivity extends AppCompatActivity {private static final String TAG = "GrpcDemo";private static final int PROT = 56322;private static final String NAME = "hello world";private static final String HOST = "localhost";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Log.d(TAG, "start");startServer(PROT);Log.d(TAG, "start server.");startClient(HOST, PROT, NAME);Log.d(TAG, "start client.");}private void startServer(int port){try {NettyServerBuilder.forPort(port).addService(new GreeterImpl()).build().start();} catch (IOException e) {e.printStackTrace();Log.d(TAG, e.getMessage());}}private void startClient(String host, int port, String name){ManagedChannel mChannel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();GreeterGrpc.GreeterStub stub = GreeterGrpc.newStub(mChannel);HelloRequest message = HelloRequest.newBuilder().setName(name).build();stub.sayHello(message, new StreamObserver<HelloReply>() {@Overridepublic void onNext(HelloReply value) {//Log.d(TAG, "sayHello onNext.");Log.d(TAG, value.getMessage());}@Overridepublic void onError(Throwable t) {Log.d(TAG, "sayHello onError.");}@Overridepublic void onCompleted() {Log.d(TAG, "sayHello onCompleted.");}});}private class GreeterImpl extends GreeterGrpc.GreeterImplBase {public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {responseObserver.onNext(sayHello(request));responseObserver.onCompleted();}private HelloReply sayHello(HelloRequest request) {return HelloReply.newBuilder().setMessage(request.getName()).build();}}} 
然后需要在AndroidManifest.xml文件中添加网络权限
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><!-- 添加网络权限 --><uses-permission android:name="android.permission.INTERNET"/><applicationandroid:allowBackup="true"android:dataExtractionRules="@xml/data_extraction_rules"android:fullBackupContent="@xml/backup_rules"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/Theme.Gprc_learn"tools:targetApi="31"><activityandroid:name=".MainActivity"android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter><meta-dataandroid:name="android.app.lib_name"android:value="" /></activity></application></manifest> 
最后编译运行,如果能看到控制台中有如下信息表示环境搭建成功了,好耶ヾ(✿゚▽゚)ノ

好了,到了这一步,我们可以将hello.proto和MainActivity中的代码清除啦,这只是为了测试环境是否搭建成功而编写的文件。
GRPC的四种通信模式
GRPC针对不同的业务场景,一共提供了四种通信模式,分别是简单一元模式,客户端流模式,服务端流模式和双向流模式,接下来这个进行介绍。
简单一元模式
所谓简单一元模式,实际上就是客户端和服务端进行一问一答的通信。

这种通信模式是最简单的,应用场景有无线设备之间和客户端之间保持连接的心跳检测,每隔一段时间就给服务端发送一个心跳检测包,服务端接收到心跳包后就知道相应客户端处于连接状态。
在客户端编写如下程序
    // 简单一元模式public void simpleHello() {// 构建简单的消息发送Request request = Request.newBuilder().setReqInfo("simpleHello").build();stub.simpleHello(request, new StreamObserver<Reply>() {@Overridepublic void onNext(Reply value) {Log.d(TAG, "simpleHello onNext.");String info = "[服务端->客户端]" + value.getRepInfo();sendInfo(info);}@Overridepublic void onError(Throwable t) {Log.d(TAG, "simpleHello onError.");}@Overridepublic void onCompleted() {Log.d(TAG, "simpleHello onCompleted.");}});} 
服务端也需要编写对应的处理程序
    @Overridepublic void simpleHello(Request request, StreamObserver<Reply> responseObserver) {Log.d(TAG, "服务端调用simpleHello.");String info = "[客户端->服务端]" + request.getReqInfo();sendInfo(info);responseObserver.onNext(Reply.newBuilder().setRepInfo("simpleHello").build());responseObserver.onCompleted();super.simpleHello(request, responseObserver);} 
客户端流模式
客户端流模式的意思就是客户端可以一次性发送多个数据片段,当然数据片段是一个类,具体的类有哪些字段都是你在最开始的proto文件中进行指定的。这种模式的应用场景就比如客户端向服务端发送一连串的数据,然后服务端最后发送一个响应数据表示接收成功。

在客户端流模式中,客户端可以在onCompleted之前使用多个onNext进行数据发送。
客户端代码如下
    // 客户端流模式public void clientStream() {StreamObserver<Request> requestStreamObserver = stub.clientStream(new StreamObserver<Reply>() {@Overridepublic void onNext(Reply value) {Log.d(TAG, "clientStream onNext.");String info = "[服务端->客户端]" + value.getRepInfo();sendInfo(info);}@Overridepublic void onError(Throwable t) {Log.d(TAG, "clientStream onError.");}@Overridepublic void onCompleted() {Log.d(TAG, "clientStream onCompleted.");}});requestStreamObserver.onNext(Request.newBuilder().setReqInfo("clientStream1").build());requestStreamObserver.onNext(Request.newBuilder().setReqInfo("clientStream2").build());requestStreamObserver.onCompleted();} 
服务端也需要编写相应代码
    @Overridepublic StreamObserver<Request> clientStream(StreamObserver<Reply> responseObserver) {StreamObserver<Request> streamObserver = new StreamObserver<Request>() {@Overridepublic void onNext(Request value) {Log.d(TAG, "clientStream onNext.");String info = "[服务端->客户端]" + value.getReqInfo();sendInfo(info);}@Overridepublic void onError(Throwable t) {Log.d(TAG, "clientStream onError.");}@Overridepublic void onCompleted() {Log.d(TAG, "clientStream onCompleted.");// 接收完所有消息后给客户端发送消息responseObserver.onNext(Reply.newBuilder().setRepInfo("clientStream").build());responseObserver.onCompleted();}};return streamObserver;} 
服务端流模式
服务端流模式和客户端流模式正好相反,本质都是差不多的,应用场景有客户端发送一个数据包告诉服务端,我需要某某数据,然后服务器将对应的所有信息都发送给客户端。

客户端和服务端代码分别如下所示
    // 服务端流模式public void serverStream() {Request request = Request.newBuilder().setReqInfo("serverStream").build();stub.serverStream(request, new StreamObserver<Reply>() {@Overridepublic void onNext(Reply value) {Log.d(TAG, "serverStream onNext.");String info = "[服务端->客户端]" + value.getRepInfo();sendInfo(info);}@Overridepublic void onError(Throwable t) {Log.d(TAG, "serverStream onError.");}@Overridepublic void onCompleted() {Log.d(TAG, "serverStream onCompleted.");}});} 
    @Overridepublic void serverStream(Request request, StreamObserver<Reply> responseObserver) {String info = "[客户端->服务端]" + request.getReqInfo();sendInfo(info);responseObserver.onNext(Reply.newBuilder().setRepInfo("serverStream1").build());responseObserver.onNext(Reply.newBuilder().setRepInfo("serverStream2").build());responseObserver.onCompleted();super.serverStream(request, responseObserver);} 
双向流模式
双向流模式是最后一种,也是最常用的一种,在这种模式中,客户端和服务端的通信没有什么限制,是比较理想的通信模式,应用场景也最为广泛,因为在这种模式中,你也可以只发送一个数据包。

客户端和服务端的代码如下
    // 双向流模式public void bothFlowStream() {StreamObserver<Request> streamObserver = stub.bothFlowStream(new StreamObserver<Reply>() {@Overridepublic void onNext(Reply value) {Log.d(TAG, "bothFlowStream onNext.");String info = "[服务端->客户端]" + value.getRepInfo();sendInfo(info);}@Overridepublic void onError(Throwable t) {Log.d(TAG, "bothFlowStream onError.");}@Overridepublic void onCompleted() {Log.d(TAG, "bothFlowStream onCompleted.");}});streamObserver.onNext(Request.newBuilder().setReqInfo("bothFlowStream1").build());streamObserver.onNext(Request.newBuilder().setReqInfo("bothFlowStream2").build());streamObserver.onCompleted();} 
    @Overridepublic StreamObserver<Request> bothFlowStream(StreamObserver<Reply> responseObserver) {StreamObserver<Request> streamObserver = new StreamObserver<Request>() {@Overridepublic void onNext(Request value) {Log.d(TAG, "bothFlowStream onNext.");String info = "[客户端->服务端]" + value.getReqInfo();sendInfo(info);}@Overridepublic void onError(Throwable t) {Log.d(TAG, "bothFlowStream onError.");}@Overridepublic void onCompleted() {Log.d(TAG, "bothFlowStream onCompleted.");responseObserver.onNext(Reply.newBuilder().setRepInfo("bothFlowStream1").build());responseObserver.onNext(Reply.newBuilder().setRepInfo("bothFlowStream2").build());responseObserver.onCompleted();}};return streamObserver;} 
简单的GRPC客户端服务端程序设计
上面介绍了GRPC的四种通信模式,以及各种模式中客户端和服务端对应的编写方法。
下面来介绍一下我们具体应该如何编写客户端服务端代码。
我们一般会将客户端和服务端分开来编写,具体的文件如下图所示

首先需要编写hello.proto文件,并且编译后生成对应的java文件,我们在proto文件中编写了两个类用来请求和相应,并且编写了四个接口方法,分别对应GRPC请求响应的四种模式
syntax = "proto3";option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";package helloworld;service Greeter {// 简单一元模式rpc simpleHello (Request) returns (Reply) {}// 客户端流模式rpc clientStream (stream Request) returns (Reply) {}// 服务端流模式rpc serverStream (Request) returns (stream Reply) {}// 双向流模式rpc bothFlowStream (stream Request) returns (stream Reply) {}
}message Request {string reqInfo = 1;
}message Reply {string repInfo = 1;
} 
客户端我们只需要编写一个文件即可
public class GRPCClient {private final String TAG = GRPCClient.class.toString();private final String host = "localhost";private final int port = 55056;private Context context;private ManagedChannel managedChannel;private GreeterGrpc.GreeterStub stub;// 在构造函数中连接public GRPCClient(Context context) {this.context = context;managedChannel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();stub = GreeterGrpc.newStub(managedChannel);}// 使用广播的方法发送数据更新uiprivate void sendInfo(String info) {Intent intent = new Intent("main.info");intent.putExtra("info", info);context.sendBroadcast(intent);}// 简单一元模式public void simpleHello() {// 构建简单的消息发送Request request = Request.newBuilder().setReqInfo("simpleHello").build();stub.simpleHello(request, new StreamObserver<Reply>() {@Overridepublic void onNext(Reply value) {Log.d(TAG, "simpleHello onNext.");String info = "[服务端->客户端]" + value.getRepInfo();sendInfo(info);}@Overridepublic void onError(Throwable t) {Log.d(TAG, "simpleHello onError.");}@Overridepublic void onCompleted() {Log.d(TAG, "simpleHello onCompleted.");}});}// 客户端流模式public void clientStream() {StreamObserver<Request> requestStreamObserver = stub.clientStream(new StreamObserver<Reply>() {@Overridepublic void onNext(Reply value) {Log.d(TAG, "clientStream onNext.");String info = "[服务端->客户端]" + value.getRepInfo();sendInfo(info);}@Overridepublic void onError(Throwable t) {Log.d(TAG, "clientStream onError.");}@Overridepublic void onCompleted() {Log.d(TAG, "clientStream onCompleted.");}});requestStreamObserver.onNext(Request.newBuilder().setReqInfo("clientStream1").build());requestStreamObserver.onNext(Request.newBuilder().setReqInfo("clientStream2").build());requestStreamObserver.onCompleted();}// 服务端流模式public void serverStream() {Request request = Request.newBuilder().setReqInfo("serverStream").build();stub.serverStream(request, new StreamObserver<Reply>() {@Overridepublic void onNext(Reply value) {Log.d(TAG, "serverStream onNext.");String info = "[服务端->客户端]" + value.getRepInfo();sendInfo(info);}@Overridepublic void onError(Throwable t) {Log.d(TAG, "serverStream onError.");}@Overridepublic void onCompleted() {Log.d(TAG, "serverStream onCompleted.");}});}// 双向流模式public void bothFlowStream() {StreamObserver<Request> streamObserver = stub.bothFlowStream(new StreamObserver<Reply>() {@Overridepublic void onNext(Reply value) {Log.d(TAG, "bothFlowStream onNext.");String info = "[服务端->客户端]" + value.getRepInfo();sendInfo(info);}@Overridepublic void onError(Throwable t) {Log.d(TAG, "bothFlowStream onError.");}@Overridepublic void onCompleted() {Log.d(TAG, "bothFlowStream onCompleted.");}});streamObserver.onNext(Request.newBuilder().setReqInfo("bothFlowStream1").build());streamObserver.onNext(Request.newBuilder().setReqInfo("bothFlowStream2").build());streamObserver.onCompleted();}} 
在构造函数中,我们需要和服务端建立连接,所以一般需要服务端先启动。在连接建立完成后,无论调用什么方法都采用一个连接,然后分别编写GRPC对应的不同服务接口。
服务端可以分成两个类来编写,其中GRPCServer主要用来启动服务端,GRPCServiceImpl则是继承了GreeterGrpc.GreeterImplBase,可以重写里面的方法,表示服务端如何处理GRPC请求。
public class GRPCServer {private final String TAG = GRPCServer.class.toString();private final int port = 55056;private Context context;public GRPCServer(Context context) {this.context = context;start();Log.d(TAG, "服务端启动");sendInfo("服务端启动");}// 使用广播的方法发送数据更新uiprivate void sendInfo(String info) {Intent intent = new Intent("main.info");intent.putExtra("info", info);context.sendBroadcast(intent);}private void start() {try {NettyServerBuilder.forPort(port).addService(new GRPCServiceImpl(context)).build().start();} catch (IOException e) {e.printStackTrace();}}} 
public class GRPCServiceImpl extends GreeterGrpc.GreeterImplBase {private final String TAG = GRPCServiceImpl.class.toString();private Context context;public GRPCServiceImpl(Context context) {this.context = context;}// 使用广播的方法发送数据更新uiprivate void sendInfo(String info) {Intent intent = new Intent("main.info");intent.putExtra("info", info);context.sendBroadcast(intent);}@Overridepublic void simpleHello(Request request, StreamObserver<Reply> responseObserver) {Log.d(TAG, "服务端调用simpleHello.");String info = "[客户端->服务端]" + request.getReqInfo();sendInfo(info);responseObserver.onNext(Reply.newBuilder().setRepInfo("simpleHello").build());responseObserver.onCompleted();super.simpleHello(request, responseObserver);}@Overridepublic StreamObserver<Request> clientStream(StreamObserver<Reply> responseObserver) {StreamObserver<Request> streamObserver = new StreamObserver<Request>() {@Overridepublic void onNext(Request value) {Log.d(TAG, "clientStream onNext.");String info = "[服务端->客户端]" + value.getReqInfo();sendInfo(info);}@Overridepublic void onError(Throwable t) {Log.d(TAG, "clientStream onError.");}@Overridepublic void onCompleted() {Log.d(TAG, "clientStream onCompleted.");// 接收完所有消息后给客户端发送消息responseObserver.onNext(Reply.newBuilder().setRepInfo("clientStream").build());responseObserver.onCompleted();}};return streamObserver;}@Overridepublic void serverStream(Request request, StreamObserver<Reply> responseObserver) {String info = "[客户端->服务端]" + request.getReqInfo();sendInfo(info);responseObserver.onNext(Reply.newBuilder().setRepInfo("serverStream1").build());responseObserver.onNext(Reply.newBuilder().setRepInfo("serverStream2").build());responseObserver.onCompleted();super.serverStream(request, responseObserver);}@Overridepublic StreamObserver<Request> bothFlowStream(StreamObserver<Reply> responseObserver) {StreamObserver<Request> streamObserver = new StreamObserver<Request>() {@Overridepublic void onNext(Request value) {Log.d(TAG, "bothFlowStream onNext.");String info = "[客户端->服务端]" + value.getReqInfo();sendInfo(info);}@Overridepublic void onError(Throwable t) {Log.d(TAG, "bothFlowStream onError.");}@Overridepublic void onCompleted() {Log.d(TAG, "bothFlowStream onCompleted.");responseObserver.onNext(Reply.newBuilder().setRepInfo("bothFlowStream1").build());responseObserver.onNext(Reply.newBuilder().setRepInfo("bothFlowStream2").build());responseObserver.onCompleted();}};return streamObserver;}
}
 
我们采用一个简单的布局,就是四个按钮,分别对应GRPC的四个接口,然后在显示客户端和服务端发送给MainActivity的信息。这里面我们在信息传递的时候采用了广播的方法,为了能够发送广播,在实例化客户端和服务端类的时候都需要传递Context作为参数,这个Context就可以发送广播了,然后在MainActivity中需要注册一个广播接收器,当接收到具体信息的时候就更新ui。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><Buttonandroid:id="@+id/button1"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1"android:text="一元模式" /><Buttonandroid:id="@+id/button2"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1"android:text="客户端流模式" /></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><Buttonandroid:id="@+id/button3"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1"android:text="服务端流模式" /><Buttonandroid:id="@+id/button4"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1"android:text="双向流模式" /></LinearLayout><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="@string/text_title" /><TextViewandroid:id="@+id/text_info"android:layout_width="match_parent"android:layout_height="418dp" /></LinearLayout> 
public class MainActivity extends AppCompatActivity {private final String TAG = MainActivity.class.toString();private Button button1;private Button button2;private Button button3;private Button button4;private TextView text_info;// 服务端和客户端private GRPCClient grpcClient;private GRPCServer grpcServer;// 注册一个广播用于更新uiprivate BroadcastReceiver broadcastReceiver;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化控件initView();// 注册广播接收器register();// 初始化服务端和客户端grpcClient = new GRPCClient(MainActivity.this);grpcServer = new GRPCServer(MainActivity.this);}// 初始化控件private void initView() {button1 = findViewById(R.id.button1);button1.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {grpcClient.simpleHello();}});button2 = findViewById(R.id.button2);button2.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {grpcClient.clientStream();}});button3 = findViewById(R.id.button3);button3.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {grpcClient.serverStream();}});button4 = findViewById(R.id.button4);button4.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {grpcClient.bothFlowStream();}});text_info = findViewById(R.id.text_info);text_info.setMovementMethod(new ScrollingMovementMethod());}// 注册广播更新uiprivate void register() {broadcastReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {Log.d(TAG, "广播收到消息" + intent.getStringExtra("info"));text_info.append(intent.getStringExtra("info") + "\n");}};IntentFilter filter = new IntentFilter("main.info");registerReceiver(broadcastReceiver, filter);}} 
最后在虚拟机上运行程序,依次点击四个按钮,如果得到了下图的结果,则表示程序跑通啦,好耶ヽ(✿゚▽゚)ノ

最后,欢迎关注我的公众号(●'◡'●) dangao123coding

相关文章:
Android中使用GRPC简明教程
引言 Android作为一个开发平台,本身是使用java进行封装的,因此java可以调用的库,在Android中同样可以进行调用,这样就使得Android设备具有丰富的功能,可以进行各种类型的开发。 这篇文章就介绍如何在Android设备中使…...
【Linux】使用U盘自动化安装Linux(VMware虚拟机)
文章目录前言一、准备二、新建虚拟机2.1 创建虚拟机2.2 新增硬盘2.3 系统启动项三、加电运行四、EFI方式五、总结前言 一、准备 基于之前的基础【Linux】Kickstart 配置U盘自动化安装Linux系统,现在我们可以在虚拟机中尝试自动化安装Linux系统。 二、新建虚拟机 …...
内网渗透(五十七)之域控安全和跨域攻击-基于服务账户的非约束委派攻击
系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内网渗透(五)之基础知识-Active Directory活动目录介绍和使用 内网渗透(六)之基…...
gitlab 安装到项目上传一篇解决
文章目录1.安装1.1创建挂载目录1.2启动1.3 配置gitlab查看docker admin 账户初始密码注册普通用户2.1进入注册2.2创建后通过登录admin审批3.2 步骤13.2 步骤23.3步骤33.4 项目添加成员4 使用成员用户,上传到新建的项目中4.1 复制项目地址4.2使用 git here 克隆项目4.3进入下载目…...
Verilog 逻辑与()、按位与()、逻辑或(||)、按位或(|)、等于(==)、全等(===)的区别
逻辑与(&&)逻辑与是一个双目运算符,当符号两边为1时输出1,符号两边为0时输出0。真值表:&&01xz00000101xxx0xxxz0xxx两个4bit的数字相与;A4b0x1z;B4b01xx;C4b00xz&am…...
剑指 Offer 22. 链表中倒数第k个节点
剑指 Offer 22. 链表中倒数第k个节点 难度:easy\color{Green}{easy}easy 题目描述 输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。 例如,一个链…...
数据结构预算法之买卖股票的最好时机(三)动态规划
目录:一.题目知识点:动态规划二.动态规划数组思路确定1.dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组一.题目知识点:动态规划动态规划算法的基本思想是:将待求解的问题分解成若干个相互联…...
【数通网络交换基础梳理2】三层设备、网关、ARP表、VLAN、路由表及跨网段路由下一跳转发原理
一、不同网段如何通讯 同网段可以依靠二层交换机通讯,网络中存在多个网段192.168.1.1/24 172.16.1.1/24 173.73.1.1/24情况下如何互相通讯?上节留一下的问题,这节继续讲解。 1、这里以Ping命令讲解,PC1 ping173.73.1.2…...
Java-排序链表问题
Java-排序链表问题题目题解方法:自顶向下归并排序算法题目 给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。 示例 1: 示例 2: 示例 3: 提示: *链表中节点的数目在范围 [0, 5 * 104]…...
c++之二叉树【进阶版】
前言 在c语言阶段的数据结构系列中已经学习过二叉树,但是这篇文章是二叉树的进阶版,因为首先就会讲到一种树形结构“二叉搜索树”,学习二叉搜索树的目标是为了更好的理解map和set的特性。二叉搜索树的特性就是左子树键值小于根,右…...
【数据库】 SQLServer
SQL Server 安装 配置 修改SQL Server默认的数据库文件保存路径_ 认识 master :是SQL Server中最重要的系统数据 库,存储SQL Server中的元数据。 Model:模板数据库,在创建新的数据库时,SQL Server 将会复制此数据…...
Linux 4.19 内核中 spinlock 概览
Linux内核中 spinlock相关数据结构和代码实现涉及的文件还是挺多的,这篇博客尝试从文件的角度来梳理一下 spinlock的相关数据结构和代码实现,适合想大概了解 Linux内核中 spinlock从上层 API到底层实现间的调用路径和传参变化,尤其适合了解 s…...
TensorFlow 1.x学习(系列二 :1):基本概念TensorFlow的基本介绍,图,会话,会话中的run(),placeholder(),常见的报错
目录1.基本介绍2.图的结构3.会话,会话的run方法4.placeholder5.返回值异常写在前边的话:之前发布过一个关于TensorFlow1.x的转载系列,自己将基本的TensorFlow操作敲了一遍,但是仍然有很多地方理解的不够深入。所以重开一个系列&am…...
javaEE 初阶 — 关于 IPv4、IPv6 协议、NAT(网络地址转换)、动态分配 IP 地址 的介绍
文章目录1. IPv42. IPv63. NAT4. 动态分配 IP 地址1. IPv4 在互联网的世界中只有 0 和1 ,所以每个人都有一个由 0 和 1 组成的地址来让别人找到你。 这段由 0 和 1 组成的地址叫 IP 地址,这是互联网的基础资源,可以简单的理解为互联网的土地。…...
《Qt 6 C++开发指南》简介
我们编写的新书《Qt 6 C开发指南》在2月份终于正式发行销售了,这本书是对2018年5月出版的《Qt 5.9 C开发指南》的重磅升级。以下是本书前言的部分内容,算是对《Qt 6 C开发指南》的一个简介。1.编写本书的目的《Qt 5.9C开发指南》是我写的第一…...
CleanMyMac是什么清理软件?及使用教程
你知道CleanMyMac是什么吗?它的字面意思为“清理我的Mac”,作为软件,那就是一款Mac清理工具,Mac OS X 系统下知名系统清理软件,是数以万计的Mac用户的选择。它可以流畅地与系统性能相结合,只需简单的步骤就…...
Linux小黑板(9):共享内存
"My poor lost soul"上章花了不少的篇幅讲了讲基于管道((匿名、命名))技术实现的进程间通信。进程为什么需要通信?目的是为了完成进程间的"协同",提高处理数据的能力、优化业务逻辑的实现等等,在linux中我们已经谈过了一个通信的大类…...
Detr源码解读(mmdetection)
Detr源码解读(mmdetection) 1、原理简要介绍 整体流程: 在给定一张输入图像后,1)特征向量提取: 首先经过ResNet提取图像的最后一层特征图F。注意此处仅仅用了一层特征图,是因为后续计算复杂度原因,另外&am…...
一个.Net Core开发的,撑起月6亿PV开源监控解决方案
更多开源项目请查看:一个专注推荐.Net开源项目的榜单 项目发布后,对于我们程序员来说,项目还不是真正的结束,保证项目的稳定运行也是非常重要的,而对于服务器的监控,就是保证稳定运行的手段之一。对数据库、…...
C语言数据结构初阶(2)----顺序表
目录 1. 顺序表的概念及结构 2. 动态顺序表的接口实现 2.1 SLInit(SL* ps) 的实现 2.2 SLDestory(SL* ps) 的实现 2.3 SLPrint(SL* ps) 的实现 2.4 SLCheckCapacity(SL* ps) 的实现 2.5 SLPushBack(SL* ps, SLDataType x) 的实现 2.6 SLPopBack(SL* ps) 的实现 2.7 SLP…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...
Golang——7、包与接口详解
包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...
[论文阅读]TrustRAG: Enhancing Robustness and Trustworthiness in RAG
TrustRAG: Enhancing Robustness and Trustworthiness in RAG [2501.00879] TrustRAG: Enhancing Robustness and Trustworthiness in Retrieval-Augmented Generation 代码:HuichiZhou/TrustRAG: Code for "TrustRAG: Enhancing Robustness and Trustworthin…...
用递归算法解锁「子集」问题 —— LeetCode 78题解析
文章目录 一、题目介绍二、递归思路详解:从决策树开始理解三、解法一:二叉决策树 DFS四、解法二:组合式回溯写法(推荐)五、解法对比 递归算法是编程中一种非常强大且常见的思想,它能够优雅地解决很多复杂的…...
生信服务器 | 做生信为什么推荐使用Linux服务器?
原文链接:生信服务器 | 做生信为什么推荐使用Linux服务器? 一、 做生信为什么推荐使用服务器? 大家好,我是小杜。在做生信分析的同学,或是将接触学习生信分析的同学,<font style"color:rgb(53, 1…...
