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…...

K8S常用命令速查手册
K8S常用命令速查手册一. K8S日常维护常用命令1.1 查看kubectl版本1.2 启动kubelet1.3 master节点执行查看所有的work-node节点列表1.4 查看所有的pod1.5 检查kubelet运行状态排查问题1.6 诊断某pod故障1.7 诊断kubelet故障方式一1.8 诊断kubelet故障方式二二. 端口策略相关2.1 …...

Linux系统下命令行安装MySQL5.6+详细步骤
1、因为想在腾讯云的服务器上创建自己的数据库,所以我在这里是通过使用Xshell 7来连接腾讯云的远程服务器; 2、Xshell 7与服务器连接好之后,就可以开始进行数据库的安装了(如果服务器曾经安装过数据库,得将之前安装的…...

13.STM32超声波模块讲解与实战
目录 1.超声波模块讲解 2.超声波时序图 3.超声波测距步骤 4.项目实战 1.超声波模块讲解 超声波传感器模块上面通常有两个超声波元器件,一个用于发射,一个用于接收。电路板上有4个引脚:VCC GND Trig(触发)ÿ…...

逆向之Windows PE结构
写在前面 对于Windows PE文件结构,个人认为还是非常有必要掌握和了解的,不管是在做逆向分析、免杀、病毒分析,脱壳加壳都是有着非常重要的技能。但是PE文件的学习又是一个非常枯燥过程,希望本文可以帮你有一个了解。 PE文件结构…...

ACL是什么
目录 一、ACL是什么 二、ACL的使用:setacl与getacl 1)针对特定使用者的方式: 1. 创建acl_test1后设置其权限 2. 读取acl_test1的权限 2)针对特定群组的方式: 3)针对有效权限 mask 的设置方式…...

操作系统核心知识点整理--内存篇
操作系统核心知识点整理--内存篇按段对内存进行管理内存分区内存分页为什么需要多级页表TLB解决了多级页表什么样的缺陷?TLB缓存命中率高的原理是什么?段页结合: 为什么需要虚拟内存?虚拟地址到物理地址的转换过程段页式管理下程序如何载入内存?页面置…...

从零开始学习iftop流量监控(找出服务器耗费流量最多的ip和端口)
一、iftop是什么iftop是类似于top的实时流量监控工具。作用:监控网卡的实时流量(可以指定网段)、反向解析IP、显示端口信息等官网:http://www.ex-parrot.com/~pdw/iftop/二、界面说明>代表发送数据,< 代表接收数…...

第一篇博客------自我介绍篇
目录🔆自我介绍🔆学习目标🔆如何学习单片机Part 1 基础理论知识学习Part 2 单片机实践Part 3 单片机硬件设计🔆希望进入的公司🔆结束语🔆自我介绍 Hello!!!我是一名即已经步入大二的计算机小白。 --------…...

No suitable device found for this connection (device lo not available(网络突然出问题)
当执行 ifup ens33 出现错误:[rootlocalhost ~]# ifup ens33Error: Connection activation failed: No suitable device found for this connection (device lo not available because device is strictly unmanaged).1解决办法:[rootlocalhost ~]# chkc…...

【算法设计技巧】分治算法
分治算法 用于设计算法的另一种常用技巧为分治算法(divide and conquer)。分治算法由两部分组成: 分(divide):递归解决较小的问题(当然,基准情况除外)治(conquer):然后,从子问题的解构建原问题的解。 传统上&#x…...