Android上使用grpc的方法教程

前言

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

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

编译谷歌官方的helloworld工程

添加rotobuf-gradle-plugin插件

首先添加rotobuf-gradle-plugin插件,他是用来从proto文件自动生成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文件并自动生成java代码

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

之后只需要rebuild一下就能看到build/generated/source/proto/目录下根据helloworld.proto生成了几个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'
 }

我这个时候报了这个错误

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标签中配置一下解决

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);
 }
 }
}

这段代码运行会崩溃:

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'
 ...
}

这么编译会报错

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'
 }
 ...
}

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

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

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文件改成下面的样子:

// 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;
}

报错如下

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

但是现在做的这个项目的linux端实现已经用了Any,要改的话需要耗费比较大的精力。幸好尝试了下,发现安卓上也能跑支持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配置使得自动生成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 {}
   }
  }
 }
}

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

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代码可以点这里在我的github中查看(也可以通过本地下载)

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

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

总结

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

(0)

相关推荐

  • Android用Scroller实现一个可向上滑动的底部导航栏

    静静等了5分钟竟不知道如何写我这第一篇文章.每次都想好好的学习学习,有时间多敲敲代码,写几篇自己的文章.今天终于开始实行了,还是有点小激动的.哈哈! 好了废话就不多收了.我今天想实现的一个功能就是一个可以上滑底部菜单栏.为什么我会想搞这么个东西呢, 还是源于一年前,我们app 有这么个需求,当时百度也好谷歌也好,都没有找到想要的效果,其实很简单的一个效果.但是当时我也是真的太菜了,所有有关自定义的控件真的是不会,看别人的代码还好,真要是自己写,一点头绪都没有.因为我试着写了,真的不行啊.当时觉得

  • 详解Android ViewCompat的作用

    详解Android ViewCompat的作用 ViewCompat类主要是用来提供兼容性的, 比如我最近看的比较的多的canScrollVertically方法, 在ViewCompat里面针对几个版本有不同的实现, 原理上还是根据版本判断, 有时甚至还要判断传入参数的类型. 但是要注意的是, ViewCompat仅仅让你调用不崩溃, 并不保证你调用的结果在不同版本的机器上一致. 关于如何优雅的组织代码, ViewCompat类的结构非常适合我们参考. ViewCompat里面定义了一个接口,

  • Android动画之小球拟合动画实例

    Android动画之小球拟合动画实例 实现效果: 动画组成: 1.通过三阶贝塞尔曲线来拟合圆,拟合系数的由来,以及怎么选控制点. 2.利用画布canvas.translate,以及scale,rotate的方法,来渐变绘制的过程. 3.熟悉拟合过程. 4.不熟悉的话,先绘制辅助点的移动路线,对理解两个圆的分裂的拟合过程有好处. package com.example.administrator.animationworkdemo.views; import android.animation.V

  • Android 解决WebView无法上传文件的问题

    Android 解决WebView无法上传文件的问题 Android原生的WebView并不支持上传文件,需要我们自己实现相应的方法.于是我把工作中的相关代码记录下来.下次直接拿来用就行了.一点一滴都是经验. 1.需要定义三个变量 private ValueCallback<Uri[]> uploadMessageAboveL; private final static int FILE_CHOOSER_RESULT_CODE = 10000; private ValueCallback<

  • Android绘制验证码的实例代码

    在前面仿华为加载动画.仿网易音乐听歌识曲-麦克风动画中,我们通过绘图的基础知识完成了简单的绘制.在本例中,我们将绘制常见的验证码. 一.效果图 二.知识点与思路分析 通过上面的效果图观察,我们可以看到里面有绘制的随机线条,随机绘制的验证码. 绘制线条,直线或曲线 绘制文本,生成的验证码文本的绘制 绘制圆点. 三.代码编写 /** * Created by Iflytek_dsw on 2017/7/3. */ public class IdentifyCodeUtil { private sta

  • Android RecyclerView显示Item布局不一致解决办法

    RecyclerView显示Item布局不一致 在自定义RecyclerAdapter的时候,在重写onCreateViewHolder方法是使用了 @Override public H onCreateViewHolder(ViewGroup parent, int viewType) { View view=View.inflate(context,layoutId,null); return view; } 进行生成布局,结果发现生成的布局没有LayoutParams.以前自定义View的

  • Android文件下载功能实现代码

    本文实例为大家分享了Android文件下载功能的具体代码,供大家参考,具体内容如下 1.普通单线程下载文件: 直接使用URLConnection.openStream()打开网络输入流,然后将流写入到文件中! public static void downLoad(String path,Context context)throws Exception { URL url = new URL(path); InputStream is = url.openStream(); //截取最后的文件名

  • Android中应用前后台切换监听的实现详解

    前言 最近在工作中遇到了这么一个需求:如何实现 Android 应用前后台切换的监听?下面来一起看看详细的介绍: iOS 内边是可以实现的,AppDelegate 给了一个回调监听: @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func applicationWillResignActive(_ application: UIApplication) { // Sent when the a

  • Android上使用grpc的方法教程

    前言 最近的一个项目使用到了grpc实现跨平台的远程调用,在安卓端使用的时候遇到了一些坑,这里记录一下. 首先根据grpc android的官方Demo配置grpc依赖,测试它的hello world工程. 编译谷歌官方的helloworld工程 添加rotobuf-gradle-plugin插件 首先添加rotobuf-gradle-plugin插件,他是用来从proto文件自动生成java代码的: //Project的build.gradle中添加rotobuf-gradle-plugin插

  • Android上实现easyconfig(airkiss)方法

    刚买回来一个智能音箱和博联,需要给音箱和博联配置联网,音箱需要先打开蓝牙,然后在手机app中填写wifi的ssid和密码,通过蓝牙发送到音箱,音箱收到后连接到wifi. 博联就比较奇怪,进入联网模式以后,手机app上填写wifi的ssid和密码后,直接点配置按钮后,博联就连上了.要知道手机并没有与这个设备建立连接,ssid和密码不知道怎么就莫名其妙的被发送过去了. 仔细想了一下,应该是通过wifi信号发送的,wifi本质上是一种射频信号,手机可以发送wifi信号,博联上也有wifi芯片,理论上可

  • node.js中实现kindEditor图片上传功能的方法教程

    前言 最近由于工作需要使用在线编辑器,找了几个对比了下KindEditor还是不错的,国产的但文档还是不全,还好能参考官方插件,kindEditor官网上中提供了ASP,ASP.NET,JSP相关的整合应用 可以参照官方文档实现nodejs的kindEditor上传功能:http://kindeditor.net/docs/upload.html 在线下载kindEditor编辑器:http://www.jb51.net/codes/36131.html 实现方法: 1.在客户端js中定义upl

  • Android基于Http协议实现文件上传功能的方法

    本文实例讲述了Android基于Http协议实现文件上传功能的方法.分享给大家供大家参考,具体如下: 注意一般使用Http协议上传的文件都比较小,一般是小于2M 这里示例是上传一个小的MP3文件 1.主Activity:MainActivity.java public class MainActivity extends Activity { private static final String TAG = "MainActivity"; private EditText timel

  • Android实现让图片在屏幕上任意移动的方法(拖拽功能)

    本文实例讲述了Android实现让图片在屏幕上任意移动的方法.分享给大家供大家参考,具体如下: public class DragExampleActivity extends Activity { Bitmap mBitmap; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInst

  • android studio编译jar包或者aar包的方法教程详解

    1. 在原有工程目录右键-> new ->Module->: 2. 选择library: 3. 一路next,最后finish: 4. 在新生成的lib module下的build.gradle中添加如下代码: task makeJar(type: Copy) { //删除存在的 delete 'build/outputs/aar/plugin-release.aar' delete 'libs/' //设置拷贝的文件来源 from('build/outputs/aar/') ////新

  • Android 创建依赖库的方法(保姆级教程)

    新建工程,新建Module 新建一个工程,之后按下图中的操作方式,创建一个 Module 创建 Android Library 选中 Android Library,之后点击Next! 输入你要创建module名字 输入你要创建module名字,如果有,但请不要删除前面的两个冒号(可能会因AndroidStudio版本原因显示界面不一样)!之后点击Finish! 在Module中新建测试类 如图,找到以下文件目录,在Module中新建测试类 上传github 之后要上传Github,导入JitP

  • Android上使用jspf插件框架的方法

    本文实例讲述了Android上使用jspf插件框架的方法.分享给大家供大家参考.具体如下: jspf (Java Simple Plugin Framework) 是一个插件框架,集成了很多 IoC 框架的概念在里面. package de.jspfdemo; import net.xeoh.plugins.base.PluginManager; import net.xeoh.plugins.base.impl.PluginManagerFactory; import net.xeoh.plu

  • Android编程使用HTTP协议与TCP协议实现上传文件的方法

    本文实例讲述了Android编程使用HTTP协议与TCP协议实现上传文件的方法.分享给大家供大家参考,具体如下: Android上传文件有两种方式,第一种是基于Http协议的HttpURLConnection,第二种是基于TCP协议的Socket. 这两种方式的区别是使用HttpURLConnection上传时内部有缓存机制,如果上传较大文件会导致内存溢出.如果用TCP协议Socket方式上传就会解决这种弊端. HTTP协议HttpURLConnection 1. 通过URL封装路径打开一个Ht

  • Android实现在屏幕上移动图片的方法

    本文实例讲述了Android实现在屏幕上移动图片的方法.分享给大家供大家参考.具体实现方法如下: 1. Speed.java文件: package net.obviam.droidz.model.components; public class Speed { public static final int DIRECTION_RIGHT = 1; public static final int DIRECTION_LEFT = -1; public static final int DIREC

随机推荐