查看: 449|回复: 0

[Android教程] Android上使用grpc的方法教程

发表于 2017-11-28 08:00:01

前言

最近的一个项目使用到了grpc实现跨平台的远程调用,在安卓端使用的时候遇到了一些坑,这里记录一下。

首先根据grpc android的官方Demo配置grpc依赖,测试它的hello world工程。

编译谷歌官方的helloworld工程

添加rotobuf-gradle-plugin插件

首先添加rotobuf-gradle-plugin插件,他是用来从proto文件自动生成java代码的:

  1. //Project的build.gradle中添加rotobuf-gradle-plugin插件
  2. buildscript {
  3. ...
  4. dependencies {
  5. ...
  6. classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.0"
  7. ...
  8. }
  9. ...
  10. }
复制代码
  1. //App的build.gradle中添加下面配置
  2. apply plugin: 'com.google.protobuf'
  3. protobuf {
  4. protoc {
  5. artifact = 'com.google.protobuf:protoc:3.0.0'
  6. }
  7. plugins {
  8. javalite {
  9. artifact = "com.google.protobuf:protoc-gen-javalite:3.0.0"
  10. }
  11. grpc {
  12. artifact = 'io.grpc:protoc-gen-grpc-java:1.0.0' // CURRENT_GRPC_VERSION
  13. }
  14. }
  15. generateProtoTasks {
  16. all().each { task ->
  17. task.plugins {
  18. javalite {}
  19. grpc {
  20. // Options added to --grpc_out
  21. option 'lite'
  22. }
  23. }
  24. }
  25. }
  26. }
复制代码

添加proto文件并自动生成java代码

在src/main/目录下创建一个proto目录,并将官方的helloworld.proto放到proto目录下

之后只需要rebuild一下就能看到build/generated/source/proto/目录下根据helloworld.proto生成了几个Java类


添加安卓端grpc的依赖

  1. //App的build.gradle中添加下面配置
  2. dependencies {
  3. ...
  4. compile 'io.grpc:grpc-okhttp:1.1.2'
  5. compile 'io.grpc:grpc-protobuf-lite:1.1.2'
  6. compile 'io.grpc:grpc-stub:1.1.2'
  7. compile 'javax.annotation:javax.annotation-api:1.2'
  8. ...
  9. }
复制代码
  1. configurations.all {
  2. resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.1'
  3. }
复制代码

我这个时候报了这个错误

  1. 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.
复制代码

这是因为com.google.code.findbugs:jsr305的版本不一致导致的

可以在App的build.gradle的android标签中配置一下解决

  1. android {
  2. ...
  3. configurations.all {
  4. resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.1'
  5. }
  6. ...
  7. }
复制代码

编写demo代码

  1. public class MainActivity extends AppCompatActivity {
  2. private static final String TAG = "GrpcDemo";
  3. private static final int PROT = 55055;
  4. private static final String NAME = "linjw";
  5. private static final String HOST = "localhost";
  6. @Override
  7. protected void onCreate(Bundle savedInstanceState) {
  8. super.onCreate(savedInstanceState);
  9. setContentView(R.layout.activity_main);
  10. startServer(PROT);
  11. startClient(HOST, PROT, NAME);
  12. }
  13. private void startServer(int port){
  14. try {
  15. Server server = ServerBuilder.forPort(port)
  16. .addService(new GreeterImpl())
  17. .build()
  18. .start();
  19. } catch (IOException e) {
  20. e.printStackTrace();
  21. Log.d(TAG, e.getMessage());
  22. }
  23. }
  24. private void startClient(String host, int port, String name){
  25. new GrpcTask(host, port, name).execute();
  26. }
  27. private class GreeterImpl extends GreeterGrpc.GreeterImplBase {
  28. public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
  29. responseObserver.onNext(sayHello(request));
  30. responseObserver.onCompleted();
  31. }
  32. private HelloReply sayHello(HelloRequest request) {
  33. return HelloReply.newBuilder()
  34. .setMessage("hello "+ request.getName())
  35. .build();
  36. }
  37. }
  38. private class GrpcTask extends AsyncTask<Void, Void, String> {
  39. private String mHost;
  40. private String mName;
  41. private int mPort;
  42. private ManagedChannel mChannel;
  43. public GrpcTask(String host, int port, String name) {
  44. this.mHost = host;
  45. this.mName = name;
  46. this.mPort = port;
  47. }
  48. @Override
  49. protected void onPreExecute() {
  50. }
  51. @Override
  52. protected String doInBackground(Void... nothing) {
  53. try {
  54. mChannel = ManagedChannelBuilder.forAddress(mHost, mPort)
  55. .usePlaintext(true)
  56. .build();
  57. GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(mChannel);
  58. HelloRequest message = HelloRequest.newBuilder().setName(mName).build();
  59. HelloReply reply = stub.sayHello(message);
  60. return reply.getMessage();
  61. } catch (Exception e) {
  62. StringWriter sw = new StringWriter();
  63. PrintWriter pw = new PrintWriter(sw);
  64. e.printStackTrace(pw);
  65. pw.flush();
  66. return "Failed... : " + System.lineSeparator() + sw;
  67. }
  68. }
  69. @Override
  70. protected void onPostExecute(String result) {
  71. try {
  72. mChannel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
  73. } catch (InterruptedException e) {
  74. Thread.currentThread().interrupt();
  75. }
  76. Log.d(TAG, result);
  77. }
  78. }
  79. }
复制代码

这段代码运行会崩溃:

  1. Caused by: io.grpc.ManagedChannelProvider$ProviderNotFoundException: No functional server found. Try adding a dependency on the grpc-netty artifact
复制代码

猜测google使用netty替代了okhttp,尝试换成grpc-netty的依赖:

  1. dependencies {
  2. ...
  3. compile 'io.grpc:grpc-netty:1.1.2'
  4. compile 'io.grpc:grpc-protobuf-lite:1.1.2'
  5. compile 'io.grpc:grpc-stub:1.1.2'
  6. compile 'javax.annotation:javax.annotation-api:1.2'
  7. ...
  8. }
复制代码

这么编译会报错

  1. com.android.build.api.transform.TransformException: com.android.builder.packaging.DuplicateFileException: Duplicate files copied in APK META-INF/INDEX.LIST
复制代码

需要加上下面的配置解决

  1. android {
  2. ...
  3. packagingOptions {
  4. pickFirst 'META-INF/INDEX.LIST'
  5. pickFirst 'META-INF/LICENSE'
  6. pickFirst 'META-INF/io.netty.versions.properties'
  7. }
  8. ...
  9. }
复制代码

当然,还需要加上INTERNET权限,要不然运行的时候还是会崩溃。

最终就能看的下面的打印,这样安卓grpc的helloworld就成功了。

  1. 03-03 00:04:20.000 6137-6137/linjw.com.grpcdemo D/GrpcDemo: hello linjw
复制代码

使用com.google.protobuf.Any

Any可以携带任意类型的数据,用法相当于c语言的void指针。在项目中是很常用的,但是谷歌在javalite的版本不支持Any。

如果在proto文件中使用了Any的话生成java代码就会有报错,例如将helloworld的proto文件改成下面的样子:

  1. // Copyright 2015, Google Inc.
  2. // All rights reserved.
  3. //
  4. // Redistribution and use in source and binary forms, with or without
  5. // modification, are permitted provided that the following conditions are
  6. // met:
  7. //
  8. // * Redistributions of source code must retain the above copyright
  9. // notice, this list of conditions and the following disclaimer.
  10. // * Redistributions in binary form must reproduce the above
  11. // copyright notice, this list of conditions and the following disclaimer
  12. // in the documentation and/or other materials provided with the
  13. // distribution.
  14. // * Neither the name of Google Inc. nor the names of its
  15. // contributors may be used to endorse or promote products derived from
  16. // this software without specific prior written permission.
  17. //
  18. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. syntax = "proto3";
  30. option java_multiple_files = true;
  31. option java_package = "io.grpc.examples.helloworld";
  32. option java_outer_classname = "HelloWorldProto";
  33. option objc_class_prefix = "HLW";
  34. package helloworld;
  35. import "google/protobuf/any.proto";
  36. // The greeting service definition.
  37. service Greeter {
  38. // Sends a greeting
  39. rpc SayHello (google.protobuf.Any) returns (HelloReply) {}
  40. }
  41. // The request message containing the user's name.
  42. message HelloRequest {
  43. string name = 1;
  44. }
  45. // The response message containing the greetings
  46. message HelloReply {
  47. string message = 1;
  48. }
复制代码

报错如下

  1. google/protobuf/any.proto: File not found.
  2. helloworld.proto: Import “google/protobuf/any.proto” was not found or had errors.
  3. helloworld.proto:44:17: “google.protobuf.Any” is not defined.
复制代码

使用grpc-jave代替grpc-javalite

但是现在做的这个项目的linux端实现已经用了Any,要改的话需要耗费比较大的精力。幸好尝试了下,发现安卓上也能跑支持Any的grpc-java。

首先我们要使用grpc-protobuf依赖替换grpc-protobuf-lite依赖

  1. dependencies {
  2. ...
  3. compile 'io.grpc:grpc-netty:1.1.2'
  4. compile 'io.grpc:grpc-protobuf:1.1.2'
  5. compile 'io.grpc:grpc-stub:1.1.2'
  6. compile 'javax.annotation:javax.annotation-api:1.2'
  7. ...
  8. }
复制代码

接着修改protobuf-gradle-plugin配置使得自动生成java的代码而不是javalite的代码

  1. protobuf {
  2. protoc {
  3. artifact = 'com.google.protobuf:protoc:3.0.0'
  4. }
  5. plugins {
  6. grpc {
  7. artifact = 'io.grpc:protoc-gen-grpc-java:1.0.0' // CURRENT_GRPC_VERSION
  8. }
  9. }
  10. generateProtoTasks {
  11. all().each { task ->
  12. task.builtins {
  13. java {}
  14. }
  15. task.plugins {
  16. grpc {}
  17. }
  18. }
  19. }
  20. }
复制代码

对应的修改helloworld的代码就能运行了

  1. public class MainActivity extends AppCompatActivity {
  2. private static final String TAG = "GrpcDemo";
  3. private static final int PROT = 55055;
  4. private static final String NAME = "linjw";
  5. private static final String HOST = "localhost";
  6. @Override
  7. protected void onCreate(Bundle savedInstanceState) {
  8. super.onCreate(savedInstanceState);
  9. setContentView(R.layout.activity_main);
  10. startServer(PROT);
  11. startClient(HOST, PROT, NAME);
  12. }
  13. private void startServer(int port){
  14. try {
  15. Server server = ServerBuilder.forPort(port)
  16. .addService(new GreeterImpl())
  17. .build()
  18. .start();
  19. } catch (IOException e) {
  20. e.printStackTrace();
  21. Log.d(TAG, e.getMessage());
  22. }
  23. }
  24. private void startClient(String host, int port, String name){
  25. new GrpcTask(host, port, name).execute();
  26. }
  27. private class GreeterImpl extends GreeterGrpc.GreeterImplBase {
  28. public void sayHello(Any request, StreamObserver<HelloReply> responseObserver) {
  29. try {
  30. responseObserver.onNext(sayHello(request.unpack(HelloRequest.class)));
  31. responseObserver.onCompleted();
  32. } catch (InvalidProtocolBufferException e) {
  33. e.printStackTrace();
  34. }
  35. }
  36. private HelloReply sayHello(HelloRequest request) {
  37. return HelloReply.newBuilder()
  38. .setMessage("hello "+ request.getName())
  39. .build();
  40. }
  41. }
  42. private class GrpcTask extends AsyncTask<Void, Void, String> {
  43. private String mHost;
  44. private String mName;
  45. private int mPort;
  46. private ManagedChannel mChannel;
  47. public GrpcTask(String host, int port, String name) {
  48. this.mHost = host;
  49. this.mName = name;
  50. this.mPort = port;
  51. }
  52. @Override
  53. protected void onPreExecute() {
  54. }
  55. @Override
  56. protected String doInBackground(Void... nothing) {
  57. try {
  58. mChannel = ManagedChannelBuilder.forAddress(mHost, mPort)
  59. .usePlaintext(true)
  60. .build();
  61. GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(mChannel);
  62. HelloRequest message = HelloRequest.newBuilder().setName(mName).build();
  63. HelloReply reply = stub.sayHello(Any.pack(message));
  64. return reply.getMessage();
  65. } catch (Exception e) {
  66. StringWriter sw = new StringWriter();
  67. PrintWriter pw = new PrintWriter(sw);
  68. e.printStackTrace(pw);
  69. pw.flush();
  70. return "Failed... : " + System.lineSeparator() + sw;
  71. }
  72. }
  73. @Override
  74. protected void onPostExecute(String result) {
  75. try {
  76. mChannel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
  77. } catch (InterruptedException e) {
  78. Thread.currentThread().interrupt();
  79. }
  80. Log.d(TAG, result);
  81. }
  82. }
  83. }
复制代码

完整的demo代码可以点这里在我的github中查看(也可以通过本地下载)

Android方法数不能超过65535的问题

最后使用grpc,方法数会超过65535,可以使用com.android.support:multidex去解决

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如有疑问大家可以留言交流,谢谢大家对程序员之家的支持。



回复

使用道具 举报

关闭

站长推荐上一条 /1 下一条