Android上使用grpc的方法教程
前言
最近的一個(gè)項(xiàng)目使用到了grpc實(shí)現(xiàn)跨平臺(tái)的遠(yuǎn)程調(diào)用,在安卓端使用的時(shí)候遇到了一些坑,這里記錄一下。
首先根據(jù)grpc android的官方Demo配置grpc依賴,測試它的hello world工程。
編譯谷歌官方的helloworld工程
添加rotobuf-gradle-plugin插件
首先添加rotobuf-gradle-plugin插件,他是用來從proto文件自動(dòng)生成java代碼的:
//Project的build.gradle中添加rotobuf-gradle-plugin插件
buildscript {
...
dependencies {
...
classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.0"
...
}
...
}
//App的build.gradle中添加下面配置
apply plugin: 'com.google.protobuf'
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.0.0'
}
plugins {
javalite {
artifact = "com.google.protobuf:protoc-gen-javalite:3.0.0"
}
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.0.0' // CURRENT_GRPC_VERSION
}
}
generateProtoTasks {
all().each { task ->
task.plugins {
javalite {}
grpc {
// Options added to --grpc_out
option 'lite'
}
}
}
}
}
添加proto文件并自動(dòng)生成java代碼
在src/main/目錄下創(chuàng)建一個(gè)proto目錄,并將官方的helloworld.proto放到proto目錄下
之后只需要rebuild一下就能看到build/generated/source/proto/目錄下根據(jù)helloworld.proto生成了幾個(gè)Java類

添加安卓端grpc的依賴
//App的build.gradle中添加下面配置
dependencies {
...
compile 'io.grpc:grpc-okhttp:1.1.2'
compile 'io.grpc:grpc-protobuf-lite:1.1.2'
compile 'io.grpc:grpc-stub:1.1.2'
compile 'javax.annotation:javax.annotation-api:1.2'
...
}
configurations.all {
resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.1'
}
我這個(gè)時(shí)候報(bào)了這個(gè)錯(cuò)誤
Warning:Conflict with dependency ‘com.google.code.findbugs:jsr305'. Resolved versions for app (3.0.0) and test app (2.0.1) differ. See http://g.co/androidstudio/app-test-app-conflict for details.
這是因?yàn)?code>com.google.code.findbugs:jsr305的版本不一致導(dǎo)致的
可以在App的build.gradle的android標(biāo)簽中配置一下解決
android {
...
configurations.all {
resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.1'
}
...
}
編寫demo代碼
public class MainActivity extends AppCompatActivity {
private static final String TAG = "GrpcDemo";
private static final int PROT = 55055;
private static final String NAME = "linjw";
private static final String HOST = "localhost";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startServer(PROT);
startClient(HOST, PROT, NAME);
}
private void startServer(int port){
try {
Server server = ServerBuilder.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){
new GrpcTask(host, port, name).execute();
}
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("hello "+ request.getName())
.build();
}
}
private class GrpcTask extends AsyncTask<Void, Void, String> {
private String mHost;
private String mName;
private int mPort;
private ManagedChannel mChannel;
public GrpcTask(String host, int port, String name) {
this.mHost = host;
this.mName = name;
this.mPort = port;
}
@Override
protected void onPreExecute() {
}
@Override
protected String doInBackground(Void... nothing) {
try {
mChannel = ManagedChannelBuilder.forAddress(mHost, mPort)
.usePlaintext(true)
.build();
GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(mChannel);
HelloRequest message = HelloRequest.newBuilder().setName(mName).build();
HelloReply reply = stub.sayHello(message);
return reply.getMessage();
} catch (Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
pw.flush();
return "Failed... : " + System.lineSeparator() + sw;
}
}
@Override
protected void onPostExecute(String result) {
try {
mChannel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Log.d(TAG, result);
}
}
}
這段代碼運(yùn)行會(huì)崩潰:
Caused by: io.grpc.ManagedChannelProvider$ProviderNotFoundException: No functional server found. Try adding a dependency on the grpc-netty artifact
猜測google使用netty替代了okhttp,嘗試換成grpc-netty的依賴:
dependencies {
...
compile 'io.grpc:grpc-netty:1.1.2'
compile 'io.grpc:grpc-protobuf-lite:1.1.2'
compile 'io.grpc:grpc-stub:1.1.2'
compile 'javax.annotation:javax.annotation-api:1.2'
...
}
這么編譯會(huì)報(bào)錯(cuò)
com.android.build.api.transform.TransformException: com.android.builder.packaging.DuplicateFileException: Duplicate files copied in APK META-INF/INDEX.LIST
需要加上下面的配置解決
android {
...
packagingOptions {
pickFirst 'META-INF/INDEX.LIST'
pickFirst 'META-INF/LICENSE'
pickFirst 'META-INF/io.netty.versions.properties'
}
...
}
當(dāng)然,還需要加上INTERNET權(quán)限,要不然運(yùn)行的時(shí)候還是會(huì)崩潰。
最終就能看的下面的打印,這樣安卓grpc的helloworld就成功了。
03-03 00:04:20.000 6137-6137/linjw.com.grpcdemo D/GrpcDemo: hello linjw
使用com.google.protobuf.Any
Any可以攜帶任意類型的數(shù)據(jù),用法相當(dāng)于c語言的void指針。在項(xiàng)目中是很常用的,但是谷歌在javalite的版本不支持Any。
如果在proto文件中使用了Any的話生成java代碼就會(huì)有報(bào)錯(cuò),例如將helloworld的proto文件改成下面的樣子:
// Copyright 2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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;
import "google/protobuf/any.proto";
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (google.protobuf.Any) 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;
}
報(bào)錯(cuò)如下
google/protobuf/any.proto: File not found. helloworld.proto: Import “google/protobuf/any.proto” was not found or had errors. helloworld.proto:44:17: “google.protobuf.Any” is not defined.
使用grpc-jave代替grpc-javalite
但是現(xiàn)在做的這個(gè)項(xiàng)目的linux端實(shí)現(xiàn)已經(jīng)用了Any,要改的話需要耗費(fèi)比較大的精力。幸好嘗試了下,發(fā)現(xiàn)安卓上也能跑支持Any的grpc-java。
首先我們要使用grpc-protobuf依賴替換grpc-protobuf-lite依賴
dependencies {
...
compile 'io.grpc:grpc-netty:1.1.2'
compile 'io.grpc:grpc-protobuf:1.1.2'
compile 'io.grpc:grpc-stub:1.1.2'
compile 'javax.annotation:javax.annotation-api:1.2'
...
}
接著修改protobuf-gradle-plugin配置使得自動(dòng)生成java的代碼而不是javalite的代碼
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.0.0'
}
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.0.0' // CURRENT_GRPC_VERSION
}
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {}
}
task.plugins {
grpc {}
}
}
}
}
對應(yīng)的修改helloworld的代碼就能運(yùn)行了
public class MainActivity extends AppCompatActivity {
private static final String TAG = "GrpcDemo";
private static final int PROT = 55055;
private static final String NAME = "linjw";
private static final String HOST = "localhost";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startServer(PROT);
startClient(HOST, PROT, NAME);
}
private void startServer(int port){
try {
Server server = ServerBuilder.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){
new GrpcTask(host, port, name).execute();
}
private class GreeterImpl extends GreeterGrpc.GreeterImplBase {
public void sayHello(Any request, StreamObserver<HelloReply> responseObserver) {
try {
responseObserver.onNext(sayHello(request.unpack(HelloRequest.class)));
responseObserver.onCompleted();
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
private HelloReply sayHello(HelloRequest request) {
return HelloReply.newBuilder()
.setMessage("hello "+ request.getName())
.build();
}
}
private class GrpcTask extends AsyncTask<Void, Void, String> {
private String mHost;
private String mName;
private int mPort;
private ManagedChannel mChannel;
public GrpcTask(String host, int port, String name) {
this.mHost = host;
this.mName = name;
this.mPort = port;
}
@Override
protected void onPreExecute() {
}
@Override
protected String doInBackground(Void... nothing) {
try {
mChannel = ManagedChannelBuilder.forAddress(mHost, mPort)
.usePlaintext(true)
.build();
GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(mChannel);
HelloRequest message = HelloRequest.newBuilder().setName(mName).build();
HelloReply reply = stub.sayHello(Any.pack(message));
return reply.getMessage();
} catch (Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
pw.flush();
return "Failed... : " + System.lineSeparator() + sw;
}
}
@Override
protected void onPostExecute(String result) {
try {
mChannel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Log.d(TAG, result);
}
}
}
完整的demo代碼可以點(diǎn)這里在我的github中查看(也可以通過本地下載)
Android方法數(shù)不能超過65535的問題
最后使用grpc,方法數(shù)會(huì)超過65535,可以使用com.android.support:multidex去解決
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,如有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
詳解Android的Splash啟動(dòng)圖的兩種動(dòng)態(tài)切換方式
本篇文章主要介紹了詳解Android的Splash啟動(dòng)圖的兩種動(dòng)態(tài)切換方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
Android編程實(shí)現(xiàn)獲取系統(tǒng)內(nèi)存、CPU使用率及狀態(tài)欄高度的方法示例
這篇文章主要介紹了Android編程實(shí)現(xiàn)獲取系統(tǒng)內(nèi)存、CPU使用率及狀態(tài)欄高度的方法,涉及Android基于自定義類實(shí)現(xiàn)針對系統(tǒng)硬件信息的相關(guān)獲取操作技巧,需要的朋友可以參考下2017-08-08
Android自定義View實(shí)現(xiàn)漸變色進(jìn)度條
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)漸變色進(jìn)度條,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05
android實(shí)現(xiàn)關(guān)閉或開啟移動(dòng)網(wǎng)絡(luò)數(shù)據(jù)
本篇文章是對android實(shí)現(xiàn)關(guān)閉或開啟移動(dòng)網(wǎng)絡(luò)數(shù)據(jù)進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06
Android中LeakCanary檢測內(nèi)存泄漏的方法
本篇文章主要介紹了Android中LeakCanary檢測內(nèi)存泄漏的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09
Android中Xposed框架篇---修改系統(tǒng)位置信息實(shí)現(xiàn)自身隱藏功能實(shí)例
本篇文章介紹了Android中Xposed框架的使用,詳細(xì)的介紹了修改系統(tǒng)位置信息實(shí)現(xiàn)自身隱藏功能實(shí)例,有需要的朋友可以了解一下。2016-11-11
Android 自定義View實(shí)現(xiàn)計(jì)時(shí)文字詳解
這篇文章主要為大家介紹了Android 自定義View實(shí)現(xiàn)計(jì)時(shí)文字詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
Flutter開發(fā)之設(shè)置應(yīng)用名稱及圖標(biāo)的教程
這篇文章主要介紹了Flutter設(shè)置應(yīng)用名稱及圖標(biāo)的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
Android自定義View實(shí)現(xiàn)拖拽效果
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)拖拽效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11

