Android中使用GRPC简明教程

news2024/9/28 15:30:50

引言

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 32

    defaultConfig {
        applicationId "com.example.grpc_learn"
        minSdk 29
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility 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_VERSION
    implementation 'io.grpc:grpc-protobuf-lite:1.39.0' // CURRENT_GRPC_VERSION
    implementation 'io.grpc:grpc-stub:1.39.0' // CURRENT_GRPC_VERSION
    implementation '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 greeting
    rpc 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";

    @Override
    protected 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>() {
            @Override
            public void onNext(HelloReply value) {
                //Log.d(TAG, "sayHello onNext.");
                Log.d(TAG, value.getMessage());
            }

            @Override
            public void onError(Throwable t) {
                Log.d(TAG, "sayHello onError.");
            }

            @Override
            public 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"/>
    <application
        android: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">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <meta-data
                android: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>() {
            @Override
            public void onNext(Reply value) {
                Log.d(TAG, "simpleHello onNext.");
                String info = "[服务端->客户端]" + value.getRepInfo();
                sendInfo(info);
            }

            @Override
            public void onError(Throwable t) {
                Log.d(TAG, "simpleHello onError.");
            }

            @Override
            public void onCompleted() {
                Log.d(TAG, "simpleHello onCompleted.");
            }
        });
    }

 服务端也需要编写对应的处理程序

    @Override
    public 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>() {
            @Override
            public void onNext(Reply value) {
                Log.d(TAG, "clientStream onNext.");
                String info = "[服务端->客户端]" + value.getRepInfo();
                sendInfo(info);
            }

            @Override
            public void onError(Throwable t) {
                Log.d(TAG, "clientStream onError.");
            }

            @Override
            public void onCompleted() {
                Log.d(TAG, "clientStream onCompleted.");
            }
        });
        requestStreamObserver.onNext(Request.newBuilder().setReqInfo("clientStream1").build());
        requestStreamObserver.onNext(Request.newBuilder().setReqInfo("clientStream2").build());
        requestStreamObserver.onCompleted();
    }

服务端也需要编写相应代码

    @Override
    public StreamObserver<Request> clientStream(StreamObserver<Reply> responseObserver) {
        StreamObserver<Request> streamObserver = new StreamObserver<Request>() {
            @Override
            public void onNext(Request value) {
                Log.d(TAG, "clientStream onNext.");
                String info = "[服务端->客户端]" + value.getReqInfo();
                sendInfo(info);
            }

            @Override
            public void onError(Throwable t) {
                Log.d(TAG, "clientStream onError.");
            }

            @Override
            public 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>() {
            @Override
            public void onNext(Reply value) {
                Log.d(TAG, "serverStream onNext.");
                String info = "[服务端->客户端]" + value.getRepInfo();
                sendInfo(info);
            }

            @Override
            public void onError(Throwable t) {
                Log.d(TAG, "serverStream onError.");
            }

            @Override
            public void onCompleted() {
                Log.d(TAG, "serverStream onCompleted.");
            }
        });
    }
    @Override
    public 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>() {
            @Override
            public void onNext(Reply value) {
                Log.d(TAG, "bothFlowStream onNext.");
                String info = "[服务端->客户端]" + value.getRepInfo();
                sendInfo(info);
            }

            @Override
            public void onError(Throwable t) {
                Log.d(TAG, "bothFlowStream onError.");
            }

            @Override
            public void onCompleted() {
                Log.d(TAG, "bothFlowStream onCompleted.");
            }
        });
        streamObserver.onNext(Request.newBuilder().setReqInfo("bothFlowStream1").build());
        streamObserver.onNext(Request.newBuilder().setReqInfo("bothFlowStream2").build());
        streamObserver.onCompleted();
    }
    @Override
    public StreamObserver<Request> bothFlowStream(StreamObserver<Reply> responseObserver) {
        StreamObserver<Request> streamObserver = new StreamObserver<Request>() {
            @Override
            public void onNext(Request value) {
                Log.d(TAG, "bothFlowStream onNext.");
                String info = "[客户端->服务端]" + value.getReqInfo();
                sendInfo(info);
            }

            @Override
            public void onError(Throwable t) {
                Log.d(TAG, "bothFlowStream onError.");
            }

            @Override
            public 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);
    }

    // 使用广播的方法发送数据更新ui
    private 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>() {
            @Override
            public void onNext(Reply value) {
                Log.d(TAG, "simpleHello onNext.");
                String info = "[服务端->客户端]" + value.getRepInfo();
                sendInfo(info);
            }

            @Override
            public void onError(Throwable t) {
                Log.d(TAG, "simpleHello onError.");
            }

            @Override
            public void onCompleted() {
                Log.d(TAG, "simpleHello onCompleted.");
            }
        });
    }

    // 客户端流模式
    public void clientStream() {
        StreamObserver<Request> requestStreamObserver = stub.clientStream(new StreamObserver<Reply>() {
            @Override
            public void onNext(Reply value) {
                Log.d(TAG, "clientStream onNext.");
                String info = "[服务端->客户端]" + value.getRepInfo();
                sendInfo(info);
            }

            @Override
            public void onError(Throwable t) {
                Log.d(TAG, "clientStream onError.");
            }

            @Override
            public 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>() {
            @Override
            public void onNext(Reply value) {
                Log.d(TAG, "serverStream onNext.");
                String info = "[服务端->客户端]" + value.getRepInfo();
                sendInfo(info);
            }

            @Override
            public void onError(Throwable t) {
                Log.d(TAG, "serverStream onError.");
            }

            @Override
            public void onCompleted() {
                Log.d(TAG, "serverStream onCompleted.");
            }
        });
    }

    // 双向流模式
    public void bothFlowStream() {
        StreamObserver<Request> streamObserver = stub.bothFlowStream(new StreamObserver<Reply>() {
            @Override
            public void onNext(Reply value) {
                Log.d(TAG, "bothFlowStream onNext.");
                String info = "[服务端->客户端]" + value.getRepInfo();
                sendInfo(info);
            }

            @Override
            public void onError(Throwable t) {
                Log.d(TAG, "bothFlowStream onError.");
            }

            @Override
            public 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("服务端启动");
    }

    // 使用广播的方法发送数据更新ui
    private 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;
    }

    // 使用广播的方法发送数据更新ui
    private void sendInfo(String info) {
        Intent intent = new Intent("main.info");
        intent.putExtra("info", info);
        context.sendBroadcast(intent);
    }

    @Override
    public 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);
    }

    @Override
    public StreamObserver<Request> clientStream(StreamObserver<Reply> responseObserver) {
        StreamObserver<Request> streamObserver = new StreamObserver<Request>() {
            @Override
            public void onNext(Request value) {
                Log.d(TAG, "clientStream onNext.");
                String info = "[服务端->客户端]" + value.getReqInfo();
                sendInfo(info);
            }

            @Override
            public void onError(Throwable t) {
                Log.d(TAG, "clientStream onError.");
            }

            @Override
            public void onCompleted() {
                Log.d(TAG, "clientStream onCompleted.");
                // 接收完所有消息后给客户端发送消息
                responseObserver.onNext(Reply.newBuilder().setRepInfo("clientStream").build());
                responseObserver.onCompleted();
            }
        };
        return streamObserver;
    }

    @Override
    public 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);
    }

    @Override
    public StreamObserver<Request> bothFlowStream(StreamObserver<Reply> responseObserver) {
        StreamObserver<Request> streamObserver = new StreamObserver<Request>() {
            @Override
            public void onNext(Request value) {
                Log.d(TAG, "bothFlowStream onNext.");
                String info = "[客户端->服务端]" + value.getReqInfo();
                sendInfo(info);
            }

            @Override
            public void onError(Throwable t) {
                Log.d(TAG, "bothFlowStream onError.");
            }

            @Override
            public 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">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/button1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="一元模式" />
        <Button
            android:id="@+id/button2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="客户端流模式" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/button3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="服务端流模式" />
        <Button
            android:id="@+id/button4"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="双向流模式" />
    </LinearLayout>


    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/text_title" />

    <TextView
        android: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;

    // 注册一个广播用于更新ui
    private BroadcastReceiver broadcastReceiver;

    @Override
    protected 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() {
            @Override
            public void onClick(View v) {
                grpcClient.simpleHello();
            }
        });
        button2 = findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                grpcClient.clientStream();
            }
        });
        button3 = findViewById(R.id.button3);
        button3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                grpcClient.serverStream();
            }
        });
        button4 = findViewById(R.id.button4);
        button4.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                grpcClient.bothFlowStream();
            }
        });

        text_info = findViewById(R.id.text_info);
        text_info.setMovementMethod(new ScrollingMovementMethod());
    }


    // 注册广播更新ui
    private void register() {
        broadcastReceiver = new BroadcastReceiver() {
            @Override
            public 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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/371241.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Cloud Nacos源码讲解(七)- Nacos客户端服务订阅机制的核心流程

Nacos客户端服务订阅机制的核心流程 ​ 说起Nacos的服务订阅机制&#xff0c;大家会觉得比较难理解&#xff0c;那我们就来详细分析一下&#xff0c;那我们先从Nacos订阅的概述说起 Nacos订阅概述 ​ Nacos的订阅机制&#xff0c;如果用一句话来描述就是&#xff1a;Nacos客…

十四、项目实战二(CORS、同源策略、cookie跨域)

项目实战二 需求 以前后端分离的方式实现学生的增删改查操作 学生列表接口 url:/students/ 请求方法&#xff1a;get 参数&#xff1a; 格式&#xff1a;查询参数 参数名类型是否必传说明pageint否页码&#xff0c;默认为1sizeinit否每页数据条数默认为10namestr否根据姓…

SpringBoot+HttpClient+JsonPath提取A接口返回值作为参数调用B接口

前言 在做java接口自动化中&#xff0c;我们常常需要依赖多个接口&#xff0c;A接口依赖B&#xff0c;C&#xff0c;D接口的响应作为请求参数&#xff1b;或者URL中的参数是从其他接口中提取返回值作获取参数这是必不可少的。那么怎么实现呢&#xff1f;下面就来介绍多业务依赖…

大数据技术之Hive(三)函数

hive有大量内置函数&#xff0c;大致可分为&#xff1a;单行函数、聚合函数、炸裂函数、窗口函数。查看内置函数show functions;查看内置函数用法desc function upper;查看内置函数详细信息desc function extended upper;一、单行函数单行函数的特点是一进一出&#xff0c;输入…

Kubernetes之服务的基本管理

svc是kubernetes最核心的概念&#xff0c;通过创建Service&#xff0c;可以为一组具有相同功能的容器应用提供一个统一的入口地址&#xff0c;并将请求进行负载分发到后端的各个容器应用上。pod生命周期短不稳定&#xff0c;pod异常后新生成的pod的IP会发生变化&#xff0c;通过…

JSD2212班第二次串讲-面向对象阶段

1. 概述 系统介绍一下接下来阶段要学习的内容&#xff1a; 1.JAVA&#xff1a; 1.1 java语言基础 1.2 java面向对象阶段&#xff08;很重要&#xff01;起的承上启下&#xff01;&#xff01;&#xff09; 1.3 API阶段&#xff08;Application Interface &#xff09;学会看字…

Spring Cloud @RefreshScope 原理分析:代理类的创建

背景 前面我们知道&#xff0c;被 RefreshScope 注解的实例&#xff0c;在扫描生成 BeanDefiniton 时&#xff0c;被偷龙转凤了&#xff1a;注册了两个 Bean 定义&#xff0c;一个 beanName 同名、类型是 LockedScopedProxyFactoryBean.class 代理工厂 Bean&#xff0c;一个 s…

6-Java中新建一个文件、目录、路径

文章目录前言1-文件、目录、路径2-在当前路径下创建一个文件3-在当前路径下创建一个文件夹&#xff08;目录&#xff09;3.1 测试1-路径已经存在3.2 测试2-路径不存在3.2 创建不存在的路径并新建文件3.3 删除已存在的文件并新建4-总结前言 学习Java中如何新建文件、目录、路径…

ASE40N50SH-ASEMI高压MOS管ASE40N50SH

编辑-Z ASE40N50SH在TO-247封装里的静态漏极源导通电阻&#xff08;RDS(ON)&#xff09;为100mΩ&#xff0c;是一款N沟道高压MOS管。ASE40N50SH的最大脉冲正向电流ISM为160A&#xff0c;零栅极电压漏极电流(IDSS)为1uA&#xff0c;其工作时耐温度范围为-55~150摄氏度。ASE40N…

【架构师】零基础到精通——架构发展

博客昵称&#xff1a;架构师Cool 最喜欢的座右铭&#xff1a;一以贯之的努力&#xff0c;不得懈怠的人生。 作者简介&#xff1a;一名Coder&#xff0c;软件设计师/鸿蒙高级工程师认证&#xff0c;在备战高级架构师/系统分析师&#xff0c;欢迎关注小弟&#xff01; 博主小留言…

PHP - ChatGpt API 接入 ,代码,亲测!(最简单!)

由于最近ChatGpt 大火&#xff0c;但是门槛来说是对于大家最头疼的环节&#xff0c; 我自己也先开发了一个个人小程序&#xff01;大家可以访问使用下&#xff0c; 由此ChatGpt 有一个API 可以仅供大伙对接 让我来说下资质&#xff1a; 1&#xff1a;首先要搞得到一个 ChatGp…

HttpRunner接口自动化测试框架--常见问题

本篇文章主要总结在使用httprunner框架做接口自动化测试过程中遇到的问题 官方的问题总结&#xff1a;Issues httprunner/httprunner GitHub 1.在参数化过程中读取CSV文件&#xff0c;不能读取出整型来。 读取下方文件数据&#xff0c;全部是字符串格式 原因&#xff1a;c…

操作系统(复试准备)

操作系统&#xff08;复试准备&#xff09; 第一章知识点 操作系统概述 操作系统的概念 负责协调软硬件等计算机资源的工作 为上层用户&#xff0c;应用程序提供简单易用的接口 是一种系统软件 操作系统的功能与目标 资源的管理者 处理机管理&#xff0c;存储器管理&#x…

Linux 基础知识:指令与shell

目录一、操作系统二、指令三、shell一、操作系统 什么是操作系统&#xff1f; 单纯的操作系统应该是指操作系统内核。内核的作用就是管理计算机的软硬件资源&#xff0c;让计算机在合适的时候干合适的事情。 但是有一个问题&#xff0c;并不是人人都会直接通过内核来操作计算机…

异常信息记录入库

方案介绍 将异常信息放在日志里面&#xff0c;如果磁盘定期清理&#xff0c;会导致很久之前的日志丢失&#xff0c;因此考虑将日志中的异常信息存在表里&#xff0c;方便后期查看定位问题。 由于项目是基于SpringBoot构架的&#xff0c;所以采用AdviceControllerExceptionHand…

【跟着ChatGPT学深度学习】ChatGPT带我入门深度学习

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

树与二叉树与森林的相关性质

文章目录树的度树的性质二叉树的性质二叉树与森林树的度 树的度指的是树内所有节点的度数的最大值。 节点的度&#xff1a;节点所拥有的子树的数量。简单来说&#xff0c;我们直接数分支即可&#xff0c;例如下图&#xff1a; 在这颗二叉树中&#xff0c;节点2的度为2&#…

【Java】Synchronized锁原理和优化

一、synchronized介绍 synchronized中文意思是同步&#xff0c;也称之为”同步锁“。 synchronized的作用是保证在同一时刻&#xff0c; 被修饰的代码块或方法只会有一个线程执行&#xff0c;以达到保证并发安全的效果。 synchronized是Java中解决并发问题的一种最常用的方法…

K3S系列文章-使用AutoK3s在腾讯云上安装高可用K3S集群

开篇 《K3s 系列文章》《Rancher 系列文章》 方案 在腾讯云上安装 K3S 后续会在这套 K3S 集群上安装 Rancher 方案目标 高可用3 台master 的 k3s 集群 数据备份k3s 数据备份到 腾讯云对象存储 cos 尽量复用公有云的能力Tencent Cloud Controller Manager (❌ 因为腾讯云已…

【LINUX】环境变量以及main函数的参数

文章目录前言环境变量常见环境变量&#xff1a;设置环境变量&#xff1a;和环境变量相关的命令&#xff1a;环境变量的组织方式&#xff1a;获取环境变量环境变量可以被子进程继承环境变量总结main函数的参数前言 大家好久不见&#xff0c;今天分享的内容是环境变量和main函数…