一、原因:android项目中要用到底层底层给定的protobuf接口,但如何解析或者说如何使用不懂,故咨询一波。

二、已查到的相关资料:

1、定义:Protocol Buffers(也称protobuf)是google旗下一款独立于开发语言,独立于平台的可扩展的结构化数据序列机制。简单来说,就是像json、xml这种用来传输数据的一种数据交互协议。不过,相比与json、xml,protobuf更加轻便与高效。

更多详细资料可查看官网:

2、入门级别博客:

发现的几篇后面应该去跟着尝试做一下的博客:

http://www.gaohaiyan.com/2134.html

https://www.jianshu.com/p/df200894f5da

https://gqdy365.iteye.com/blog/2163076

3、我按照访问量最高,看了很多博客差不多的进行操作,决定据此进行配置,https://www.jianshu.com/p/fcf7f8cc0d8d

试着配置完,发现如下问题,多次修改设置,编译仍然无法通过,该bug仍然无法消退

org.gradle.api.tasks.TaskValidationException: A problem was found with the configuration of task ':app:generateDebugProto'.
	at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.report(ValidatingTaskExecuter.java:66)
	at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:53)
	at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:119)
	at org.gradle.api.internal.tasks.execution.ResolvePreviousStateExecuter.execute(ResolvePreviousStateExecuter.java:43)
	at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:93)
	at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:45)
	at org.gradle.api.internal.tasks.execution.ResolveTaskArtifactStateTaskExecuter.execute(ResolveTaskArtifactStateTaskExecuter.java:94)
	at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:56)
	at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:55)
	at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:67)
	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:49)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:315)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:305)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:175)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:101)
	at org.gradle.internal.operations.DelegatingBuildOperationExecutor.call(DelegatingBuildOperationExecutor.java:36)
	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:49)
	at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:43)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:355)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:343)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:336)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:322)
	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker$1.execute(DefaultPlanExecutor.java:134)
	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker$1.execute(DefaultPlanExecutor.java:129)
	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:202)
	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:193)
	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:129)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
	at java.lang.Thread.run(Thread.java:745)
Caused by: org.gradle.api.InvalidUserDataException: Directory 'C:\Users\zhouxueli\AndroidStudioProjects\Protobuf\app\build\extracted-include-protos\main' specified for property '$3' does not exist.
	at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.report(ValidatingTaskExecuter.java:63)
	... 34 more

在build下 的如下目录extracted-include-protos 一直无法生成main文件夹,只有debug文件夹

昨天看了n篇文章之后,发现问题出在配置文件中,因为我初次时参考的博客链接如下

https://www.jianshu.com/p/0da79242babc

报错一直提示$3有问题,

//将会在 "$projectDir/src/generated"这个目录中自动生成对应的java文件
    generatedFilesBaseDir = "$projectDir/src/generated"

同时能看到:

WARNING: Folder C:\Users\zhouxueli\AndroidStudioProjects\Protobuf\app\src\generated\debugUnitTest\java
INFO: 3rd-party Gradle plug-ins may be the cause

当昨晚再次打开前几天搁置下来的这个问题时,再次对照配置文件,想起这一项,当我把该项删除,编译便通过了

对于如上配置项,可能是博主自己系统中在哪里配置了什么东西。

若有类似参考如上文档的小伙伴,可参考本实践尝试修改解决。

最终我的简单测试步骤如下:

1、在android studio 中安装protobuf support 插件:File——>Settings——>Plugins——搜索protobuf support 安装即可;

2、创建一个普通的android项目,在负责本项目整个配置的build.gradle 文件中进行配置:

// 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.1'
        //该项配置和gradle 要兼容,貌似gradle 3以下,要配置0.8.2以下,具体后续补充
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.10'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
allprojects {
    repositories {
        google()
        jcenter()
    }
}

3、在android项目的app目录下的build.gradle目录中进行配置
 

apply plugin: 'com.android.application'
//注意这里
apply plugin: 'com.google.protobuf'
android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.example.zhouxueli.protobuf"
        minSdkVersion 19
        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'
        }
    }
//如下和该测试紧密相关
    sourceSets {
        main {
            proto {
                //main目录新建proto目录
                srcDir 'src/main/protobuf'
                include '**/*.proto'
            }
            java {
                srcDir 'src/main/java'
            }
        }
    }
}
//构建task 和protobuf紧密相关
protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.1.0'
    }
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                remove java
            }
            task.builtins {
                java {}
                // Add cpp output without any option.
                // DO NOT omit the braces if you want this builtin to be added.
               // cpp {} //java 项目的话,该项可删除
            }
        }
    }
    //如下为参考博客进行配置时踩到的一个小坑,源于自己对配置项含义不理解
    //generatedFilesBaseDir = "$projectDir/src/generated"
}
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    //这个才是和protobuf配置相关的 start ~
    implementation 'com.google.protobuf:protobuf-java:3.1.0'
    implementation 'com.google.protobuf:protoc:3.1.0'
    //这个才是和protobuf配置相关的 end ~
    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'
}

完成到此,android studio中的配置环境的问题就完成了,下面开始演示后续实例

4、在app ——> src——》main目录下,和java同层次的目录,创建一个protobuf 文件夹,该文件夹的命名和上方配置有关

//如下和该测试紧密相关
    sourceSets {
        main {
            proto {
                //main目录新建proto目录
                srcDir 'src/main/protobuf'
                include '**/*.proto'
            }
            java {
                srcDir 'src/main/java'
            }
        }
    }

这里的srcDir目录即为刚才为何要创建如此目录的原因。注protobuf的命名并非非要用protobuf,只要这里两个地方配置一致即可

然后即可在protobuf 目录中创建自己的实例 .proto文件

如我当前创建了一个Adrressbook.proto文件

package tutorial;

option java_package = "com.example.zhouxueli.test";
option java_outer_classname = "AddressBookProto";

message Person {
    required string name = 1;
    required int32 id = 2;
    optional string email = 3;

    enum PhoneType {
        MOBILE = 0;
        HOME = 1;
        WORK = 2;
    }

    message PhoneNumber {
        required string number = 1;
        optional PhoneType type = 2 [default = HOME];
    }

    repeated PhoneNumber phone = 4;
}

message AddressBook {
    repeated Person person = 1;
}

简单来说,如上是一个类似通讯簿的简单实例,其中可以存储多个人信息,每个人有自己的姓名 邮箱  person中的id貌似是这个结构必须有的。然后事实上,一个人可能拥有多个电话,所以结构如上,你也可以做一个简单的,只有单层的而非嵌套的message出来。

5、我在activity调用的地方,代码如下:

package com.example.zhouxueli.protobuf;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

import com.example.zhouxueli.test.AddressBookProto;
import com.google.protobuf.InvalidProtocolBufferException;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.ceshitext);
        AddressBookProto.Person person = AddressBookProto.Person.newBuilder()
                .setId(1)
                .setName("测试用文言")
                .setEmail("123")
                .build();
        AddressBookProto.AddressBook book = AddressBookProto.AddressBook.newBuilder()
                .addPerson(person)
                .build();
        //如下是为了演示我们工程项目中实际使用时的方案,意思是底层给了我们一个符合一定结构的,如上信息
        //的字符串,通过给定的字符串转成相对应的对象结构,再获取值。
//        AddressBookProto.Person person1;
//        try {
//            person1 =AddressBookProto.Person.parseFrom(person.toByteString());
//            tv.setText(person1.getName());
//            tv.setText((book.getPerson(0).getEmail()));
//
//        } catch (InvalidProtocolBufferException e) {
//            e.printStackTrace();
//        }
        tv.setText(book.getPerson(0).getEmail());
    }
}

布局文件就是放了一个textview。

5、执行编译运行即可,默认会在app——》build——》generated——》source——》proto——》debug——》java (如果配置有cpp,大致意味是会产生c或c++相关的相应结构体)。此时可在java下看到和上方自定义的protobuf下面文件名+proto.java 类文件,如这里的Adressbook对应产生了AddressBookProto.java。因为文件很长,此处不再粘贴。

6、运行可看到,界面上打印出了一个我们在上面赋的值123