কীপ নিয়ম যোগ করা

উচ্চ স্তরে, একটি কিপ রুল একটি ক্লাস (বা সাবক্লাস বা বাস্তবায়ন) নির্দিষ্ট করে, এবং তারপর সেই ক্লাসের মধ্যে মেম্বার—মেথড, কনস্ট্রাক্টর বা ফিল্ড—সংরক্ষণ করতে হবে।

রাখার নিয়মের সাধারণ সিনট্যাক্স নিচে দেওয়া হল:


-<keep_option>[,<keep_option_modifier_1>,<keep_option_modifier_2>,...] <class_specification>

নিচে একটি 'রাখুন' নিয়মের উদাহরণ দেওয়া হল যেখানে keepclassmembers-কে রাখার বিকল্প হিসেবে, allowoptimization-কে পরিবর্তক হিসেবে এবং com.example.MyClass থেকে someSpecificMethod()-কে রাখা হয়েছে:

-keepclassmembers,allowoptimization class com.example.MyClass {
  void someSpecificMethod();
}

বিকল্প রেখে দিন

'রাখুন' বিকল্পটি হল আপনার 'রাখার' নিয়মের প্রথম অংশ। এটি ক্লাসের কোন কোন দিক সংরক্ষণ করতে হবে তা নির্দিষ্ট করে। ছ'টি আলাদা আলাদা Keep বিকল্প আছে, যেমন keep, keepclassmembers, keepclasseswithmembers, keepnames, keepclassmembernames ও keepclasseswithmembernames.

নিচের সারণীতে এই 'রাখুন' বিকল্পগুলি সম্পর্কে বর্ণনা করা হয়েছে:

Keep বিকল্প বিবরণ
keepclassmembers R8 যদি নির্দিষ্ট মেম্বারদের ক্লাস না সরায়, তাহলেই শুধু তাদের তথ্য সুরক্ষিত রাখে।
keep নির্দিষ্ট ক্লাস ও নির্দিষ্ট মেম্বার (ফিল্ড ও পদ্ধতি) সংরক্ষণ করে, যাতে সেগুলি অপ্টিমাইজ করা না যায়।

মনে রাখবেন: keep সাধারণত শুধু কিপ অপশন মডিফায়ার-এর সাথে ব্যবহার করা উচিত কারণ keep নিজে থেকে ম্যাচ করা ক্লাসে যেকোনও ধরনের অপ্টিমাইজেশন হওয়া আটকায়।
keepclasseswithmembers কোনও ক্লাস এবং তার নির্দিষ্ট মেম্বারদের তখনই সংরক্ষণ করে যদি ক্লাসে ক্লাস স্পেসিফিকেশন থেকে সব মেম্বার থাকে।
keepclassmembernames নির্দিষ্ট ক্লাস মেম্বারদের নাম পরিবর্তন করা আটকায়, তবে ক্লাস বা তার মেম্বারদের সরিয়ে দেওয়া আটকায় না।

মনে রাখবেন: এই বিকল্পের অর্থ প্রায়শই ভুল বোঝা হয়; পরিবর্তে সমতুল্য -keepclassmembers,allowshrinking ব্যবহার করার কথা বিবেচনা করুন।
keepnames ক্লাস ও তার মেম্বারদের নাম পরিবর্তন করা আটকায়, তবে অব্যবহৃত বলে মনে হলে সেগুলি সম্পূর্ণভাবে সরিয়ে দেওয়া আটকায় না।

মনে রাখবেন: এই বিকল্পের অর্থ প্রায়ই ভুল বোঝা হয়; এর পরিবর্তে সমার্থক -keep,allowshrinking ব্যবহার করার কথা বিবেচনা করুন।
keepclasseswithmembernames ক্লাস ও তার নির্দিষ্ট মেম্বারদের নাম পরিবর্তন করা আটকায়, তবে শুধু তখনই যদি মেম্বাররা ফাইনাল কোডে থাকেন। এটি কোড সরানো আটকায় না।

মনে রাখবেন: এই বিকল্পের অর্থ প্রায়শই ভুল বোঝা হয়; এর পরিবর্তে সমতুল্য -keepclasseswithmembers,allowshrinking ব্যবহার করার কথা বিবেচনা করুন।

সঠিক 'রাখুন' বিকল্প বেছে নিন

আপনার অ্যাপের জন্য সঠিক অপ্টিমাইজেশন নির্ধারণ করতে, সঠিক 'রাখুন' বিকল্প বেছে নেওয়া অত্যন্ত গুরুত্বপূর্ণ। কিছু 'রাখুন' বিকল্প কোড ছোট করে, এটি এমন একটি প্রসেস যার মাধ্যমে রেফারেন্স করা হয়নি এমন কোড সরিয়ে দেওয়া হয়, অন্য বিকল্পগুলি কোড অস্পষ্ট করে বা নাম পরিবর্তন করে। নিচের টেবিলে বিভিন্ন 'কিপ' বিকল্পের অ্যাকশন দেখানো হয়েছে:

Keep বিকল্প Shrinks ক্লাস ক্লাস অস্পষ্ট করে Shrinks মেম্বার মেম্বারদের অস্পষ্ট করে
keep
keepclassmembers
keepclasseswithmembers
keepnames
keepclassmembernames
keepclasseswithmembernames

বিকল্প মডিফায়ার রাখুন

'রাখুন' সংক্রান্ত নিয়মের পরিধি ও আচরণ নিয়ন্ত্রণ করতে 'রাখুন' বিকল্পের মডিফায়ার ব্যবহার করা হয়। আপনার রাখার নিয়মে আপনি ০ বা তার বেশি রাখার বিকল্প মডিফায়ার যোগ করতে পারবেন।

কীপ বিকল্প মডিফায়ারের সম্ভাব্য ভ্যালুগুলি নিম্নলিখিত টেবিলে বর্ণনা করা হয়েছে:

মান বর্ণনা
allowoptimization নির্দিষ্ট এলিমেন্ট অপ্টিমাইজ করার অনুমতি দেয়। তবে, নির্দিষ্ট করা এলিমেন্টগুলির নাম পরিবর্তন করা বা সেগুলি সরানো হয় না।
allowobfuscation নির্দিষ্ট এলিমেন্টের নাম পরিবর্তন করার অনুমতি দেয়। তবে, এলিমেন্টগুলি সরানো বা অন্যভাবে অপ্টিমাইজ করা হয় না।
allowshrinking R8 যদি নির্দিষ্ট এলিমেন্টের কোনও রেফারেন্স খুঁজে না পায়, তাহলে সেগুলি সরানোর অনুমতি দেয়। তবে, এলিমেন্টগুলির নাম পরিবর্তন করা হয়নি বা অন্য কোনওভাবে অপ্টিমাইজ করা হয়নি।
includedescriptorclasses R8-কে নির্দেশ দেয় যাতে মেথড (প্যারামিটার টাইপ ও রিটার্ন টাইপ) ও ফিল্ডের (ফিল্ড টাইপ) ডেসক্রিপ্টরে দেখানো সব ক্লাস রেখে দেওয়া হয়।
allowaccessmodification অপ্টিমাইজেশন প্রসেস চলাকালীন R8-কে ক্লাস, পদ্ধতি ও ফিল্ডের অ্যাক্সেস মডিফায়ার (public, private, protected) পরিবর্তন (সাধারণত আরও বড়) করার অনুমতি দেয়।
allowrepackage R8-কে ডিফল্ট (রুট) প্যাকেজ সহ বিভিন্ন প্যাকেজে ক্লাস সরানোর অনুমতি দেয়।

শ্রেণী নির্দিষ্টকরণ

আপনাকে অবশ্যই প্রতিটি কিপ নিয়মে একটি ক্লাস (ইন্টারফেস, এনুম ও অ্যানোটেশন ক্লাস সহ) উল্লেখ করতে হবে। আপনি ঐচ্ছিকভাবে কোনও সুপারক্লাস বা প্রয়োগ করা ইন্টারফেস নির্দিষ্ট করে অথবা ক্লাসের জন্য অ্যাক্সেস মডিফায়ার নির্দিষ্ট করে, অ্যানোটেশনের উপর ভিত্তি করে নিয়ম সীমাবদ্ধ করতে পারেন। java.lang নেমস্পেসের ক্লাস যেমন java.lang.String সহ সব ক্লাসকে অবশ্যই তাদের সম্পূর্ণ কোয়ালিফায়েড জাভা নাম ব্যবহার করে নির্দিষ্ট করতে হবে। কোন নাম ব্যবহার করা উচিত তা বুঝতে, জেনারেট করা জাভা নাম পরীক্ষা করুন-এ বর্ণিত টুল ব্যবহার করে বাইটকোড পরীক্ষা করুন।

MaterialButton ক্লাস কীভাবে নির্দিষ্ট করবেন তা নিম্নলিখিত উদাহরণ থেকে দেখুন:

  • সঠিক: com.google.android.material.button.MaterialButton
  • ভুল: MaterialButton

ক্লাস স্পেসিফিকেশন কোনও ক্লাসের মধ্যে সেইসব মেম্বারকেও নির্দিষ্ট করে যেগুলিকে রাখতে হবে। যেমন, নিম্নলিখিত নিয়ম MyClass ক্লাস ও someSpecificMethod() পদ্ধতি বজায় রাখে:

-keep class com.example.MyClass {
  void someSpecificMethod();
}

অ্যানোটেশনের উপর ভিত্তি করে ক্লাস নির্দিষ্ট করা

অ্যানোটেশনের ভিত্তিতে ক্লাস নির্দিষ্ট করতে, অ্যানোটেশনের সম্পূর্ণ উপযুক্ত জাভা নামের আগে @ চিহ্ন যোগ করুন। যেমন:

-keep class @com.example.MyAnnotation com.example.MyClass

কোনও 'কিপ' নিয়মে একাধিক অ্যানোটেশন থাকলে, এটি সেইসব ক্লাস রাখে যেগুলিতে তালিকাভুক্ত সব অ্যানোটেশন থাকে। আপনি একাধিক অ্যানোটেশন তালিকাভুক্ত করতে পারেন, তবে নিয়মটি তখনই প্রযোজ্য হয় যদি ক্লাসে প্রতিটি তালিকাভুক্ত অ্যানোটেশন থাকে। যেমন, নিম্নলিখিত নিয়মটি Annotation1 এবং Annotation2, দুটির মাধ্যমেই অ্যানোটেট করা সব ক্লাস রেখে দেয়।

-keep class @com.example.Annotation1 @com.example.Annotation2 *

সাবক্লাস ও ইমপ্লিমেন্টেশন উল্লেখ করো

সাবক্লাস বা ইন্টারফেস প্রয়োগ করে এমন ক্লাস টার্গেট করতে, যথাক্রমে extend এবং implements ব্যবহার করুন।

যেমন, আপনার যদি সাবক্লাস Foo সহ ক্লাস Bar থাকে, তাহলে:

class Foo : Bar()

নিম্নলিখিত 'রাখুন' নিয়ম Bar-এর সব সাবক্লাসকে সুরক্ষিত রাখে। মনে রাখবেন যে কীপ নিয়মের মধ্যে সুপারক্লাস Bar অন্তর্ভুক্ত থাকে না।

-keep class * extends Bar

আপনার কাছে যদি Foo ক্লাস থাকে যা Bar ইন্টারফেস প্রয়োগ করে:

class Foo : Bar

নিম্নলিখিত 'কিপ' রুল Bar ইমপ্লিমেন্ট করে এমন সব ক্লাসকে সুরক্ষিত রাখে। মনে রাখবেন যে রাখার নিয়মে ইন্টারফেস Bar অন্তর্ভুক্ত থাকে না।

-keep class * implements Bar

অ্যাক্সেস মডিফায়ারের উপর ভিত্তি করে ক্লাস নির্দিষ্ট করা

আপনার রাখার নিয়ম আরও সুনির্দিষ্ট করতে আপনি public, private, static এবং final-এর মতো অ্যাক্সেস মডিফায়ার নির্দিষ্ট করতে পারেন।

যেমন, নিম্নলিখিত নিয়মটি api প্যাকেজ ও এর সাব-প্যাকেজের মধ্যে থাকা সব public ক্লাস এবং এইসব ক্লাসের মধ্যে থাকা সব পাবলিক ও সুরক্ষিত মেম্বারকে রাখে।

-keep public class com.example.api.** { public protected *; }

এছাড়াও, আপনি ক্লাসের মধ্যে থাকা মেম্বারদের জন্য মডিফায়ার ব্যবহার করতে পারবেন। যেমন, নিম্নলিখিত নিয়মটি শুধুমাত্র Utils ক্লাসের public static পদ্ধতি রাখে:

-keep class com.example.Utils {
  public static void *(...);
}

Kotlin-নির্দিষ্ট মডিফায়ার

R8-এ internal ও suspend-এর মতো Kotlin-নির্দিষ্ট মডিফায়ার কাজ করে না। এই ধরনের ফিল্ড রাখার জন্য নিম্নলিখিত নির্দেশিকা অনুসরণ করুন।

  • internal ক্লাস, পদ্ধতি বা ফিল্ড রাখতে, এটিকে সর্বজনীন হিসেবে বিবেচনা করুন। যেমন, নিম্নলিখিত Kotlin সোর্সটি বিবেচনা করুন:

    package com.example
    internal class ImportantInternalClass {
      internal val f: Int
      internal fun m() {}
    }
    

    Kotlin কম্পাইলারের তৈরি করা .class ফাইলে internal ক্লাস, মেথড ও ফিল্ড public হিসেবে থাকে, তাই আপনাকে অবশ্যই নিম্নলিখিত উদাহরণের মতো public কীওয়ার্ড ব্যবহার করতে হবে:

    -keepclassmembers public class com.example.ImportantInternalClass {
      public int f;
      public void m();
    }
    
  • কোনও suspend মেম্বার কম্পাইল করা হলে, keep নিয়মে সেটির কম্পাইল করা বাইটকোড সিগনেচার ম্যাচ করুন।

    যেমন, Kotlin-এ সংজ্ঞায়িত একটি বাস্তব-জগতের কংক্রিট রিপোজিটরি ক্লাস বিবেচনা করুন নিম্নলিখিত হিসাবে:

    package com.example.repository
    
    import com.example.model.User
    
    class UserRepository {
        suspend fun fetchUser(id: String): User {
            // Implementation details...
        }
    }
    

    Kotlin কম্পাইলার এই ক্লাসকে বাইটকোডে কম্পাইল করলে, suspend ফাংশন কন্টিনিউয়েশন-পাসিং স্টাইল (CPS) ট্রান্সফর্মেশনের মধ্য দিয়ে যায়। suspend মডিফায়ার সরিয়ে দেওয়া হয়েছে, রিটার্ন টাইপ পরিবর্তন করে java.lang.Object করা হয়েছে এবং অ্যাসিঙ্ক্রোনাস স্টেট মেশিন ম্যানেজ করার জন্য মেথড সিগনেচারে kotlin.coroutines.Continuation প্যারামিটার যোগ করা হয়েছে।

    বাইটকোডে কম্পাইল করা মেথড সিগনেচার এইভাবে দেখানো হয়:

    public final Object fetchUser(String id, Continuation<? super User> continuation);
    

    এই ফাংশনের জন্য একটি কিপ নিয়ম লিখতে, আপনি Kotlin suspend সিনট্যাক্স ব্যবহার করতে পারবেন না। পরিবর্তে, আপনাকে অবশ্যই কম্পাইল করা বাইটকোড সিগনেচার (নির্দিষ্টভাবে kotlin.coroutines.Continuation-এর রেফারেন্স) মেলাতে হবে অথবা ... ব্যবহার করতে হবে।

    সুনির্দিষ্টভাবে কম্পাইল করা বাইটকোড সিগনেচারের সাথে মেলে এমন একটি উদাহরণ নিচে দেওয়া হল:

    -keepclassmembers class com.example.repository.UserRepository {
      public java.lang.Object fetchUser(java.lang.String, kotlin.coroutines.Continuation);
    }
    

    ... ব্যবহার করে একটি উদাহরণ নিচে দেওয়া হল:

    -keepclassmembers class com.example.repository.UserRepository {
      public java.lang.Object fetchUser(...);
    }
    

মেম্বার স্পেসিফিকেশন

ক্লাস স্পেসিফিকেশনে ঐচ্ছিকভাবে সংরক্ষিত ক্লাস মেম্বার অন্তর্ভুক্ত থাকে। আপনি যদি কোনও ক্লাসের জন্য এক বা একাধিক মেম্বার নির্দিষ্ট করেন, তাহলে নিয়মটি অন্যান্য মেম্বারদের ক্ষেত্রে প্রযোজ্য হবে না।

অ্যানোটেশনের ভিত্তিতে মেম্বারদের নির্দিষ্ট করুন

আপনি তাদের অ্যানোটেশনের উপর ভিত্তি করে মেম্বারদের নির্দিষ্ট করতে পারবেন। ক্লাসের মতোই, আপনি অ্যানোটেশনের সম্পূর্ণ উপযুক্ত জাভা নামের আগে @ যোগ করেন। এর ফলে, আপনি কোনও ক্লাসে শুধু সেইসব মেম্বারদের রাখতে পারবেন যাদের নির্দিষ্ট অ্যানোটেশন দিয়ে চিহ্নিত করা হয়েছে। যেমন, পদ্ধতি ও ফিল্ড @com.example.MyAnnotation-এর সাথে অ্যানোটেট করে রাখতে:

-keep class com.example.MyClass {
  @com.example.MyAnnotation <methods>;
  @com.example.MyAnnotation <fields>;
}

আপনি শক্তিশালী, টার্গেট করা নিয়মের জন্য এটি ক্লাস-লেভেল অ্যানোটেশন ম্যাচিংয়ের সাথে একত্রিত করতে পারেন:

-keep class @com.example.ClassAnnotation * {
  @com.example.MethodAnnotation <methods>;
  @com.example.FieldAnnotation <fields>;
}

এটি @ClassAnnotation দিয়ে অ্যানোটেট করা ক্লাসগুলি রেখে দেয় এবং সেইসব ক্লাসে, এটি @MethodAnnotation দিয়ে অ্যানোটেট করা মেথড ও @FieldAnnotation দিয়ে অ্যানোটেট করা ফিল্ড রেখে দেয়।

সম্ভব হলে, অ্যানোটেশন-ভিত্তিক 'রাখার' নিয়ম ব্যবহার করার কথা বিবেচনা করুন। এই পদ্ধতি আপনার কোড এবং আপনার রাখার নিয়মের মধ্যে একটি স্পষ্ট লিঙ্ক প্রদান করে এবং প্রায়শই আরও শক্তিশালী কনফিগারেশনের দিকে পরিচালিত করে। যেমন, androidx.annotation অ্যানোটেশন লাইব্রেরি এই মেকানিজম ব্যবহার করে।

আপনার নিজের কোডে রিফ্লেকশনের জন্য, আপনি Jetpack Keep ব্যবহার করতে পারেন। অ্যানোটেশন টার্গেট করে এমন কিপ নিয়ম লেখার পরিবর্তে, আপনি নিজের কোড অ্যানোটেট করেন এবং Gradle প্লাগ-ইন আপনার জন্য কিপ নিয়ম তৈরি করে।

পদ্ধতি

কীপ নিয়মের মেম্বার স্পেসিফিকেশনে কোনও পদ্ধতি নির্দিষ্ট করার সিনট্যাক্স নিম্নলিখিত:

[<access_modifier>] [<return_type>] <method_name>(<parameter_types>);

যেমন, নিম্নলিখিত 'কিপ' রুল getUserId() নামের একটি পাবলিক মেথড রাখে যা String রিটার্ন করে।

-keep class com.example.model.UserData {
    public java.lang.String getUserId();
}

আপনি কোনও ক্লাসের সব পদ্ধতিকে নিম্নলিখিতভাবে ম্যাচ করানোর জন্য শর্টকাট হিসেবে <methods> ব্যবহার করতে পারেন:

-keep class com.example.model.UserData {
    <methods>;
}

রিটার্ন টাইপ ও প্যারামিটার টাইপের জন্য কীভাবে টাইপ নির্দিষ্ট করতে হয় সেই সম্পর্কে আরও জানতে, টাইপ দেখুন।

নির্মাণকারী

কনস্ট্রাক্টর নির্দিষ্ট করতে, <init> ব্যবহার করুন। কিপ রুলের জন্য মেম্বার স্পেসিফিকেশনে কনস্ট্রাক্টর নির্দিষ্ট করার সিনট্যাক্স নিচে দেওয়া হল:

[<access_modifier>] <init>(parameter_types);

যেমন, নিম্নলিখিত কিপ নিয়মটি a UI স্টেট হোল্ডারের জন্য একটি কনস্ট্রাক্টর রাখে যা একটি রেপোজিটরি ইনস্ট্যান্স নেয়।

-keep class com.example.ui.state.UserViewModel {
    public <init>(com.example.repository.UserDataRepository);
}

সব পাবলিক কনস্ট্রাক্টর রাখতে, রেফারেন্স হিসেবে নিম্নলিখিত উদাহরণ ব্যবহার করুন:

-keep class com.example.ui.state.UserViewModel {
    public <init>(...);
}

ফিল্ড

রাখার নিয়মের জন্য মেম্বার স্পেসিফিকেশনে ফিল্ড নির্দিষ্ট করার সিনট্যাক্স নিম্নলিখিত:

[<access_modifier>...] [<type>] <field_name>;

যেমন, নিম্নলিখিত কিপ রুল userId নামের একটি ব্যক্তিগত স্ট্রিং ফিল্ড এবং STATUS_ACTIVE নামের একটি সর্বজনীন স্ট্যাটিক ইন্টিজার ফিল্ড রাখে:

-keep class com.example.models.User {
    private java.lang.String userId;
    public static int STATUS_ACTIVE;
}

আপনি কোনও ক্লাসের সব ফিল্ডকে এগুলি হিসেবে ম্যাচ করানোর জন্য শর্টকাট হিসেবে <fields> ব্যবহার করতে পারেন:

-keep class com.example.models.User {
    <fields>;
}

ধরন

এই বিভাগে, কীভাবে রিটার্ন টাইপ, প্যারামিটার টাইপ ও ফিল্ড টাইপ, কিপ রুল মেম্বার স্পেসিফিকেশনে উল্লেখ করতে হয় তা বর্ণনা করা হয়েছে। Kotlin সোর্স কোডের থেকে আলাদা হলে, টাইপ নির্দিষ্ট করতে জেনারেট করা Java নাম ব্যবহার করতে ভুলবেন না।

প্রিমিটিভ টাইপ

প্রিমিটিভ টাইপ নির্দিষ্ট করতে, এর জাভা কীওয়ার্ড ব্যবহার করুন। R8 নিম্নলিখিত প্রিমিটিভ টাইপ শনাক্ত করে: boolean, byte, short, char, int, long, float, double.

প্রিমিটিভ টাইপ সহ একটি উদাহরণ নিয়ম নিচে দেওয়া হল:

# Keeps a method that takes an int and a float as parameters.
-keepclassmembers class com.example.Calculator {
    public void setValues(int, float);
}

জেনেরিক টাইপ

কম্পাইল করার সময়, Kotlin/Java কম্পাইলার জেনেরিক টাইপের তথ্য মুছে দেয়, তাই জেনেরিক টাইপ আছে এমন নিয়ম রাখার সময় আপনাকে অবশ্যই আপনার কোডের কম্পাইল করা প্রতিনিধিত্বকে টার্গেট করতে হবে, আসল সোর্স কোডকে নয়। জেনেরিক টাইপ কীভাবে পরিবর্তন করা হয় সেই সম্পর্কে আরও জানতে, টাইপ ইরেজার দেখুন।

যেমন, আপনার কাছে যদি Box.kt-এ সংজ্ঞায়িত আনবাউন্ডেড জেনেরিক টাইপ সহ নিম্নলিখিত কোড থাকে:

package com.myapp.data

class Box<T>(val item: T) {
    fun getItem(): T {
        return item
    }
}

টাইপ মুছে ফেলার পরে, T-এর পরিবর্তে Object ব্যবহার করা হয়। ক্লাস কনস্ট্রাক্টর ও পদ্ধতি রেখে দিতে, আপনার নিয়মে জেনেরিক T-এর পরিবর্তে java.lang.Object ব্যবহার করতে হবে।

রাখার নিয়ম সংক্রান্ত একটি উদাহরণ নিচে দেওয়া হল:

# Keep the constructor and methods of the Box class.
-keep class com.myapp.data.Box {
    public init(java.lang.Object);
    public java.lang.Object getItem();
}

NumberBox.kt-এ বাউন্ডেড জেনেরিক টাইপ সহ আপনার কাছে নিম্নলিখিত কোড থাকলে:

package com.myapp.data

// T is constrained to be a subtype of Number
class NumberBox<T : Number>(val number: T)

এই ক্ষেত্রে, টাইপ ইরেজার T-কে তার বাউন্ড java.lang.Number দিয়ে প্রতিস্থাপন করে।

কীভাবে ডেটা রাখা হবে তার একটি উদাহরণ নিচে দেওয়া হল:

-keep class com.myapp.data.NumberBox {
    public init(java.lang.Number);
}

অ্যাপ-নির্দিষ্ট জেনেরিক টাইপ মূল ক্লাস হিসেবে ব্যবহার করার সময়, মূল ক্লাসের জন্য কিপ রুল অন্তর্ভুক্ত করাও প্রয়োজন।

যেমন, নিম্নলিখিত কোডের জন্য:

package com.myapp.data

data class UnpackOptions(val useHighPriority: Boolean)

// The generic Box class with UnpackOptions as the bounded type
class Box<T: UnpackOptions>(val item: T) {
}

আপনি includedescriptorclasses-এর সাথে একটি কিপ নিয়ম ব্যবহার করে, নিম্নলিখিত নিয়মের মতো একটি নিয়ম দিয়ে UnpackOptions ক্লাস ও Box ক্লাস পদ্ধতি, দুটিই সেভ করতে পারবেন:

-keep,includedescriptorclasses class com.myapp.data.Box {
    public <init>(com.myapp.data.UnpackOptions);
}

অবজেক্টের তালিকা প্রসেস করে এমন নির্দিষ্ট ফাংশন রাখতে, আপনাকে এমন একটি নিয়ম লিখতে হবে যা ফাংশনের সিগনেচারের সাথে হুবহু মিলে যায়। মনে রাখবেন যে, জেনেরিক ধরন মুছে দেওয়া হয়, তাই List<Product>-এর মতো প্যারামিটারকে java.util.List হিসেবে দেখা হয়।

যেমন, আপনার যদি এমন একটি ফাংশন সহ ইউটিলিটি ক্লাস থাকে যা নিম্নলিখিতভাবে Product অবজেক্টের একটি তালিকা প্রসেস করে:

package com.myapp.utils

import com.myapp.data.Product
import android.util.Log

class DataProcessor {
    // This is the function we want to keep
    fun processProducts(products: List<Product>) {
        Log.d("DataProcessor", "Processing ${products.size} products.")
        // Business logic ...
    }
}

// The data class used in the list (from the previous example)
package com.myapp.data
data class Product(val id: String, val name: String)

আপনি শুধু processProducts ফাংশন সুরক্ষিত রাখতে নিম্নলিখিত রাখার নিয়ম ব্যবহার করতে পারেন:

-keep class com.myapp.utils.DataProcessor {
    public void processProducts(java.util.List);
}

অ্যারের ধরন

অ্যারের প্রতিটি ডাইমেনশনের জন্য কম্পোনেন্ট টাইপের সাথে [] যোগ করে অ্যারে টাইপ নির্দিষ্ট করুন। এটি ক্লাস ও প্রিমিটিভ, দুই ধরনের টাইপের ক্ষেত্রেই প্রযোজ্য।

  • এক-মাত্রিক ক্লাস অ্যারে: java.lang.String[]
  • দ্বিমাত্রিক প্রিমিটিভ অ্যারে: int[][]

যেমন, আপনার কাছে নিম্নলিখিত কোড থাকলে:

package com.example.data

class ImageProcessor {
  fun process(): ByteArray {
    // process image to return a byte array
  }
}

আপনি নিম্নলিখিত রাখার নিয়ম ব্যবহার করতে পারেন:

# Keeps a method that returns a byte array.
-keepclassmembers class com.example.data.ImageProcessor {
    public byte[] process();
}

উদাহরণ

যেমন, নির্দিষ্ট ক্লাস ও তার সব মেম্বারকে সুরক্ষিত রাখতে, নিম্নলিখিত কোড ব্যবহার করুন:

-keep class com.myapp.MyClass { *; }

শুধুমাত্র ক্লাস ও তার ডিফল্ট কনস্ট্রাক্টরকে রেখে, অন্যান্য মেম্বারদের রাখতে না চাইলে, নিম্নলিখিত কোড ব্যবহার করুন:

-keep class com.myapp.MyClass

আমরা সাজেস্ট করি যে আপনি সবসময় কিছু মেম্বার নির্দিষ্ট করুন। যেমন, নিম্নলিখিত উদাহরণে MyClass ক্লাসের মধ্যে পাবলিক ফিল্ড text এবং পাবলিক মেথড updateText() রাখা হয়েছে।

-keep class com.myapp.MyClass {
    public java.lang.String text;
    public void updateText(java.lang.String);
}

সব পাবলিক ফিল্ড ও পাবলিক মেথড বজায় রাখতে, নিম্নলিখিত উদাহরণ দেখুন:

-keep public class com.example.api.ApiClient {
    public *;
}

মেম্বার নির্দিষ্ট করার বিষয়টি বাদ দিন

মেম্বার স্পেসিফিকেশন বাদ দিলে, R8 ক্লাসটির ডিফল্ট কনস্ট্রাক্টর রেখে দেয়।

যেমন, আপনি -keep class com.example.MyClass অথবা -keep class com.example.MyClass {} লিখলে, R8 এগুলিকে এমনভাবে দেখবে যেন আপনি নিচে লেখা কোডটি লিখেছেন:

-keep class com.example.MyClass{
  void <init>();
}

মেম্বারের নামের প্যাটার্ন বাতিল করা

Android Gradle Plugin (AGP) 9.2.0 থেকে শুরু করে, আপনি নিজের 'রাখুন' নিয়মের মধ্যে মেম্বারের নামের প্যাটার্ন নেগেট করতে পারবেন। এর মাধ্যমে আপনি প্যাটার্নের উপর ভিত্তি করে মেম্বারদের রাখতে বা বাদ দিতে পারবেন। কোনও প্যাটার্নকে নেগেট করতে, মেম্বারের নামের প্যাটার্নের আগে একটি বিস্ময়বোধক চিহ্ন (!) যোগ করুন।

আপনি এই ফিচারটি এমন পরিস্থিতিতে ব্যবহার করতে পারেন যেখানে আপনি বেশিরভাগ মেম্বারদের রাখতে চান যারা একটি বৃহত্তর প্যাটার্নের সাথে মেলে, কিন্তু নির্দিষ্ট মেম্বারদের বাদ দিতে চান, যেমন শুধুমাত্র পরীক্ষার জন্য তৈরি করা পদ্ধতি।

উদাহরণ:

com.example.MyClass-এ সব পাবলিক মেথড রাখতে, যেগুলি "ForTesting" দিয়ে শেষ হয় সেগুলি ছাড়া, নিম্নলিখিত নিয়ম ব্যবহার করুন:

-keepclassmembers class com.example.MyClass {
    public *** !*ForTesting(...);
}

প্যাকেজ-লেভেল ফাংশন

কোনও ক্লাসের বাইরে (সাধারণত টপ লেভেল ফাংশন বলা হয়) সংজ্ঞায়িত Kotlin ফাংশনকে রেফার করতে, Kotlin কম্পাইলারের মাধ্যমে পরোক্ষভাবে যোগ করা ক্লাসের জন্য জেনারেট করা Java নাম ব্যবহার করতে ভুলবেন না। ক্লাসের নাম হল Kotlin ফাইলের নাম যার সাথে Kt যোগ করা আছে। যেমন, আপনার কাছে যদি MyClass.kt নামের একটি Kotlin ফাইল থাকে যা এইভাবে সংজ্ঞায়িত করা হয়েছে:

package com.example.myapp.utils

// A top-level function not inside a class
fun isEmailValid(email: String): Boolean {
    return email.contains("@")
}

isEmailValid ফাংশনের জন্য একটি কিপ রুল লিখতে, ক্লাস স্পেসিফিকেশনকে জেনারেট করা ক্লাস MyClassKt-কে টার্গেট করতে হবে:

-keep class com.example.myapp.utils.MyClassKt {
    public static boolean isEmailValid(java.lang.String);
}

ওয়াইল্ডকার্ড

নির্দিষ্ট প্যাটার্নের সাথে মেলে এমন একাধিক ক্লাস বা মেম্বারের ক্ষেত্রে রাখার নিয়ম প্রয়োগ করতে কীভাবে ওয়াইল্ডকার্ড ব্যবহার করবেন তা নিম্নলিখিত সারণীতে দেখানো হয়েছে।

ওয়াইল্ডকার্ড ক্লাস বা মেম্বারদের ক্ষেত্রে প্রযোজ্য বিবরণ
** দুটিই সবচেয়ে বেশি ব্যবহার করা হয়। যেকোনও সংখ্যক প্যাকেজ বিভাজক সহ যেকোনও ধরনের নামের সাথে মেলে। প্যাকেজ ও এর সাব-প্যাকেজের মধ্যে থাকা সব ক্লাস ম্যাচ করানোর জন্য এটি কাজে লাগে।
* দুটিই ক্লাস স্পেসিফিকেশনের জন্য, কোনও ধরনের নামের যেকোনও অংশের সাথে মেলে যাতে প্যাকেজ বিভাজক (.) থাকে না
মেম্বার স্পেসিফিকেশনের জন্য, যেকোনও পদ্ধতি বা ফিল্ডের নামের সাথে মেলে। এটি একা ব্যবহার করা হলে, এটি **-এর একটি ছদ্মনামও।
? দুটিই কোনও ক্লাস বা মেম্বারের নামের যেকোনও একটি অক্ষরের সাথে মেলে।
*** সদস্য প্রিমিটিভ টাইপ (যেমন int), ক্লাস টাইপ (যেমন java.lang.String) এবং যেকোনও ডাইমেনশনের অ্যারে টাইপ (যেমন byte[][]) সহ যেকোনও ধরনের সাথে ম্যাচ করে।
... সদস্য কোনও পদ্ধতির প্যারামিটারের যেকোনও তালিকার সাথে ম্যাচ করে।
% সদস্য যেকোনও প্রিমিটিভ টাইপের (যেমন int, float, boolean বা অন্যান্য) সাথে মেলে।

বিশেষ ওয়াইল্ডকার্ড কীভাবে ব্যবহার করবেন তার কিছু উদাহরণ এখানে দেওয়া হল:

  • আপনার কাছে একই নামের একাধিক পদ্ধতি থাকলে, যেগুলি ইনপুট হিসেবে বিভিন্ন প্রিমিটিভ টাইপ নেয়, সেক্ষেত্রে আপনি % ব্যবহার করে এমন একটি 'কিপ' রুল লিখতে পারেন যা সেগুলি সবকটিকেই রেখে দেয়। যেমন, এই DataStore ক্লাসে একাধিক setValue পদ্ধতি আছে:

    class DataStore {
        fun setValue(key: String, value: Int) { ... }
        fun setValue(key: String, value: Boolean) { ... }
        fun setValue(key: String, value: Float) { ... }
    }
    

    নিম্নলিখিত 'কিপ' নিয়ম সব পদ্ধতি রাখে:

    -keep class com.example.DataStore {
        public void setValue(java.lang.String, %);
    }
    
  • আপনার কাছে একাধিক ক্লাস থাকলে এবং সেগুলির নাম একটি অক্ষর দ্বারা পরিবর্তিত হলে, সেগুলি সব রাখার জন্য একটি রাখার নিয়ম লিখতে ? ব্যবহার করুন। যেমন, আপনার যদি নিম্নলিখিত ক্লাস থাকে:

    com.example.models.UserV1 {...}
    com.example.models.UserV2 {...}
    com.example.models.UserV3 {...}
    

    নিচে উল্লেখ করা 'কিপ' নিয়মের মাধ্যমে সব ক্লাস রাখা হয়:

    -keep class com.example.models.UserV?
    
  • Example ও AnotherExample ক্লাস (যদি সেগুলি রুট-লেভেল ক্লাস হয়) ম্যাচ করাতে, কিন্তু com.foo.Example ক্লাস ম্যাচ না করাতে, নিম্নলিখিত কিপ নিয়ম ব্যবহার করুন:

    -keep class *Example
    
  • আপনি শুধু * ব্যবহার করলে, এটি **-এর বিকল্প হিসেবে কাজ করে। যেমন, নিচে দেওয়া কিপ নিয়মগুলি একই:

    -keepclasseswithmembers class * { public static void main(java.lang.String[];) }
    
    -keepclasseswithmembers class ** { public static void main(java.lang.String[];) }
    

শর্তসাপেক্ষে রাখার নিয়ম

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

শর্তাধীন রাখার নিয়মগুলি বিশেষ করে লাইব্রেরি বা কোড প্যাটার্ন নিয়ে কাজ করার সময় কাজে লাগে, যেখানে কোনও নির্দিষ্ট ক্লাস বা মেম্বার থাকলে বা প্যাটার্নের সাথে মিললে তবেই রাখার নিয়ম প্রয়োজন হয়। কন্ডিশনাল নিয়ম ব্যবহার করলে, অপ্রয়োজনীয় কোড রিটেনশন আটকানোর মাধ্যমে আপনার অ্যাপের সাইজ ছোট করতে সাহায্য করে।

শর্তসাপেক্ষে রাখার নিয়মের সাধারণ সিনট্যাক্স হল:

-if <class_specification_if> <keep_rule>

-if শর্তে ক্লাস স্পেসিফিকেশনে ওয়াইল্ডকার্ড (যেমন * বা **) থাকলে, ওয়াইল্ডকার্ডের সাথে ম্যাচ করা অক্ষরের ক্রম ক্যাপচার করা হয়। আপনি ব্যাকরেফারেন্স ব্যবহার করে পরবর্তী রাখার নিয়মে এইসব ক্যাপচার করা স্ট্রিং রেফার করতে পারবেন: <1> প্রথম ওয়াইল্ডকার্ডের মাধ্যমে ক্যাপচার করা স্ট্রিং রেফার করে, <2> দ্বিতীয় ওয়াইল্ডকার্ডের মাধ্যমে ক্যাপচার করা স্ট্রিং রেফার করে এবং আরও অনেক কিছু।

যেমন, Jetpack Navigation কম্পোনেন্ট, গন্তব্যের মধ্যে টাইপ-সেফ আর্গুমেন্ট পাস করার জন্য NavArgs ক্লাস জেনারেট করে। NavArgsLazy প্রতিনিধি ব্যবহার করার সময়, এটি আর্গুমেন্ট ডিসিরিয়ালাইজ করার জন্য জেনারেট করা NavArgs ক্লাসে একটি স্ট্যাটিক fromBundle পদ্ধতি খুঁজে পেতে এবং আহ্বান করতে রিফ্লেকশন ব্যবহার করে। আপনার অ্যাপে NavArgs ব্যবহার করা হলে, আপনাকে শুধুমাত্র সেইসব ক্লাসের জন্য fromBundle পদ্ধতি রাখতে হবে যেগুলি NavArgs ইন্টারফেস ইমপ্লিমেন্ট করে।

আপনি কন্ডিশনাল কিপ নিয়ম ব্যবহার করে এটি নির্দিষ্ট করতে পারেন যে কোনও ক্লাস যদি androidx.navigation.NavArgs প্রয়োগ করে, তাহলে R8-কে সেই নির্দিষ্ট ক্লাসের জন্য fromBundle পদ্ধতি রাখতে হবে:

# If a class implements NavArgs...
-if public class ** implements androidx.navigation.NavArgs
# ...then keep the fromBundle method of that matched class (<1>).
-keepclassmembers public class <1> {
    public static ** fromBundle(android.os.Bundle);
}

এই উদাহরণে, ** হল -if শর্তের প্রথম এবং একমাত্র ওয়াইল্ডকার্ড। এটি androidx.navigation.NavArgs প্রয়োগকারী যেকোনও ক্লাসের নামের সাথে ম্যাচ করে। ** যে স্ট্রিং ম্যাচ করে (এই ক্ষেত্রে, ক্লাস নেম) সেটি ক্যাপচার করা হয় এবং আপনি পরবর্তী নিয়মে <1> ব্যবহার করে সেটি উল্লেখ করতে পারেন। তাই -keepclassmembers নিয়মটি androidx.navigation.NavArgs প্রয়োগকারী যেকোনও ক্লাসের ক্ষেত্রে প্রযোজ্য হয় যা -if শর্তের সাথে ম্যাচ করেছে। R8 NavArgs প্রয়োগকারী কোনও ক্লাস খুঁজে না পেলে, এই কিপ নিয়মটি উপেক্ষা করে।

আরেকটি সাধারণ ব্যবহারিক ক্ষেত্র হল Gson-এর মতো JSON সিরিয়ালাইজেশন লাইব্রেরি। আপনার ডেটা মডেল ক্লাসে কোনও ফিল্ডে Gson-এর @SerializedName অ্যানোটেশন ব্যবহার করা হলে, আপনি এমন কোনও ক্লাস ও তার মেম্বারদের সুরক্ষিত রাখতে কন্ডিশনাল নিয়ম ব্যবহার করতে পারেন যেগুলি Gson-কে রিফ্লেকশনের জন্য প্রয়োজন:

# If a class has fields annotated with @SerializedName...
-if class ** { @com.google.gson.annotations.SerializedName <fields>; }
# ...then keep that class (<1>), its @SerializedName fields,
# and its constructors for Gson.
-keep class <1> {
    @com.google.gson.annotations.SerializedName <fields>;
    <init>(...);
}

ব্যাকরেফারেন্স স্ট্রিং ক্যাপচার করে, যা ক্লাস নামের সাবস্ট্রিং হতে পারে যদি কোনও ওয়াইল্ডকার্ড শুধুমাত্র নামের অংশ ম্যাচ করে। যেমন, আপনি যদি -if class com.example.*X* ব্যবহার করেন, তাহলে R8, X-এর আগে থাকা সাবস্ট্রিং <1> এবং X-এর পরে থাকা সাবস্ট্রিং <2> হিসেবে ক্যাপচার করে। X আছে এমন যেকোনও ক্লাসের নাম খুঁজে পেতে এবং X-এর জায়গায় Y বসিয়ে সংশ্লিষ্ট ক্লাসটি রাখতে নিম্নলিখিত নিয়মটি এটি ব্যবহার করে:

# If a class like com.example.PrefixXPostfix exists...
-if class com.example.*X*
# ...keep com.example.PrefixYPostfix.
-keep class com.example.<1>Y<2>

রিফ্লেকশনের জন্য কন্ডিশনাল কিপ রুল

কন্ডিশনাল কিপ নিয়মের একটি সাধারণ ব্যবহার হল রিফ্লেকশন ম্যানেজ করা, যেখানে রানটাইমে নির্দিষ্ট পদ্ধতি বা ক্লাস ডাইনামিক অ্যাক্সেস করা হয়। যেমন, কোনও লাইব্রেরি যদি আপনার কোডের সাথে ইন্টার‍্যাক্ট করার জন্য রিফ্লেকশন ব্যবহার করে, তাহলে সেই লাইব্রেরির নির্দিষ্ট ফিচার ব্যবহার করলে, আপনাকে হয়ত শুধু নির্দিষ্ট মেম্বারদের রাখতে হবে।

Jetpack Navigation লাইব্রেরি, টাইপ-সেফ আর্গুমেন্ট পাস করার জন্য জেনারেট করা NavArgs ক্লাসে স্ট্যাটিক fromBundle মেথড কল করতে NavArgsLazy delegate ব্যবহার করে রিফ্লেকশন ব্যবহার করে। এই পদ্ধতিটি শুধুমাত্র NavArgs ইমপ্লিমেন্টেশনের জন্য রাখা হয়েছে এবং প্রতিটি ক্লাসের জন্য নয় তা নিশ্চিত করতে, Jetpack Navigation-এ নিম্নলিখিত শর্তসাপেক্ষ রাখার নিয়ম অন্তর্ভুক্ত রয়েছে:

# If a class implements NavArgs...
-if public class ** implements androidx.navigation.NavArgs
# ...then keep the fromBundle method of that matched class (<1>).
-keepclassmembers public class <1> {
    public static ** fromBundle(android.os.Bundle);
}

এই নিয়ম শুধুমাত্র সেইসব ক্লাসের জন্য fromBundle রাখে যেগুলির এটি প্রয়োজন, সব ক্লাসের জন্য রাখে না অথবা কোন কোন ক্লাসের এটি প্রয়োজন তা আপনাকে ম্যানুয়ালি উল্লেখ করতে হয় না।

জেনারেট করা Java নামগুলি পরীক্ষা করা

কিপ রুল লেখার সময়, জাভা বাইটকোডে কম্পাইল করার পরে আপনাকে অবশ্যই ক্লাস ও অন্যান্য রেফারেন্সের ধরন তাদের নাম ব্যবহার করে নির্দিষ্ট করতে হবে (উদাহরণ দেখতে ক্লাস স্পেসিফিকেশন ও টাইপ দেখুন)। আপনার কোডের জন্য জেনারেট করা জাভা নাম কী তা চেক করতে, Android Studio-তে নিম্নলিখিত টুলগুলির মধ্যে যেকোনও একটি ব্যবহার করুন:

  • APK Analyzer
  • Kotlin সোর্স ফাইল খোলা অবস্থায়, টুল > Kotlin > Kotlin বাইটকোড দেখুন > ডিকম্পাইল করুন বিকল্পে গিয়ে বাইটকোড চেক করুন।