R8 অপ্টিমাইজারের পূর্ণ সম্ভাবনা সক্ষম করুন

R8 দুটি মোড প্রদান করে, সামঞ্জস্যতা মোড এবং পূর্ণ মোড। পূর্ণ মোড আপনাকে শক্তিশালী অপ্টিমাইজেশন দেয় যা আপনার অ্যাপের কর্মক্ষমতা উন্নত করে।

এই নির্দেশিকাটি অ্যান্ড্রয়েড ডেভেলপারদের জন্য যারা R8 এর সবচেয়ে শক্তিশালী অপ্টিমাইজেশন ব্যবহার করতে চান। এটি সামঞ্জস্যতা এবং পূর্ণ মোডের মধ্যে মূল পার্থক্যগুলি অন্বেষণ করে এবং আপনার প্রকল্পটি নিরাপদে স্থানান্তরিত করার জন্য এবং সাধারণ রানটাইম ক্র্যাশ এড়াতে প্রয়োজনীয় স্পষ্ট কনফিগারেশনগুলি প্রদান করে।

পূর্ণ মোড সক্ষম করুন

পূর্ণ মোড সক্ষম করতে, আপনার gradle.properties ফাইল থেকে নিম্নলিখিত লাইনটি সরিয়ে ফেলুন:

android.enableR8.fullMode=false // Remove this line to enable full mode

বৈশিষ্ট্যের সাথে সম্পর্কিত ক্লাসগুলি ধরে রাখুন

অ্যাট্রিবিউট হল কম্পাইল করা ক্লাস ফাইলের মধ্যে সংরক্ষিত মেটাডেটা যা এক্সিকিউটেবল কোডের অংশ নয়। তবে, নির্দিষ্ট ধরণের প্রতিফলনের জন্য এগুলি প্রয়োজন হতে পারে। সাধারণ উদাহরণগুলির মধ্যে রয়েছে Signature (যা টাইপ ইরেজারের পরে জেনেরিক ধরণের তথ্য সংরক্ষণ করে), InnerClasses এবং EnclosingMethod (ক্লাস স্ট্রাকচারের প্রতিফলনের জন্য) এবং রানটাইম-দৃশ্যমান অ্যানোটেশন।

নিচের কোডটি দেখায় যে বাইটকোডের একটি ক্ষেত্রের জন্য একটি Signature বৈশিষ্ট্য কেমন দেখাচ্ছে। একটি ক্ষেত্রের জন্য:

List<User> users;

কম্পাইল করা ক্লাস ফাইলটিতে নিম্নলিখিত বাইটকোড থাকবে:

.field public static final users:Ljava/util/List;
    .annotation system Ldalvik/annotation/Signature;
        value = {
            "Ljava/util/List<",
            "Lcom/example/package/User;",
            ">;"
        }
    .end annotation
.end field

যেসব লাইব্রেরি প্রচুর পরিমাণে প্রতিফলন ব্যবহার করে (যেমন Gson) তারা প্রায়শই আপনার কোডের গঠন গতিশীলভাবে পরিদর্শন এবং বোঝার জন্য এই বৈশিষ্ট্যগুলির উপর নির্ভর করে। R8 এর পূর্ণ মোডে ডিফল্টরূপে, কেবলমাত্র সংশ্লিষ্ট শ্রেণী, ক্ষেত্র বা পদ্ধতি স্পষ্টভাবে রাখা হলেই বৈশিষ্ট্যগুলি ধরে রাখা হয়।

নিম্নলিখিত উদাহরণটি দেখায় যে কেন অ্যাট্রিবিউটগুলি প্রয়োজনীয় এবং সামঞ্জস্যতা থেকে পূর্ণ মোডে স্থানান্তর করার সময় আপনাকে কী কী নিয়ম যোগ করতে হবে।

নিচের উদাহরণটি বিবেচনা করুন যেখানে আমরা Gson লাইব্রেরি ব্যবহার করে ব্যবহারকারীদের একটি তালিকা ডিসিরিয়ালাইজ করি।


import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

data class User(
    @SerializedName("username")
    var username: String? = null,
    @SerializedName("age")
    var age: Int = 0
)

fun GsonRemoteJsonListExample() {
    val gson = Gson()

    // 1. The JSON string for a list of users returned from remote
    val jsonOutput = """[{"username":"alice","age":30}, {"username":"bob","age":25}]"""

    // 2. Deserialize the JSON string into a List<User>
    // We must use TypeToken for generic types like List
    val listType = object : TypeToken<List<User>>() {}.type
    val deserializedList: List<User> = gson.fromJson(jsonOutput, listType)

    // Print the list
    println("First user from list: ${deserializedList}")
}

কম্পাইলেশনের সময়, জাভার টাইপ ইরেজার জেনেরিক টাইপ আর্গুমেন্টগুলি সরিয়ে দেয়। এর অর্থ হল রানটাইমে, List<String> এবং List<User> উভয়ই একটি raw List হিসাবে উপস্থিত হয়। অতএব, Gson এর মতো লাইব্রেরি, যা প্রতিফলনের উপর নির্ভর করে, JSON তালিকা ডিসিরিয়ালাইজ করার সময় List কোন নির্দিষ্ট অবজেক্ট টাইপ রয়েছে তা নির্ধারণ করতে পারে না, যা রানটাইম সমস্যা তৈরি করতে পারে।

টাইপ তথ্য সংরক্ষণের জন্য, Gson TypeToken ব্যবহার করে। TypeToken মোড়ানো প্রয়োজনীয় ডিসিরিয়ালাইজেশন তথ্য ধরে রাখে।

Kotlin এক্সপ্রেশন object:TypeToken<List<User>>() {}.type একটি বেনামী অভ্যন্তরীণ ক্লাস তৈরি করে যা TypeToken প্রসারিত করে এবং জেনেরিক টাইপ তথ্য ক্যাপচার করে। এই উদাহরণে, বেনামী ক্লাসটির নাম $GsonRemoteJsonListExample$listType$1

জাভা প্রোগ্রামিং ভাষা একটি সুপারক্লাসের জেনেরিক স্বাক্ষরকে কম্পাইল করা ক্লাস ফাইলের মধ্যে মেটাডেটা হিসেবে সংরক্ষণ করে, যা Signature অ্যাট্রিবিউট নামে পরিচিত। TypeToken রানটাইমে টাইপটি পুনরুদ্ধার করতে এই Signature মেটাডেটা ব্যবহার করে। এটি Gson কে Signature পড়ার জন্য প্রতিফলন ব্যবহার করতে এবং ডিসিরিয়ালাইজেশনের জন্য প্রয়োজনীয় সম্পূর্ণ List<User> টাইপটি সফলভাবে আবিষ্কার করতে দেয়।

যখন R8 সামঞ্জস্য মোডে সক্রিয় থাকে, তখন এটি ক্লাসের জন্য Signature অ্যাট্রিবিউট ধরে রাখে, যার মধ্যে $GsonRemoteJsonListExample$listType$1 এর মতো বেনামী অভ্যন্তরীণ ক্লাসও অন্তর্ভুক্ত থাকে, এমনকি যদি নির্দিষ্ট keep নিয়মগুলি স্পষ্টভাবে সংজ্ঞায়িত না করা হয়। ফলস্বরূপ, R8 সামঞ্জস্য মোডের জন্য এই উদাহরণটি প্রত্যাশা অনুযায়ী কাজ করার জন্য আর কোনও স্পষ্ট keep নিয়মের প্রয়োজন হয় না।

// keep rule for compatibility mode
-keepattributes Signature

যখন R8 পূর্ণ মোডে সক্রিয় থাকে, তখন $GsonRemoteJsonListExample$listType$1 নামক অভ্যন্তরীণ শ্রেণীর Signature অ্যাট্রিবিউটটি বাদ দেওয়া হয়। Signature এ এই ধরণের তথ্য ছাড়া, Gson সঠিক অ্যাপ্লিকেশন টাইপ খুঁজে পায় না, যার ফলে একটি IllegalStateException তৈরি হয়। এটি প্রতিরোধ করার জন্য প্রয়োজনীয় Keep নিয়মগুলি হল:

// keep rule required for full mode
-keepattributes Signature
-keep,allowobfuscation,allowshrinking,allowoptimization class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking,allowoptimization class * extends com.google.gson.reflect.TypeToken
  • -keepattributes Signature : এই নিয়মটি R8 কে নির্দেশ দেয় যে Gson-এর যে অ্যাট্রিবিউটটি পড়ার প্রয়োজন তা ধরে রাখতে। ফুল মোডে, R8 শুধুমাত্র ক্লাস, ফিল্ড বা পদ্ধতির জন্য Signature অ্যাট্রিবিউট ধরে রাখে যা একটি keep নিয়ম দ্বারা স্পষ্টভাবে মিলে যায়।

  • -keep,allowobfuscation,allowshrinking,allowoptimization class com.google.gson.reflect.TypeToken : এই নিয়মটি প্রয়োজনীয় কারণ TypeToken অবজেক্টের ধরণটি ডিসিরিয়ালাইজ করা হচ্ছে তা মোড়ানো হয়। টাইপ ইরেজারের পরে, জেনেরিক টাইপের তথ্য ধরে রাখার জন্য একটি বেনামী অভ্যন্তরীণ ক্লাস তৈরি করা হয়। com.google.gson.reflect.TypeToken স্পষ্টভাবে না রেখে, R8 পূর্ণ মোডে ডিসিরিয়ালাইজেশনের জন্য প্রয়োজনীয় Signature বৈশিষ্ট্যে এই ক্লাস টাইপটি অন্তর্ভুক্ত করবে না।

  • -keep,allowobfuscation,allowshrinking,allowoptimization class * extends com.google.gson.reflect.TypeToken : এই নিয়মটি TypeToken এক্সটেনশনকারী বেনামী ক্লাসের টাইপ তথ্য ধরে রাখে, যেমন এই উদাহরণে $GsonRemoteJsonListExample$listType$1 । এই নিয়ম ছাড়া, পূর্ণ মোডে R8 প্রয়োজনীয় টাইপ তথ্য সরিয়ে দেয়, যার ফলে ডিসিরিয়ালাইজেশন ব্যর্থ হয়।

Gson সংস্করণ 2.11.0 থেকে শুরু করে, লাইব্রেরিটি সম্পূর্ণ মোডে ডিসিরিয়ালাইজেশনের জন্য প্রয়োজনীয় নিয়মগুলি একত্রিত করে । যখন আপনি R8 সক্ষম করে আপনার অ্যাপ তৈরি করেন, তখন R8 স্বয়ংক্রিয়ভাবে লাইব্রেরি থেকে এই নিয়মগুলি খুঁজে বের করে প্রয়োগ করে। এটি আপনার প্রোজেক্টে এই নির্দিষ্ট নিয়মগুলি ম্যানুয়ালি যোগ বা বজায় না রেখে আপনার অ্যাপের প্রয়োজনীয় সুরক্ষা প্রদান করে।

এটা বোঝা গুরুত্বপূর্ণ যে আগে শেয়ার করা নিয়মগুলি শুধুমাত্র জেনেরিক টাইপ (যেমন, List<User> ) আবিষ্কারের সমস্যার সমাধান করে। R8 ক্লাসের ক্ষেত্রগুলির নামও পরিবর্তন করে। আপনি যদি আপনার ডেটা মডেলগুলিতে @SerializedName অ্যানোটেশন ব্যবহার না করেন, তাহলে Gson JSON কে ডিসিরিয়ালাইজ করতে ব্যর্থ হবে কারণ ক্ষেত্রের নামগুলি আর JSON কীগুলির সাথে মিলবে না।

তবে, যদি আপনি 2.11 এর চেয়ে পুরনো Gson সংস্করণ ব্যবহার করেন, অথবা যদি আপনার মডেলগুলি @SerializedName অ্যানোটেশন ব্যবহার না করে, তাহলে আপনাকে অবশ্যই সেই মডেলগুলির জন্য স্পষ্ট keep নিয়ম যোগ করতে হবে।

ডিফল্ট কনস্ট্রাক্টরটি ধরে রাখুন

R8 ফুল মোডে, no-args/default কনস্ট্রাক্টরটি পরোক্ষভাবে রাখা হয় না, এমনকি যখন ক্লাসটি নিজেই ধরে রাখা হয়। যদি আপনি class.getDeclaredConstructor().newInstance() অথবা class.newInstance() ব্যবহার করে একটি ক্লাসের একটি ইনস্ট্যান্স তৈরি করেন, তাহলে আপনাকে অবশ্যই no-args কনস্ট্রাক্টরটিকে সম্পূর্ণ মোডে স্পষ্টভাবে ধরে রাখতে হবে। বিপরীতে, সামঞ্জস্যতা মোড সর্বদা no-args কনস্ট্রাক্টর ধরে রাখে।

একটি উদাহরণ বিবেচনা করুন যেখানে PrecacheTask এর একটি ইনস্ট্যান্স তৈরি করা হয়েছে প্রতিফলন ব্যবহার করে তার run পদ্ধতিকে গতিশীলভাবে কল করার জন্য। যদিও এই পরিস্থিতিতে সামঞ্জস্যতা মোডে অতিরিক্ত নিয়মের প্রয়োজন হয় না, পূর্ণ মোডে, PrecacheTask এর ডিফল্ট কনস্ট্রাক্টরটি সরানো হবে। অতএব, একটি নির্দিষ্ট কিপ নিয়ম প্রয়োজন।

// In library
interface StartupTask {
    fun run()
}
// The library object that loads and executes the task.
object TaskRunner {
    fun execute(taskClass: Class<out StartupTask>) {
        // The class isn't removed, but its constructor might be.
        val task = taskClass.getDeclaredConstructor().newInstance()
        task.run()
    }
}

// In app
class PreCacheTask : StartupTask {
    override fun run() {
        Log.d("Pre cache task", "Warming up the cache...")
    }
}

fun runTaskRunner() {
    // The library is given a direct reference to the app's task class.
    TaskRunner.execute(PreCacheTask::class.java)
}
# Full mode keep rule
# default constructor needs to be specified

-keep class com.example.fullmoder8.PreCacheTask {
    <init>();
}

অ্যাক্সেস পরিবর্তন ডিফল্টরূপে সক্রিয় থাকে

সামঞ্জস্য মোডে, R8 একটি ক্লাসের মধ্যে পদ্ধতি এবং ক্ষেত্রগুলির দৃশ্যমানতা পরিবর্তন করে না। তবে, পূর্ণ মোডে, R8 আপনার পদ্ধতি এবং ক্ষেত্রগুলির দৃশ্যমানতা পরিবর্তন করে অপ্টিমাইজেশন বাড়ায়, উদাহরণস্বরূপ, ব্যক্তিগত থেকে সর্বজনীনে। এটি আরও ইনলাইনিং সক্ষম করে।

এই অপ্টিমাইজেশন সমস্যা তৈরি করতে পারে যদি আপনার কোডে এমন প্রতিফলন ব্যবহার করা হয় যা বিশেষভাবে নির্দিষ্ট দৃশ্যমানতা সম্পন্ন সদস্যদের উপর নির্ভর করে। R8 এই পরোক্ষ ব্যবহারকে স্বীকৃতি দেবে না, যার ফলে অ্যাপ ক্র্যাশ হতে পারে। এটি প্রতিরোধ করার জন্য, সদস্যদের সংরক্ষণের জন্য আপনাকে নির্দিষ্ট -keep নিয়ম যোগ করতে হবে, যা তাদের মূল দৃশ্যমানতাও সংরক্ষণ করবে।

আরও তথ্যের জন্য, প্রতিফলন ব্যবহার করে ব্যক্তিগত সদস্যদের অ্যাক্সেস করার পরামর্শ কেন দেওয়া হয় না এবং সেই ক্ষেত্র/পদ্ধতিগুলি ধরে রাখার নিয়মগুলি বুঝতে এই উদাহরণটি দেখুন।