Skip to content
Ider

沉淀我所学习,累积我所见闻,分享我所体验

Primary Navigation Menu
Menu
  • Home
  • About Ider
    • Who Ider?
    • Why Ider?
    • How Ider?
    • Where Ider?
    • What Ider?

Software Engineering(软件工程)

2021-01-14
14 January
On January 14, 2021
In Design Patterns(设计模式), English Posts(英文写作), Knowledge Base(心得笔库), Mobile Development(移动开发), Software Engineering(软件工程)

ProtoBuf 2.0 method count optimization for android development

The Protocol Buffer library was not initially built for Android, it just turned out to have a Java version to be used in Android, but it’s not optimized for Android apps, for example: it doesn’t consider the method count limit in Android.

So, in this article, I’d like to share some work I found specifically on Protocol Buffer Version 2 that can reduce the method count. If your app is also heavily relying on Protocol Buffer, I hope these approaches are useful for you too.

General Approaches

1. Use Protocol Buffer Java Lite Runtime

Just as the name indicates, the dependency library is much smaller than the regular Protocol Buffer Java runtime, the generated code is also much slimmer. However, the APIs are compatible between those two versions, so the call sites would not be affected when changing the library.

2. Don’t use <Message>OrBuilder interface

For each Protocol Buffer message definition in .proto file, the Protocol Buffer compiler generates an interface named <Message>OrBuilder (<Message> is the name defined in .proto file). This interface would be implemented by the concrete <Message> class and the corresponding Builder.

It might be attractive to use it as a variable type thereby you can depend on abstractions to not concrete classes. But calling methods on Java interface would make Dex take count of those methods.

In reality, every place can directly use either <Message> or Builder, then the optimization tool (like R8, ProGuard) can safely remove the methods declared on the interface.

Special Tricks

Protocol Buffer Java Lite is very good, but if I open the magic box of generated Java classes, I notice it’s still not optimal for Android. There are a couple places I could modify to make it more effective for Android applications.

Instead of copying the Java files and making the change, which is error prone when engineers update the .proto file, I created a script to automate the job. Just add the execution of this script at the end of the Protocol Buffer gradle task, so it works just as a complement of Protocol Buffer code generation process.

[codesyntax lang=”groovy” lines=”normal”]

protobuf {
  protoc {
    artifact = 'com.google.protobuf:protoc:3.7.0'
  }
  plugins {
    javalite {
      artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
    }
  }
  generateProtoTasks {
    all().each { task ->
      task.builtins {
        remove java
      }
      task.plugins {
        javalite { }
      }

      // `getOutputDir()` can only be read during configuration, 
      // so it’s out of action block
      def outputDir = task.getOutputDir(task.plugins[0])
      task.doLast {
          exec {
              commandLine file('protobuf-optimizer.py')
              args outputDir
          }
      }    
    }
  }
}

[/codesyntax]

In the script, I have made the following modification on generated java code.

1. Change the modifier of Builder constructor from private to package

Simply running the sample addressbook.proto from Protocol Buffer tutorial side, you would get a class like the following snippet:

[codesyntax lang=”java” lines=”normal”]

public static final class Person {
    private Person() {}

    protected final Object dynamicMethod(
            com.google.protobuf.GeneratedMessageLite.MethodToInvoke method,
            Object arg0, Object arg1) {
        switch (method) {
            case NEW_BUILDER: {
                return new Builder();
            }
        }
    }

    public static final class Builder {
        private Builder() {}
    }

}

[/codesyntax]

When analyzing the APK file, we could would find some synthetic classes and methods:

This is because the outer class is accessing the private constructor of the inner class. The generated synthetic class would contribute an extra constructor to the Dex file. Therefore, by simply removing the private from Builder(), you can remove such classes and methods.
Read More →

2019-08-29
29 August
On August 29, 2019
In Data Structures(数据结构), Knowledge Base(心得笔库), Mobile Development(移动开发), Software Engineering(软件工程)

Gradle android-library 的各种坑

软件开发随着项目越来越大,我们会开始拆封出多个子项目(sub-project)或者库(library)来更好地独立维护。Android开发也不例外,我们也会需要创建各种库。如果使用Android Studio和Gradle,他们为我们提供了便捷的方式来创建libray。但是他们也带来了不少的维护问题,本文就来讲讲android-library里的坑来帮助大家避开。

android-library v.s. java-library

对于普通Android手机项目,我们有创建两种不同的Library可以选用: android-library 和 java-library

它们的gradle文件内容分别如下

[codesyntax lang=”groovy” lines=”normal” highlight_lines=”1″]

apply plugin: 'com.android.library'

android {
    // ...
}

dependencies {
    // ...
}

[/codesyntax]

[codesyntax lang=”groovy” lines=”normal” highlight_lines=”1″]

apply plugin: 'java-library'

sourceCompatibility = "7"
targetCompatibility = "7"

dependencies {
    // ...
}

[/codesyntax]

两种库在定义上的根本差别其实就是不同gradle plugin的使用。另一个小的差别就是android-library需要有一个 AndroidManifest.xml 文件,但它可以简单到只有一行内容(其中package的值通常是文件夹的路径)

[codesyntax lang=”xml” lines=”normal”]

<manifest package="com.ider.android.library" />

[/codesyntax]

使用上他们有一些差异,这些差异也是我们决定使用哪种Library的标准:

  • android-library 可以使用 Android API(比如Activity, Service等等),java-library 则不能使用
  • android-library 可以编译android资源文件(resources files),java-library 则无视这样内容
  • android-library 编译出 arr 文件,java-library编译出 jar 文件(本质上他们都是 zip 文件)
  • android-library 的配置可以包含各种编译类型(build variants)来控制,java-library没有那个复杂
  • android-library 可以依赖于另一个android-library 或者 java-library, java-library只能依赖于java-library而不能依赖于android-library

总体而言 android-library 基本是 java-library 的一个超集, 但 android-library 比 java-libray 使用起来要更加复杂一些。从上边的对比来看,对于Android的项目,因为我们不确定什么时候会需要调用 Android 的API,或者定义一下 Android 的资源内容。再者最后一条的约束也决定了创建的 java-library 要保证在整个依赖树(dependency tree)上要在接近叶子节点的位置。所以不如优先选择创建android-library,以免掉入之后改 build.gradle 的坑中。
Read More →

2017-08-28
28 August
On August 28, 2017
In Knowledge Base(心得笔库), Language Tips(语言初试), Software Engineering(软件工程)

Android单元测试有关资源

做Android开发,实在有太多的开源库和框架供开发者选用,总是让人无所适从到底哪个才是真正合适的。但是在自动化单元测试这一块,库的选择反而少了许多。可能是这些库实在太优秀了,也可能自动化测试的受重视程度相对较低所以没有太多人投入研发之中。

但是,自动化测试特别是单元测试(Unit Test)对于提高代码质量有很大的帮助。本文就来介绍一些Android中常用的单元测试库。

Junit

Junit是最主流的Java自动化则是框架,目前发展到Junit4。通过各种Java注解(Annotation)来标识测试的基本信息。
每个测试经历几个流程:设置,执行,验证。上手简单,运行快速,结果直观。API设计也很简洁,很容易扩展。

Mockito

单元测试最重要是减少测试对象的依赖,专注在该测试对象上。Mockito框架能很好的帮助我们完成这一目标,它在运行时将部分类在二进制层面替换掉,如此测试就不用依赖在实际代码上。
另外,单元测试中的断言验证(assertion)一般都是基于对象的状态(state),而Mockito提供了基于行为(behavior)的验证方式。

Robolectric

在Android的代码则很大程度依赖于Android的系统库,比如Context,View等等。其他几个库都是基于Java开发出来的,在单元测试中无法调用到Android系统的代码。Roboletric的出现就很好的解决了这个问题,它为测试提供了Android的系统代码, 并且通过配置可以指定运行的API等级。

AssertJ

JUnit自带assert方法来验证测试结果,这些方法使用了Hamcrest的函数式API。AssertJ则更具面向对象设计,并且是流式接口(fluent interface),让代码书写更加流畅,在IDE中也有更好的补全功能。
另外AssertJ扩展性也非常强,对于各种常用的Java库都有相应的扩展来更好的适应单元测试,比如Guava。即使是Android也有开源扩展库。
Read More →

2016-12-31
31 December
On December 31, 2016
In Design Patterns(设计模式), Reading Notes(阅而后知), Software Engineering(软件工程)

面向对象开发原则:S.O.L.I.D.

solid-oop_wall-skills

一开始学编程的时候,都是在学习程序语言,觉得学好了这门语言就可以写好程序;渐渐地发现算法和数据结构更加的重要,因为语言只是细胞,要依附这些行程肉体才能产生作用;但到了实际生产应用的时候,又会察觉整个好像缺少灵魂,常常只能像僵尸般往一个方向前进,缺少驱动力来动态调整,这力量就来自各种设计模式和开发原则。

经历了越多开发中的痛楚,也越来越发觉“S.O.L.I.D.”开发原则只用几句话所概括出来的真谛和内涵。它们对于软件开发是如此有意义,当然具体的实现依然还是依赖于那些基于需求形成的数据结构和算法,只是利用这些开发原则作为灵魂,让程序走得更远更自由。

 

Single Responsibility Principle(SRP)

A class should have one, and only one, reason to change.

Open Closed Principle(OCP)

You should be able to extend a classes behavior, without modifying it.

Liskov Substitution Principle(LSP)

Derived classes must be substitutable for their base classes.

Interface Segregation Principle(ISP)

Make fine grained interfaces that are client specific.

Dependency Inversion Principle(DIP)

Depend on abstractions, not on concretions.

 

References:
  1. ArticleS.UncleBob.PrinciplesOfOod
  2. SOLID (object-oriented design) – Wikipedia
  3. S is for the Single Responsibility Principle
  4. O is for the Open/Closed Principle
  5. L is for the Liskov Substitution Principle
  6. I is for the Interface Segregation Principle
  7. D is for the Dependency Inversion Principle

(后5个是基于Android开发对这些原则做的阐述)

2016-05-03
03 May
On May 3, 2016
In Design Patterns(设计模式), Knowledge Base(心得笔库), Language Tips(语言初试), Software Engineering(软件工程)

Java的static关键字

已经看过了万能的final关键字在Java中的不同用法和效果,其实static关键字的的用处也很广泛。它的用法更多涉及到面向对象设计(Object-Oriented Design)的思想,有时更加让人迷惑。许多是否应该使用static关键字的决定也常常基于工作经验中形成的约束和规范。本文就来帮助大家解锁static的正确使用姿势,写出更加清晰的代码。

为了更好的了解Java程序,就需要了解代码被编译后的样子,所以下边会涉及到javac和javap这些指令的使用,不用担心都是比较简单直接的用法。也需要借助一些反编译(decompile)工具来查看.class文件,这里用到的是JD-GUI。

  • 静态变量 Static Variable
  • 静态方法 Static Method
  • 静态块 Static Block
  • 静态嵌套类 Static Nested Class
  • 静态引入 Static Import

Static Variable

静态变量(Static Variable)在Java中只有静态成员变量,不同于C++、PHP等,它不允许在方法内定义局部静态变量。静态成员变量也称为类变量,相对的是对象成员变量。这些变量是属于定义的类,而非某个对象实例,但是该类的所有实例都可分享该变量。

用个简单的例子会比较直观一些:
[codesyntax lang=”java” lines=”normal”]

package com.iderzheng.statickeyword;

public class StaticField {
    public static int sField;
    public int mField;

    public void print() {
        System.out.println(
                String.format("%s{sField: %d, mField: %d}", this, sField, mField)
        );
    }

    public static void main(String[] args) {
        StaticField one = new StaticField();
        StaticField two = new StaticField();

        one.sField = 7;
        one.mField = 7;

        two.sField = 21;
        two.mField = 21;

        one.print();
        two.print();

        StaticField.sField = 49;
        // Error: non-static variable mField cannot be referenced from a static context
        // StaticField.mField = 49;
        one.print();
        two.print();
    }
}

[/codesyntax]

编译运行这段代码得到如下结果:

com.iderzheng.statickeyword.StaticField@34469729{sField: 21, mField: 7}
com.iderzheng.statickeyword.StaticField@6d172f8f{sField: 21, mField: 21}
com.iderzheng.statickeyword.StaticField@34469729{sField: 49, mField: 7}
com.iderzheng.statickeyword.StaticField@6d172f8f{sField: 49, mField: 21}

很清楚的看到变量two对sField的修改影响了one中的sField值。而mField在两个对象中是独立互补影响的。也看到可以用类的名字来引用静态变量,这是提倡调用方法,不应该直接用对象变量来引用静态变量,这样做不明确调用的是静态变量,很容易带来副作用。

Static Final Variable

一般在程序中不常定义静态变量,因为静态变量的可被访问和使用的地方比局部变量和对象变量要多,维护起来更加困难。更多时候是跟final结合起来定义常量,对于无法修改的只读变量,就不用担心维护问题了。

对象变量也可以用final修饰定义为常量,只不过每个对象的常量只属于对象自己,而且每个对象都需要分配内存给这些常量。而属于类的静态变量则只需要一份内存甚至无需存储变量的值。所以如果对象变量在每个对象中都是一致的,那么就可以考虑转为静态常量。转成静态常量也能方便地用类名直接进行访问,省去创建多余的中间对象。

Java编译器对于常量会做优化,在引用它们的地方会将它们的值直接嵌入,这样就不需要内存来长期存储这些值,更不要从内存地址中寻找,所以效率要高很多。

还是做一个简单的例子:

[codesyntax lang=”java” lines=”normal”]

package com.iderzheng.statickeyword;

public class StaticFinalFields {

    static final int CONSTANT_INT = 7;
    static final String CONSTANT_STRING = "iderzheng.com";
    static final int[] CONSTANT_ARRAY = { 7, 21, 31 };

    final String name;
    final int constantInt = 21;
    final boolean constantBoolean;

    {
        constantBoolean = true;
    }

    public StaticFinalFields(String name) {
        this.name = name;
    }

    public static void main(String[] args) {
        StaticFinalFields finalFields = new StaticFinalFields("Ider");
        System.out.println(finalFields.name);
        System.out.println(finalFields.constantInt);
        System.out.println(finalFields.constantBoolean);

        System.out.println(CONSTANT_INT);
        System.out.println(CONSTANT_STRING);
        System.out.println(CONSTANT_ARRAY);
    }
}

[/codesyntax]

对于上边的代码,通过javac进行编译后得到StaticFinalFields.class,再讲其通过JD-UI打开,会看到编译后代码的
static-final-inline

[codesyntax lang=”java” lines=”normal”]

package com.iderzheng.statickeyword;

import java.io.PrintStream;

public class StaticFinalFields
{
    static final int CONSTANT_INT = 7;
    static final String CONSTANT_STRING = "iderzheng.com";
    static final int[] CONSTANT_ARRAY = { 7, 21, 31 };
    final String name;
    final int constantInt = 21;

    final boolean constantBoolean = true;

    public StaticFinalFields(String paramString)
    {
        this.name = paramString;
    }

    public static void main(String[] paramArrayOfString) {
        StaticFinalFields localStaticFinalFields = new StaticFinalFields("Ider");
        System.out.println(localStaticFinalFields.name);
        localStaticFinalFields.getClass(); System.out.println(21);
        System.out.println(localStaticFinalFields.constantBoolean);

        System.out.println(7);
        System.out.println("iderzheng.com");
        System.out.println(CONSTANT_ARRAY);
    }
}

[/codesyntax]

从编译后的代码可以看出对于原始类型的变量,在用final进行修饰并内嵌初始化后,使用这些变量的地方就直接用这些值进行了替换。虽然也有非static修饰的对象常量被智能优化后直接内嵌,但是为了代码的维护性,依然应该显示地申明成static。

至于对象变量,它们并非真正意义上的常量,对象的创建后的地址也总会不同,而且对象的内容依然可以被修改,所以这些变量加上static final也无法用其值在使用的地方进行替换。

另外还看到通过block进行初始化的对象变量虽然也是常量,但是因为放入了block,编译器觉得它的初始化比较复杂,所以就没有将其引用直接替换。

直接内嵌常量的可以提高运行的效率,但是也有一些弊端:比如项目A中使用了项目B中的静态常量,成功编译后的A会直接使用那些常量;要是B的常量值被人修改了之后只编译了B而没有重新编译A,那A就不会得到新的值。好在现代的编译器都很智能,可以发现这些依赖关系并正确编译。
Read More →

Facebook
Twitter
LinkedIn
RSS
ZhiHu

Recent Posts

  • 三年居家工作感受
  • Pixel Watch智能手表和Pixel 5, 6 Pro 及 7 Pro手机
  • 我拥有过的无线耳机
  • 毕业工作一个月,我差点被开除
  • 我拥有过的移动硬盘
  • ProtoBuf 2.0 method count optimization for android development
  • 面过100场行为面试后

Categories

  • Algorithm Analysis(算法分析)
  • Article Collection(聚宝收藏)
  • Data Structures(数据结构)
  • Design Patterns(设计模式)
  • English Posts(英文写作)
  • Front Interface(界面构想)
  • IT Products(数码产品)
  • Knowledge Base(心得笔库)
  • Language Tips(语言初试)
  • Mathematical Theory(数学理论)
  • Mobile Development(移动开发)
  • Programming Life(程序人生)
  • Reading Notes(阅而后知)
  • Software Engineering(软件工程)
  • Special Tricks(奇技妙招)
  • Tangential Speech(漫话杂谈)

Tags

Aero Android API Bash Binary Search Bitwise Operation Book C/C++ Career Chrome Conference CSS Debug Device DOM Extension Framework Game Gradle Hearthstone HTML Initialization Intellij Interview iOS Java JavaScript jQuery Keyword Language Issues Mac Microsoft Mobile Modifier Objective-C PHP Principle Reference Regular Expression Static String Tools Tutorial UI XML

Blogroll

  • Ahmed's Blog
  • Gert Lombard's Blog
  • Gordon Luk
  • Jack & Allison
  • 开发部落

Archives

Designed using Chromatic. Powered by WordPress.