Tối ưu hoá cho tác giả thư viện

Là tác giả thư viện, bạn phải đảm bảo rằng nhà phát triển ứng dụng có thể dễ dàng kết hợp thư viện của bạn vào ứng dụng của họ trong khi vẫn duy trì trải nghiệm chất lượng cao cho người dùng cuối. Bạn nên đảm bảo rằng thư viện của bạn tương thích với việc tối ưu hoá Android mà không cần thiết lập thêm, hoặc ghi lại rằng thư viện có thể không phù hợp để sử dụng trên Android.

Tài liệu này dành cho nhà phát triển các thư viện đã xuất bản, nhưng cũng có thể hữu ích cho nhà phát triển các mô-đun thư viện nội bộ trong một ứng dụng lớn, có cấu trúc mô-đun.

Nếu bạn là nhà phát triển ứng dụng và muốn tìm hiểu về cách tối ưu hoá ứng dụng Android, hãy xem phần Bật tính năng tối ưu hoá ứng dụng. Để tìm hiểu về những thư viện phù hợp để sử dụng, hãy xem phần Chọn thư viện một cách khôn ngoan.

Sử dụng codegen thay vì reflection

Khi có thể, hãy sử dụng tạo mã (codegen) thay vì phản chiếu. Codegen và reflection đều là những phương pháp phổ biến để tránh mã lặp lại khi lập trình, nhưng codegen tương thích hơn với trình tối ưu hoá ứng dụng như R8:

  • Với codegen, mã sẽ được phân tích và sửa đổi trong quá trình xây dựng. Vì không có bất kỳ sửa đổi lớn nào sau thời gian biên dịch, trình tối ưu hoá biết mã nào là cần thiết và mã nào có thể được xoá một cách an toàn.
  • Với tính năng phản chiếu, mã sẽ được phân tích và thao tác trong thời gian chạy. Vì mã chưa thực sự hoàn tất cho đến khi thực thi, nên trình tối ưu hoá không biết mã nào có thể được xoá một cách an toàn. Việc này có thể sẽ xoá mã được dùng linh hoạt thông qua tính năng phản chiếu trong thời gian chạy, khiến ứng dụng gặp sự cố cho người dùng.

Nhiều thư viện hiện đại sử dụng codegen thay vì phản chiếu. Xem KSP để biết điểm truy cập chung, được Room, Dagger2 và nhiều thư viện khác sử dụng.

Khi nào thì nên phản ánh

Nếu phải sử dụng tính năng phản chiếu, bạn chỉ nên phản chiếu vào một trong hai trường hợp sau:

  • Các loại được nhắm đến cụ thể (các trình triển khai hoặc lớp con giao diện cụ thể)
  • Mã sử dụng chú giải thời gian chạy cụ thể

Việc sử dụng tính năng phản chiếu theo cách này sẽ giới hạn chi phí thời gian chạy và cho phép ghi các quy tắc giữ lại người dùng được nhắm đến.

Dạng phản chiếu cụ thể và có mục tiêu này là một mẫu mà bạn có thể thấy trong cả khung Android (ví dụ: khi tăng cường các hoạt động, khung hiển thị và thành phần có thể vẽ) và các thư viện AndroidX (ví dụ: khi tạo WorkManager ListenableWorkers hoặc RoomDatabases). Ngược lại, hoạt động phản chiếu mở của Gson không phù hợp để sử dụng trong các ứng dụng Android.

Các loại quy tắc giữ lại trong thư viện

Có 2 loại quy tắc giữ riêng biệt mà bạn có thể áp dụng trong thư viện:

  • Các quy tắc giữ lại của người dùng phải chỉ định các quy tắc giữ lại bất cứ nội dung nào mà thư viện phản ánh. Nếu một thư viện sử dụng tính năng phản chiếu hoặc JNI để gọi vào mã của thư viện đó hoặc mã do một ứng dụng khách xác định, thì các quy tắc này cần mô tả mã cần được giữ lại. Các thư viện nên đóng gói các quy tắc lưu giữ người dùng. Các quy tắc này sử dụng cùng một định dạng với quy tắc lưu giữ ứng dụng. Các quy tắc này được gói vào các cấu phần phần mềm thư viện (AAR hoặc JAR) và tự động được sử dụng trong quá trình tối ưu hoá ứng dụng Android khi thư viện được dùng. Các quy tắc này được duy trì trong tệp được chỉ định bằng thuộc tính consumerProguardFiles trong tệp build.gradle.kts (hoặc build.gradle). Để tìm hiểu thêm, hãy xem phần Viết quy tắc giữ lại của người tiêu dùng.
  • Các quy tắc giữ lại bản dựng thư viện sẽ được áp dụng khi thư viện của bạn được tạo. Bạn chỉ cần các tham số này nếu quyết định tối ưu hoá một phần thư viện tại thời điểm tạo. Họ phải ngăn việc xoá API công khai của thư viện, nếu không API công khai sẽ không có trong bản phân phối thư viện, tức là nhà phát triển ứng dụng không thể sử dụng thư viện. Các quy tắc này được duy trì trong tệp được chỉ định bằng thuộc tính proguardFiles trong tệp build.gradle.kts (hoặc build.gradle). Để tìm hiểu thêm, hãy xem phần Tối ưu hoá bản dựng thư viện AAR.

Viết các quy tắc giữ lại của người dùng

Ngoài hướng dẫn chung về quy tắc giữ lại, sau đây là các đề xuất dành riêng cho tác giả thư viện.

  • Không sử dụng các quy tắc chung không phù hợp – tránh đặt các chế độ cài đặt chung như -dontobfuscate hoặc -allowaccessmodification trong tệp quy tắc giữ lại người dùng của thư viện, vì các chế độ cài đặt này ảnh hưởng đến tất cả các ứng dụng sử dụng thư viện của bạn.
  • Đừng sử dụng -repackageclasses trong tệp quy tắc giữ lại người dùng của thư viện. Tuy nhiên, để tối ưu hoá bản dựng thư viện, bạn có thể sử dụng -repackageclasses với tên gói nội bộ, chẳng hạn như <your.library.package>.internal, trong tệp quy tắc giữ bản dựng của thư viện. Điều này có thể giúp thư viện của bạn hiệu quả hơn ngay cả khi các ứng dụng sử dụng thư viện đó không được tối ưu hoá, nhưng thường thì bạn không cần làm vậy vì các ứng dụng cũng nên tối ưu hoá. Để biết thêm thông tin chi tiết về cách tối ưu hoá thư viện, hãy xem bài viết Tối ưu hoá cho tác giả thư viện.
  • Khai báo mọi thuộc tính bạn cần để thư viện hoạt động trong các tệp quy tắc lưu giữ của thư viện, ngay cả khi có thể có sự trùng lặp với các thuộc tính được xác định trong proguard-android-optimize.txt.
  • Nếu bạn yêu cầu các thuộc tính sau trong quá trình phân phối thư viện, hãy duy trì các thuộc tính đó trong tệp quy tắc giữ lại bản dựng của thư viện và không trong tệp quy tắc giữ lại người dùng của thư viện:
    • AnnotationDefault
    • EnclosingMethod
    • Exceptions
    • InnerClasses
    • RuntimeInvisibleAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeInvisibleTypeAnnotations
    • RuntimeVisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • Signature
  • Tác giả thư viện nên giữ lại thuộc tính RuntimeVisibleAnnotations trong các quy tắc giữ lại của người dùng nếu chú thích được dùng trong thời gian chạy.
  • Tác giả thư viện không nên sử dụng các lựa chọn chung sau đây trong các quy tắc giữ lại người dùng:
    • -include
    • -basedirectory
    • -injars
    • -outjars
    • -libraryjars
    • -repackageclasses
    • -flattenpackagehierarchy
    • -allowaccessmodification
    • -overloadaggressively
    • -renamesourcefileattribute
    • -ignorewarnings
    • -addconfigurationdebugging
    • -printconfiguration
    • -printmapping
    • -printusage
    • -printseeds
    • -applymapping
    • -obfuscationdictionary
    • -classobfuscationdictionary
    • -packageobfuscationdictionary

Thư viện AAR

Để thêm các quy tắc dành cho người dùng cho một thư viện AAR, hãy dùng lựa chọn consumerProguardFiles trong tập lệnh bản dựng của mô-đun thư viện Android. Để biết thêm thông tin, hãy xem hướng dẫn của chúng tôi về cách tạo mô-đun thư viện.

Kotlin

android {
    defaultConfig {
        consumerProguardFiles("consumer-proguard-rules.pro")
    }
    ...
}

Groovy

android {
    defaultConfig {
        consumerProguardFiles 'consumer-proguard-rules.pro'
    }
    ...
}

Thư viện JAR

Để gói các quy tắc với thư viện Kotlin/Java được phân phối dưới dạng JAR, hãy đặt tệp quy tắc của bạn vào thư mục META-INF/proguard/ của JAR cuối cùng, với bất kỳ tên tệp nào. Ví dụ: nếu mã của bạn ở <libraryroot>/src/main/kotlin, hãy đặt tệp quy tắc người dùng tại <libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro và các quy tắc sẽ được gói ở đúng vị trí trong tệp JAR đầu ra.

Xác minh rằng các quy tắc gói JAR cuối cùng hoạt động chính xác bằng cách kiểm tra xem các quy tắc có nằm trong thư mục META-INF/proguard hay không.

Tối ưu hoá bản dựng thư viện AAR (nâng cao)

Thông thường, bạn không cần tối ưu hoá trực tiếp bản dựng thư viện vì các hoạt động tối ưu hoá có thể thực hiện trong thời gian tạo bản dựng thư viện rất hạn chế. Chỉ trong quá trình tạo ứng dụng, khi một thư viện được đưa vào dưới dạng một phần của ứng dụng, R8 mới có thể biết cách sử dụng tất cả các phương thức của thư viện và những tham số nào được truyền. Là nhà phát triển thư viện, bạn cần suy nghĩ về nhiều giai đoạn tối ưu hoá và duy trì hành vi, cả ở thời gian tạo thư viện và ứng dụng, trước khi tối ưu hoá thư viện đó.

Nếu bạn vẫn muốn tối ưu hoá thư viện tại thời gian tạo bản dựng, thì Trình bổ trợ Android cho Gradle sẽ hỗ trợ việc này.

Kotlin

android {
    buildTypes {
        release {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
        configureEach {
            consumerProguardFiles("consumer-rules.pro")
        }
    }
}

Groovy

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android-optimize.txt'),
                'proguard-rules.pro'
        }
        configureEach {
            consumerProguardFiles "consumer-rules.pro"
        }
    }
}

Xin lưu ý rằng hành vi của proguardFiles khác biệt rất nhiều so với consumerProguardFiles:

  • proguardFiles thường được dùng cùng với getDefaultProguardFile("proguard-android-optimize.txt") trong thời gian tạo bản dựng để xác định phần nào của thư viện cần được giữ lại trong quá trình tạo thư viện. Tối thiểu, đây là API công khai của bạn.
  • consumerProguardFiles được đóng gói vào thư viện để ảnh hưởng đến những hoạt động tối ưu hoá diễn ra sau đó, trong quá trình tạo một ứng dụng sử dụng thư viện của bạn.

Ví dụ: nếu thư viện của bạn sử dụng phương pháp phản chiếu để tạo các lớp nội bộ, thì bạn có thể cần xác định các quy tắc giữ lại trong cả proguardFilesconsumerProguardFiles.

Nếu bạn sử dụng -repackageclasses trong bản dựng thư viện, hãy đóng gói lại các lớp vào một gói con bên trong gói thư viện. Ví dụ: sử dụng -repackageclasses 'com.example.mylibrary.internal' thay vì -repackageclasses 'internal'.

Hỗ trợ nhiều phiên bản R8 (nâng cao)

Bạn có thể điều chỉnh các quy tắc để nhắm đến những phiên bản cụ thể của R8. Điều này cho phép thư viện của bạn hoạt động tối ưu trong các dự án sử dụng phiên bản R8 mới hơn, đồng thời cho phép tiếp tục sử dụng các quy tắc hiện có trong các dự án có phiên bản R8 cũ hơn.

Để chỉ định các quy tắc R8 được nhắm đến, bạn cần đưa các quy tắc đó vào thư mục META-INF/com.android.tools bên trong classes.jar của một AAR hoặc trong thư mục META-INF/com.android.tools của một JAR.

In an AAR library:
    proguard.txt (legacy location, the file name must be "proguard.txt")
    classes.jar
    └── META-INF
        └── com.android.tools (location of targeted R8 rules)
            ├── r8-from-<X>-upto-<Y>/<R8-rule-files>
            └── ... (more directories with the same name format)

In a JAR library:
    META-INF
    ├── proguard/<ProGuard-rule-files> (legacy location)
    └── com.android.tools (location of targeted R8 rules)
        ├── r8-from-<X>-upto-<Y>/<R8-rule-files>
        └── ... (more directories with the same name format)

Trong thư mục META-INF/com.android.tools, có thể có nhiều thư mục con có tên dưới dạng r8-from-<X>-upto-<Y> để cho biết phiên bản R8 mà các quy tắc được viết cho. Mỗi thư mục con có thể có một hoặc nhiều tệp chứa các quy tắc R8, với mọi tên tệp và đuôi tệp.

Xin lưu ý rằng phần -from-<X>-upto-<Y> là không bắt buộc, phiên bản <Y>độc quyền và các dải phiên bản thường liên tục nhưng cũng có thể trùng lặp.

Ví dụ: r8, r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0r8-from-8.2.0 là tên thư mục đại diện cho một nhóm quy tắc R8 được nhắm đến. Mọi phiên bản R8 đều có thể sử dụng các quy tắc trong thư mục r8. R8 có thể sử dụng các quy tắc trong thư mục r8-from-8.0.0-upto-8.2.0 từ phiên bản 8.0.0 cho đến phiên bản 8.2.0 (không bao gồm).

Trình bổ trợ Android cho Gradle sử dụng thông tin đó để chọn tất cả các quy tắc mà phiên bản R8 hiện tại có thể sử dụng. Nếu một thư viện không chỉ định các quy tắc R8 được nhắm đến, thì trình bổ trợ Android cho Gradle sẽ chọn các quy tắc từ các vị trí cũ (proguard.txt cho AAR hoặc META-INF/proguard/<ProGuard-rule-files> cho JAR).