美文网首页
Android客户端实现gRPC通信

Android客户端实现gRPC通信

作者: koinzhang | 来源:发表于2019-01-16 10:16 被阅读0次

    1月23日修改:使用asyncTask来获取线上数据。


    博主目前在一家做互联网金融数据的公司实习,公司要求利用gRPC实现安卓客户端和服务器进行传输数据,博主自学后,总结了一些经验。


    博主自学后,发现网上的例子都是官网给出helloworld的例子,其中的一些东西太简单,介绍的不是很详细,所以在这里补充一点自己的理解。
    文末有本文参考,对GRPC和proto语法不了解的朋友可以先看看。
    在安卓端实现gRPC通信,首先 需要添加GRPC依赖:

    1. 在 build.gradle(Project)中添加依赖
    // Top-level build file where you can add configuration options common to all sub-projects/modules.
    
    buildscript {
        
        repositories {
            google()
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.2.0'
            //添加gRPC的依赖
            classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.7"
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }
    }
    
    
    allprojects {
        repositories {
            google()
            jcenter()
        }
    }
    
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    

    在build.gradle(Module)中添加依赖,这里特别强调,配置implementation时,okhttpproto-litestub三个版本要相一致。关于配置gRPC最新版本,可以在mvnrepository查询

    apply plugin: 'com.android.application'
    //配置gRPC
    apply plugin: 'com.google.protobuf'
    
    android {
        compileSdkVersion 28
        defaultConfig {
            applicationId "com.example.zyk97.infotest"
            minSdkVersion 26
            targetSdkVersion 28
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    
    }
    //配置gRPC
    protobuf {
        protoc {
            artifact = 'com.google.protobuf:protoc:3.4.0'
        }
        plugins {
            javalite {
                artifact = "com.google.protobuf:protoc-gen-javalite:3.0.0"
            }
            grpc {
                artifact = 'io.grpc:protoc-gen-grpc-java:1.7.0' // CURRENT_GRPC_VERSION
            }
        }
        generateProtoTasks {
            all().each { task ->
                task.plugins {
                    javalite {}
                    grpc {
                        // Options added to --grpc_out
                        option 'lite'
                    }
                }
            }
        }
        configurations.all {
            resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.2'
        }
    
    }
    
    dependencies {
        implementation fileTree(include: ['*.jar'], dir: 'libs')
        implementation 'com.android.support:appcompat-v7:28.0.0'
        implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    
        //配置gRPC
        implementation 'io.grpc:grpc-okhttp:1.15.1'
        implementation 'io.grpc:grpc-protobuf-lite:1.15.1'
        implementation 'io.grpc:grpc-stub:1.15.1'
        compileOnly 'javax.annotation:javax.annotation-api:1.2'
    
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'com.android.support.test:runner:1.0.2'
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    }
    
    1. 配置好gRPC环境后,还需要再项目的projectname\app\src\main下新建proto文件夹,存放proto文件,添加自己定义的proto文件后,就可以Build Project了。Build后会自动生成两个java文件,这就是编程时要用到的两个类。
    2. 配置好以上环境后,可以开始实现客户端了,以下是博主的实现代码
      在新闻详情页调用上面接口中的函数获取线上数据
    package com.example.zyk97.infotest.Activity;
    
    import android.content.Intent;
    import android.os.AsyncTask;
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.support.v7.widget.LinearLayoutCompat;
    import android.text.Html;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    
    import com.dib.pann.DataServiceGrpc;
    import com.dib.pann.Dataservice;
    import com.example.zyk97.infotest.R;
    import com.example.zyk97.infotest.Utils.JustifyTextView;
    
    import java.util.concurrent.TimeUnit;
    
    import butterknife.BindView;
    import butterknife.ButterKnife;
    import io.grpc.ManagedChannel;
    import io.grpc.ManagedChannelBuilder;
    
    public class NewsDetail extends AppCompatActivity {
    
    
        @BindView(R.id.back)
        Button back;
        @BindView(R.id.search)
        Button search;
        @BindView(R.id.title)
        TextView title;
        @BindView(R.id.time)
        TextView time;
        @BindView(R.id.origin)
        TextView origin;
        @BindView(R.id.link)
        Button link;
        @BindView(R.id.content)
        JustifyTextView content;
        @BindView(R.id.newsdetail_liner)
        LinearLayoutCompat newsdetailLiner;
        private String[] messageGot = new String[5];
    
        //向服务器端请求信息所用到的数据
        private static int start = 2;
        private static int limit = 3;
        private static String title_ = "海尔";
        private static String content_ = "冰箱";
        private static final String HOST = "152.136.35.187";
        private static final int PORT = 50052;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.newsdetail);
    
            ButterKnife.bind(this);
    
            grpc();
            //动作事件处理
            motionEvent();
        }
    
        private void grpc() {
            String[] messageGot = new String[5];
            ManagedChannel mChannel;
            mChannel = ManagedChannelBuilder.forAddress(HOST, PORT)
                    .usePlaintext()
                    .build();
    
            DataServiceGrpc.DataServiceBlockingStub stub = DataServiceGrpc.newBlockingStub(mChannel);
            Dataservice.MsgRequest message = Dataservice.MsgRequest.newBuilder()
                    .setStart(start)
                    .setLimit(limit)
                    .setTitle(title_)
                    .setContent(content_)
                    .build();
            Dataservice.MsgResponse response = stub.getNews(message);
            messageGot[0] = response.getRep(0).getTitle();
            messageGot[1] = response.getRep(0).getPublishDate();
            messageGot[2] = response.getRep(0).getSource();
            messageGot[3] = response.getRep(0).getUrl();
            messageGot[4] = response.getRep(0).getContent();
            try {
                //关闭channal
                mChannel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            title.setText(messageGot[0]);
            time.setText(messageGot[1]);
            origin.setText(messageGot[2]);
            content.setText(Html.fromHtml(messageGot[4], 0));
        }
    
        private void initView() {
            title = findViewById(R.id.title);
            time = findViewById(R.id.time);
            origin = findViewById(R.id.origin);
            link = findViewById(R.id.link);
            content = findViewById(R.id.content);
            back = findViewById(R.id.back);
            search = findViewById(R.id.search);
    
        }
    
        class myAsyncTask extends AsyncTask<Void, Void, String[]> {
    
            @Override
            protected String[] doInBackground(Void... voids) {
    
                String[] messageGot = new String[5];
                ManagedChannel mChannel;
                mChannel = ManagedChannelBuilder.forAddress(HOST, PORT)
                        .usePlaintext()
                        .build();
    
                DataServiceGrpc.DataServiceBlockingStub stub = DataServiceGrpc.newBlockingStub(mChannel);
                Dataservice.MsgRequest message = Dataservice.MsgRequest.newBuilder()
                        .setStart(start)
                        .setLimit(limit)
                        .setTitle(title_)
                        .setContent(content_)
                        .build();
                Dataservice.MsgResponse response = stub.getNews(message);
                messageGot[0] = response.getRep(0).getTitle();
                messageGot[1] = response.getRep(0).getPublishDate();
                messageGot[2] = response.getRep(0).getSource();
                messageGot[3] = response.getRep(0).getUrl();
                messageGot[4] = response.getRep(0).getContent();
                try {
                    //关闭channal
                    mChannel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return messageGot;
            }
    
            @Override
            protected void onPostExecute(String strings[]) {
                messageGot = strings;
                //加载数据
                title.setText(messageGot[0]);
                time.setText(messageGot[1]);
                origin.setText(messageGot[2]);
                content.setText(Html.fromHtml(messageGot[4], 0));
            }
        }
    
        private void motionEvent() {
    
            back.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    finish();
                }
            });
            search.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent_search = new Intent(NewsDetail.this, SearchPage.class);
                    startActivity(intent_search);
                }
            });
            link.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Bundle bundle = new Bundle();
                    bundle.putString("uri", messageGot[3]);
                    Intent intent = new Intent(NewsDetail.this, OriginWebview.class);
                    intent.putExtra("bundle", bundle);
                    startActivity(intent);
                }
            });
        }
    
    }
    

    本例中用到的protoful文件:

    syntax = "proto3";
    
    option java_package = "com.dib.pann";
    /*已注释掉classname。
    原本后端发我的proto文件没注释掉classname,我运行之后报了上面的错误,
    注释掉classname之后可以运行。
    */
    //option java_outer_classname = "DataService";
     
    service DataService {
    /*
    新闻接口,提供近期新闻标题或内容关键词查询,调用参数示例
    (start=0,limit=5,name='盼年',title='茅台',content='酒')
    */
     rpc GetNews (MsgRequest) returns (MsgResponse){}
    
    }
     
    
    //新闻请求
    message MsgRequest {
      string name = 1;      //不用送
      int32 start = 2;       //开始条数 第一次为0
      int32 limit = 3;       //返回条数
      string title = 4;          //标题关键词
      string content = 5;       //内容关键词
    }
    
    //新闻响应
    message MsgResponse {
        // 文章
        message report{
            string publishDate=1;                     //发布时间
            string title = 2;                   //标题
            string url = 3;                     //链接 
            string source = 4;                  //来源
            string content = 5 ;                //内容
        }
        
      repeated report rep = 2;
    }
    

    以上就是博主所有的代码,有兴趣的朋友欢迎和博主探讨。
    如有错误,欢迎指正。


    关于proto语法,博主遇到的几个坑:

    1. proto文件最好加上包名,例如:option java_package = "xxx",不加包名的后果是生成的java文件会在默认包下面,而java目前是不允许引用默认包下的类的,无法import,就没法使用生成的类文件,不过能力强的大佬可以选择使用java反射机制,调用默认包下的java文件中的函数方法。

    2. proto文件中的定义的类名不要和service、message名相同,例如option java_outer_classname = "Dataservice"和service Dataservice{}中斜体字不能相同,否则会报错;message 的命名也不能相同:

    Cause: protoc: stdout: . stderr: C:\Users\zyk97\AndroidStudioProjects\InfoTest\app\build\extracted-include-protos\main: warning: directory does not exist.
    C:\Users\zyk97\AndroidStudioProjects\InfoTest\app\src\debug\proto: warning: directory does not exist.
    --javalite_out: dataservice.proto: dataservice.proto: Cannot generate Java output because the file's outer class name, "DataService", matches the name of one of the types declared inside it.  Please either rename the type or use the java_outer_classname option to specify a different outer class name for the .proto file.
    

    本文样例完整代码InfoTest
    本文参考:

    相关文章

      网友评论

          本文标题:Android客户端实现gRPC通信

          本文链接:https://www.haomeiwen.com/subject/hlypdqtx.html