首頁 > 軟體

Android使用GRPC進行通訊過程解析

2023-02-28 18:01:57

引言

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版本一共有兩種方式,第一種就是在projectstructure中進行修改。

第二種方法就是直接在組態檔中進行修改

你需要哪個版本的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);
    }
}

最後在虛擬機器器上執行程式,依次點選四個按鈕,如果得到了下圖的結果,則表示程式跑通啦,好耶ヽ(✿゚▽゚)ノ

到此這篇關於Android使用GRPC進行通訊過程解析的文章就介紹到這了,更多相關Android使用GRPC內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com