[Android Smaller APK] Part2: Minifying code

Trong phần 1, bạn đã đươc tìm hiểu về cấu trúc APK file, và sử dụng zipalign tool để thu gọn file apk, ở phần 2 này, bài viết tiếp tục giới thiệu cách clean mã code, clean các thư viện sử dụng trong project và giới thiệu công cụ để thực hiện việc này.

Dex code minification

Việc đầu tiên mà bạn cần làm là bật chế độ minifier trong file proguard.

Trong file build.gradle:

android {
    ...
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

Ý nghĩa của chế độ minify là loại bỏ các lớp và thành phần trong lớp mà không được sử dụng. Thêm nữa minify còn đặt lại tên ngắn hơn cho biến có tên dài. Hai tính năng này làm mã nguồn sau khi biên dịch gọn ghẽ, tuy nhiên không bật minify ở chế độ debug, vì tên biến thay đổi so với mã nguồn gốc, không tiện để bạn trace code. Hãy luôn bật minify khi release.

Dòng thứ 2 trong đoạn code trên là dòng config proguard file, file này chứa luật để minify. File proguard mặc định ở địa chỉ Sdk/tools/proguard/proguard-android.txt được tích hợp trong SDK và gồm một số luật cơ bản cho mọi project Android. Tìm hiểu luật ở đây có thể giúp bạn hiểu thêm về cú pháp của proguard.

Ví dụ, từ khoá keep sẽ giữ lại setter và getter trong lớp con của View:

# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
    void set*(***);
    *** get*();
}

Trong quá trình build, công cụ AAPT cũng sinh ra các luật cần thiết để giữa lại mọi activity và component được khai báo trong Manifest, view trong XML.

Hầu hết các thư viện đều cung cấp cấu hình ninifier cho project sử dụng thư viện đó, thình thoảng, họ cung cấp luật ProGuard trên website hoặc github. Trong trường hợp đó, bạn sẽ được yêu cầu sử dụng các luật này cho project chính.

Tuy nhiên, sau khi bật minifying, project có thể sẽ không biên dịch hoặc bị phá vỡ trong lúc runtime gây ra các lỗi ClassNotFoundException cho class được xoá trong quá trình minifying. Để giải quyết vấn đề này bạn cần tạo mọt proguard-rules.pro trong thư mục app/ và đưa ra luật để lấy ra log trong lúc biên dịch (xem trong file log). Bạn phải chắc chắn là đã keep các class và member class được sử dụng trong runtime, đây thường là các lớp được sử dụng thông qua reflection.

Nên kiểm tra lại ProGuard của bạn xem chạy chính xác hay chưa, bằng cách đọc thông tin của classes.dex trong file APK, có thể sử dụng công cự ClassyShark. Bạn nên test lại tất cả các màn hình, follow của ứng dụng, để chắc chắn proguard không xảy ra lỗi crash.

Uploading ProGuard mappings to Play

Phân tích các exception được ném ra bởi ứng dụng đã được ProGuard quả thực khó, vì lúc đó code đã được đổi tên, làm gọn, rất khó để đoán nó là lớp nào đang gây crash. Thông thường bạn copy stacktrace từ Play Developer Console hoặc Crashlytics và dùng công cụ cùng với ProGuard mapping file đã được gennerate để lấy ra tên file gốc. 1*3h-9CmwDuPv-sURh4Bx85w.png

Play Developer Console đã có mục để upload mapping file lên cùng với APK và sẽ hiện ra tên gốc trong stacktraces ở Craseds và ANRs. Lưu ý là file mapping này phải chính xác là file bạn sử dụng để build apk.

ProGuard configuaration for libraries

Nếu bạn viết một thư viện AAR để người khác sử dụng trong project của họ (trước kia là file JAR, AAR là chuẩn thư viện mới trong Android), bạn nên sử dụng consumerProguardFile. Cách này đảm bảo bất cứ ai sử dụng thư viện của bạn cũng sẽ không phải nghĩ về vấn đề thêm luật thủ công khi bật minifier.

android {
    ...
    defaultConfig {
        consumerProguardFiles “proguard-rules.txt”
    }
}

Using granualar dependencies for Google Play Services

0*nmsWPigHxVnyPfHO..png

Khi dùng google play services trong project, hãy nhớ sử dụng granular dependencies. Việc này có nghĩa là bạ sẽ không kéo nguyên cả gói google service về, khi chỉ sử dụng một vài tính năng của google service như map, ads,...

Transitive library dependencies

Bạn có chắc mình đã sử dụng thư viện nhỏ nhất trong project, để chắc chắn, có một công cụ để bạn liệt kê các thành phàn library

 $ ./gradlew app:dependencies
...
compile — Classpath for compiling the main sources.
+ — — com.android.support:appcompat-v7:23.1.1
| \ — — com.android.support:support-v4:23.1.1
| \ — — com.android.support:support-annotations:23.1.1
+ — — com.android.support:cardview-v7:23.1.1
+ — — com.android.support:design:23.1.1
| + — — com.android.support:appcompat-v7:23.1.1 (*)
| + — — com.android.support:recyclerview-v7:23.1.1
| | + — — com.android.support:support-annotations:23.1.1
| | \ — — com.android.support:support-v4:23.1.1 (*)
| \ — — com.android.support:support-v4:23.1.1 (*)
+ — — com.android.support:recyclerview-v7:23.1.1 (*)
\ — — com.android.support.test.espresso:espresso-idling-resource:2.2.1

Trực quan có thể thấy cả cây thư viện, từ đó dễ dàng lựa chọn thư viện nào phù hợp nhất cho bạn.

Conclusion

Tôi tóm tắt lại bài viết một chút:

  • Bật Minify code, sử dụng ProGaurd để loại bỏ class và class member không sử dụng, cũng như đặt tên ngắn gọn cho các thành phần -> APK sẽ có kichs thước gọn hơn
  • Tìm hiểu ProGuard syntax để thiết lập file ProGuard trong project của bạn
  • Sau khi đã build ProGuard rồi thì upload mapping lên Google Play để sau này trace crash bug
  • Lưu ý lấy thư viện nhỏ nhất có thể mà bạn cần, ví dụ với Goolge Play service, sử dụng gradlew để trace cây thư viện
  • Sử dụng tool ClassyShark để phân tích bug của project đã ProGuard

All Rights Reserved