Android পারফর্ম্যান্স ইঞ্জিনিয়ার
Android অ্যাপ প্রায়ই আন্ডার-অপ্টিমাইজড অবস্থায় শিপ করা হয়, কারণ থার্ড-পার্টি লাইব্রেরি থেকে পাওয়া অত্যন্ত রক্ষণশীল 'কিপ' রুল, যার ফলে APK-এর সাইজ বড় হয়ে যায়, কোল্ড স্টার্টের সময় বেশি লাগে এবং রানটাইম পারফর্ম্যান্স খারাপ হয়।
DEX অপ্টিমাইজেশনের জন্য Google Play-এর নতুন পারফর্ম্যান্স থ্রেশহোল্ডের মাধ্যমে, অ্যাপের দৃশ্যমানতা এখন সরাসরি অপ্টিমাইজেশন কার্ভের থেকে এগিয়ে থাকার উপর নির্ভর করে।
এই আলোচনায় রুল ব্লট সামলানোর জন্য একটি আধুনিক R8 ওয়ার্কফ্লোর সাথে পরিচয় করিয়ে দেওয়া হয়। আপনি শিখবেন:
ট্রান্সস্ক্রিপ্ট করা
হ্যালো!
আমার নাম ব্রিয়ানা, আমি Android DevRel টিমে আছি।
আমি ক্রিস ক্রেইক, আমি Android পারফর্ম্যান্স টিমে আছি।
টাইপ-সেফ কিপ রুল ও DEX অপ্টিমাইজেশনে স্বাগত।
আজ আমরা DEX অপ্টিমাইজেশন কী, কেন আপনার এটি নিয়ে চিন্তা করা উচিত এবং কিছু সাম্প্রতিক আপডেট সম্পর্কে কথা বলব।
অনেক দিন ধরে, DEX অপ্টিমাইজেশনকে এমন কিছু হিসেবে বিবেচনা করা হয়েছে যা থাকলে ভালো হয়।
তাই, আপনি হয়ত এটি একবার কনফিগার করে ছেড়ে দেবেন অথবা আপনার হাতে সময় থাকলে এটি নিয়ে কাজ করবেন।
তবে, মোবাইল ইন্ডাস্ট্রি উল্লেখযোগ্য হার্ডওয়্যার সাপ্লাই সংক্রান্ত সীমাবদ্ধতা নেভিগেট করার সাথে সাথে ডিভাইসের মেমরির উপলভ্যতা পরিবর্তন হচ্ছে। তাই আপনার অ্যাপের জন্য একটি ভালভাবে অপ্টিমাইজ করা DEX থাকা অপরিহার্য, যাতে এটি কম মেমরি ব্যবহার করে, দ্রুত শুরু হয়, ANR কমায় এবং সামগ্রিকভাবে রেন্ডারিং এবং রান-টাইম পারফর্ম্যান্স উন্নত করে।
Android ইকোসিস্টেমকে সুস্থ রাখতে এবং আপনার অ্যাপ যাতে প্রিমিয়াম অভিজ্ঞতা প্রদান করা চালিয়ে যেতে পারে তা নিশ্চিত করতে, Google Play পারফর্ম্যান্স থ্রেশহোল্ড সহ নতুন অ্যাপ-কোয়ালিটি সংক্রান্ত প্রয়োজনীয়তা ঘোষণা করেছে যা ফেব্রুয়ারি থেকে এনফোর্স করা হবে।
এর মধ্যে DEX কোড অপ্টিমাইজেশনের থ্রেশহোল্ড অন্তর্ভুক্ত।
অ্যাপকে সঙ্কুচিত, অপ্টিমাইজ ও অস্পষ্ট করার জন্য ২৫%-এর বেশি অর্জন করতে হবে।
তাহলে, Android ডেভেলপার হিসেবে আপনার কাছে এর তাৎপর্য কী?
অপ্টিমাইজ না করা DEX কোড শুধু ব্যবহারকারীর অভিজ্ঞতার উপর নেতিবাচক প্রভাব ফেলে না, এটি Play Store-এ আপনার অ্যাপের দৃশ্যমানতা এবং প্রকাশ করার ক্ষমতার উপরও প্রভাব ফেলতে পারে।
তবে, আপনি সঠিক জায়গায় আছেন এবং এই আলোচনা শেষ হওয়ার পরে, আপনার কাছে এমন টুল থাকবে যা দিয়ে আপনি শুধু এইসব প্রয়োজনীয়তা পূরণই করবেন না, বরং তার থেকেও বেশি কিছু করতে পারবেন।
Android-এর অ্যাপ অপ্টিমাইজার R8-এর মাধ্যমে DEX কোড অপ্টিমাইজ করা সম্ভব।
অন্য কোনও অ্যাপ অপ্টিমাইজার আপনার অ্যাপকে এই থ্রেশহোল্ডের উপরে নিয়ে যেতে পারলে, R8 ব্যবহার করা বাধ্যতামূলক নয়। তবে, এটি Android-এর টুলচেনে সরাসরি বিল্ট-ইন করা আছে বলে এটি ব্যবহার করার জন্য সাজেস্ট করা হয়।
এবং ভালভাবে অপ্টিমাইজ করা DEX, অ্যাপ বান্ডেল কম মেমরি ব্যবহার করলেও, এখানে প্রাথমিক সুবিধাগুলি এখনও পারফর্ম্যান্স-সম্পর্কিত: কম ANR, দ্রুত স্টার্ট-আপ এবং উন্নত রান-টাইম পারফর্ম্যান্স।
আপনার অ্যাপ অপ্টিমাইজ করলে, আপনার অ্যাপ ও ব্যবহারকারীদের জন্য বাস্তব, প্রমাণিত ও ইতিবাচক ফলাফল পাওয়া যায়।
যেমন, R8 ফুল মোড গ্রহণ করে এবং তাদের রাখার নিয়মগুলি পরিমার্জন করে, Grab তাদের অ্যাপের জন্য ৭৮% অপ্টিমাইজেশন, ৭৯% সঙ্কুচিত এবং ৮২% অস্পষ্টতা আনলক করতে সক্ষম হয়েছে।
আপনি হয়ত এই স্লাইডটি দেখছেন এবং ভাবছেন, "ঠিক আছে, এই সংখ্যাগুলি বেশ বেশি, কিন্তু এর থেকে আসলে কী বোঝা যায়?"
এইসব উন্নতির ফলে, বেসলাইন প্রোফাইলের সাথে যুক্ত হয়ে, তারা ANR-এর সংখ্যা ৩২% কমিয়েছে এবং স্টার্ট-আপ ৩৫% উন্নত করেছে।
সেরা অ্যাপগুলি প্রথমবার R8 গ্রহণ করার সময়, DEX সাইজে গড়ে ৩৮% এবং ব্যবহারকারীর-লক্ষ্য করা ANR-এ ১৬% হ্রাস দেখতে পায়।
আপনি যদি আগে থেকেই R8 ব্যবহার করেন, তাহলে আপনার জন্য সুখবর আছে।
যেসব অ্যাপে আগে থেকেই R8 চালু করা আছে এবং তারপরে অপ্টিমাইজেশন, সঙ্কোচন ও অস্পষ্টকরণের লেভেল ৫০%-এর বেশি উন্নত করার জন্য পদক্ষেপ নেওয়া হয়, সেগুলির ক্ষেত্রে DEX সাইজ অতিরিক্ত ২৫% কমে, স্টার্ট-আপ টাইম ৪% কমে এবং ব্যবহারকারীর-লক্ষ্য করা ANR অতিরিক্ত ১৭% কমে, যা আগের স্লাইডে দেখানো ১৬%-এর অতিরিক্ত।
তাই, আজ আমরা আপনাকে দেখাতে চাই যে আপনি কীভাবে এই ধরনের সংখ্যা অর্জন করতে পারেন।
আমরা এমন টুল সম্পর্কে আলোচনা করব যা আপনার DEX কোড অপ্টিমাইজ করতে এবং খুব বেশি ব্রড ও ভুলভাবে কনফিগার করা নিয়ম থেকে নির্দিষ্ট ও সংকীর্ণ নিয়মে পরিবর্তন করতে সাহায্য করতে পারে।
প্রথমত, আমরা এটিও উল্লেখ করতে চাই যে R8 অনেক বছর ধরে রয়েছে।
আমরা স্বীকার করি যে এটি উচ্চ-প্রভাব অপ্টিমাইজেশন সক্ষম করলেও, এটি কনফিগার করার জন্য এখনও অনেক যত্ন নিতে হয়।
রিফ্লেক্টিভ কলের অর্থ হল ইনপুটগুলি অনুমান করা যায় না এবং এর ফলে রান-টাইম ক্র্যাশ হতে পারে।
এবং এটি অতীতে অ্যাপ টিম এবং লাইব্রেরি টিম উভয়কেই প্রলুব্ধ করেছে যাতে জিনিসগুলি নিরাপদে চালানোর জন্য অতিরিক্ত বিস্তৃত রাখার নিয়ম যোগ করা যায়।
আমরা জানি যে সেরা অ্যাপগুলির ৪১% Google Play-এর নির্ধারিত ২৫% অপ্টিমাইজেশন লেভেলের নিচে রয়েছে, তবে আমরা এটাও জানি যে সেরা অ্যাপগুলির ১৩% ইতিমধ্যেই সেই থ্রেশহোল্ডের তিন গুণ অর্জন করেছে।
এবং এটি বিভিন্ন ধরনের ইন্ডাস্ট্রি এবং ব্যবহারকারীর অভিজ্ঞতা জুড়ে থাকা অ্যাপ যা এই সংখ্যা অর্জন করতে সক্ষম।
আপনার অ্যাপের জন্য উচ্চ অপ্টিমাইজেশন স্কোর অর্জন করা সম্ভব, যতক্ষণ না আপনার কাছে সঠিক টুল এবং সঠিক দৃশ্যমানতা থাকে যা আপনার নিজস্ব রাখার নিয়ম এবং লাইব্রেরি থেকে উত্তরাধিকারসূত্রে প্রাপ্ত নিয়ম উভয়ই পরিচালনা করতে পারে।
এবং আমরা নতুন টুলিং এবং উন্নতির মধ্যে ডুব দেওয়ার আগে যা এতে সাহায্য করবে, আমরা গত বছর থেকে আরও কয়েকটি R8 উন্নতি হাইলাইট করতে চেয়েছিলাম যা এই অপ্টিমাইজেশনটিকে আরও কিছুটা সহজ করে তোলে।
প্রথমত, নতুন অ্যাপ টেমপ্লেটে প্যাকেজ স্কোপ অন্তর্ভুক্ত থাকে, যা শুরু থেকেই ন্যূনতম লেভেলের অপ্টিমাইজেশন নিশ্চিত করার জন্য উপযোগী।
R8 কনফিগারেশন অ্যানালাইজার, যার ব্যাপারে আমরা একটু পরেই কথা বলব, সেটি এখন AGP-এর পুরনো ভার্সনে কাজ করে।
এছাড়াও, Bazel-এর জন্য এটি আনঅফিসিয়ালি কাজ করে।
এবং AGP 9.2-এ, কোরুটিন প্রিমিটিভ দুই গুণ দ্রুত হয়েছে।
AGP 9.2-এ R8-এর উন্নতির ফলে, Tinder 9.0.1 ভার্সন থেকে আপগ্রেড করার পরে ANR-এ ১৩% হ্রাস লক্ষ্য করেছে।
এছাড়াও, AGP 9.3-এ আরও স্পষ্ট নামকরণ, ডিফল্ট নিয়ম ও রিসোর্স সরিয়ে দেওয়ার জন্য আরও সহজ ডিফল্ট এবং নিয়ম বজায় রাখার জন্য AGP সোর্স ডিরেক্টরি সাপোর্ট সহ নতুনভাবে ডিজাইন করা R8 API নিয়ে আসে।
android {
buildTypes {
release {
optimization {
// no need to specify default
// rules and resource shrinking
enabled = true
}
}
}
...
}android {
buildTypes {
release {
// Enables code optimizations.
isMinifyEnabled = true
// Enables resource shrinking.
isShrinkResources = true
proguardFiles(
// Default rules.
getDefaultProguardFile(
"proguard-android-optimize.txt"
),
... // list out each file with keep rules
)
...
}
}
...
}আজ আমরা যে আপডেটগুলি কভার করতে চলেছি সেগুলি ডেভেলপারদের অভিজ্ঞতাকেও উন্নত করবে।
আমরা প্রথমে দেখব কীভাবে R8 কনফিগারেশন অ্যানালাইজার এবং R8 স্কিল ব্যবহার করে লিগ্যাসি রুল ওভারহেড শনাক্ত করা যায় এবং উন্নতির পথ নির্ধারণ করা যায়।
তারপর, ক্রিস দেখাবেন কীভাবে ব্রড, ওয়াইল্ডকার্ড কিপ রুল থেকে নতুন নির্ভুল, টাইপ-সেফ, অটোমেটিক জেনারেট করা অ্যানোটেশনে ট্রানজিশন করতে হয়।
এবং সবশেষে, আমরা দেখব যে কীভাবে নতুন CLI যাচাইকরণ টুল ব্যবহার করে ক্র্যাশ না ঘটিয়েই অ্যাগ্রেসিভ অপ্টিমাইজেশন নিশ্চিত করা যায়।
তাই, R8 কনফিগারেশন অ্যানালাইজার দিয়ে শুরু করে, DEX অপ্টিমাইজেশন ইনভেস্টিগেশন শুরু করার জন্য এটিই আদর্শ জায়গা।
আপনি টুলটি ব্যবহার না করে থাকলে, এটি দেখতে এমন হয়।
এটি একটি HTML রিপোর্ট যা আপনার R8 কনফিগারেশনের তথ্য দেখায়।
এই রিপোর্টটি আমার অ্যাপের জন্য, যার মধ্যে Analytics Bloat নামে একটি লাইব্রেরি রয়েছে।
এবং আপনি যেমন অনুমান করতে পারেন, এটি কোনও আসল অ্যাপ বা আসল লাইব্রেরি নয়, তবে এটি উপরে নীল রঙে হাইলাইট করা হয়েছে।
Config Analyzer নিম্নলিখিত বিষয়গুলি পরিমাপ করে: সঙ্কুচিত করা, যা হল ক্লাস, পদ্ধতি এবং ফিল্ডের শতাংশ যা R8-কে কিপ রুল দ্বারা সঙ্কুচিত করা থেকে আটকানো হয় না।
এবং R8 তার প্রথম সঙ্কুচিত পাস করার পরে এটি গণনা করা হয়।
এটি অপ্টিমাইজেশনও পরিমাপ করে, যা হল কোডের শতাংশ যা মেথড ইনলাইনিং এবং ক্লাস মার্জিংয়ের মতো সম্পূর্ণ প্রোগ্রাম অপ্টিমাইজেশনের জন্য উন্মুক্ত।
এবং অস্পষ্টকরণ, যা হল মেম্বারদের শতকরা হার যাদের অস্পষ্ট করা যেতে পারে বা ছোট শনাক্তকারীতে নাম পরিবর্তন করা যেতে পারে।
আমি আরও উল্লেখ করতে চাই যে, R8 Config Analyzer চূড়ান্ত অপ্টিমাইজেশন এবং DEX কম্পাইলেশনের আগে এই স্কোরগুলি গণনা করে, তাই অপ্টিমাইজেশন, সঙ্কুচিত করা এবং অস্পষ্ট করার জন্য আপনি এই রিপোর্টে যে মানগুলি দেখছেন তা Play Console-এ দেখা মানের সাথে হুবহু মিলবে না।
আর এটি হওয়ার কারণ হল, Play চূড়ান্ত DEX আউটপুট পরিমাপ করে।
আর এই পার্থক্যের কারণ হল, আপনি যখন ডেভেলপ করছেন তখন যাতে দ্রুত, লোকাল ফিডব্যাক লুপ অ্যাক্সেস করতে পারেন তা নিশ্চিত করা।
তবে, R8 Config Analyzer-এ আপনি যে স্কোর দেখতে পান তা Play Store-এ আপলোড করার আগে DEX অপ্টিমাইজেশন সম্ভাবনা যাচাই করার জন্য এখনও সত্যিই সহায়ক।
রিপোর্টে 'নিয়ম বিশ্লেষণ রাখুন' টেবিলও অন্তর্ভুক্ত থাকে, যা আপনি রিপোর্টে প্রবেশ করার সময় প্রধান ভিউ হিসেবে দেখতে পান।
সেখান থেকে, আপনি যেকোনও নিয়মে ক্লিক করে দেখতে পারবেন যে নির্দিষ্ট নিয়মে ঠিক কতগুলি ক্লাস, পদ্ধতি ও ফিল্ড রাখা হয়েছে। এর ফলে, আপনি দেখতে পারবেন যে কোন নিয়মে প্রয়োজনের চেয়ে বেশি ক্লাস, পদ্ধতি ও ফিল্ড রাখা হয়েছে।
এতে সাবসিউম করা নিয়মও আছে, যা এখানে জুম-ইন করা হয়েছে।
এবং এগুলি হল এমন নিয়ম যা আরও বড় নিয়মের সাথে ওভারল্যাপ করে।
এবং এগুলি হাইলাইট করা হয়েছে যাতে আপনি ব্রড ম্যাচ সরিয়ে দিয়ে শুধু আরও ন্যারো ম্যাচ রেখে দিতে পারেন।
এছাড়াও, এটি আপনাকে অব্যবহৃত নিয়ম দেখায়, যেগুলি আপনার অ্যাপের স্থিতিশীলতার ঝুঁকি না নিয়েই মুছে ফেলা যেতে পারে।
তাই, কনফিগারেশন অ্যানালাইজারকে এমনভাবে ডিজাইন করা হয়েছে যাতে এটি লিগ্যাসি রুল অ্যাকুমুলেশন থেকে শুরু করে আরও কিছু সমস্যার সমাধান করতে পারে।
আগে, প্রতিটি নিয়মের দৃশ্যমানতা না থাকায়, পুরনো নিয়ম মুছে ফেলা ঝুঁকিপূর্ণ ছিল।
তাই, টিমগুলি তাদের একা ছেড়ে দেবে বা আরও বিস্তৃত ওয়াইল্ডকার্ড যোগ করবে।
দ্বিতীয়ত, এটি আপনাকে লাইব্রেরির নিয়মের অন্ধ দাগগুলি আবিষ্কার করতে সাহায্য করে।
তাই, আপনি যখন কোনও SDK যোগ করেন, তখন সেই লাইব্রেরির সাথে বান্ডেল করা কোনও নিয়ম খুব বেশি বিস্তৃত কিনা বা এটির আদৌ প্রয়োজন আছে কিনা তা বলা কঠিন, ধরা যাক, ১০ বা তার বেশি ভার্সন পরে।
এছাড়াও, আমরা লাইব্রেরি সম্পর্কে আরও তথ্য দেব।
তৃতীয়ত, এটি আপনাকে অপ্টিমাইজেশনের প্রভাব মূল্যায়ন করতে সাহায্য করে।
আগে, কোনও রুল দুটি ক্লাস নাকি ২০০০টি ক্লাস রাখছে তা সহজে বলা যেত না।
আমার অ্যাপ এবং আগে দেখা লাইব্রেরির প্রসঙ্গে R8 কীভাবে কাজ করে তা দেখা যাক।
সুতরাং, এখানে আমার একটি অ্যাপ মডিউল এবং তিনটি লাইব্রেরি রয়েছে।
এবং ডানদিকে লাইব্রেরি তিনের দিকে মনোযোগ দিন।
এটি হল Analytics Bloat লাইব্রেরি, যার মধ্যে প্যাকেজ-ওয়াইড 'রাখুন' নিয়ম অন্তর্ভুক্ত আছে।
প্রথমে, R8 সব মার্জ করা কিপ রুল থেকে রুট এন্ট্রি পয়েন্ট শনাক্ত করে, যা এখানে হলুদ রঙে হাইলাইট করা হয়েছে।
এবং সাধারণত, এগুলি অ্যাপের প্রধান এন্ট্রি পয়েন্ট হওয়া উচিত, যেমন ম্যানিফেস্টের ক্লাস।
এবং Analytics Bloat লাইব্রেরির ওয়াইল্ডকার্ড কিপ রুলের কারণে, লাইব্রেরির প্রতিটি ক্লাসকে এন্ট্রি পয়েন্ট হিসেবে বিবেচনা করা হয়।
এরপর, R8 সেই রুট থেকে কল গ্রাফ জুড়ে রিচেবিলিটি ট্রেস করে।
রুট থেকে অ্যাক্সেস করা যায় এমন যেকোনও ক্লাস রিটেনশনের জন্য সবুজ রঙে চিহ্নিত করা হয়, অন্যদিকে রেফারেন্স না থাকা ক্লাসগুলিকে ডেড কোড হিসেবে শ্রেণীবদ্ধ করা হয় এবং এখানে লাল রঙে চিহ্নিত করা হয়।
R8 তারপর ট্রি শেকিং পারফর্ম করে এবং রেফারেন্স না থাকা ডেড কোড সরিয়ে দেয়।
তাই, এই ট্রি শেকিং বা প্রথমবার সঙ্কুচিত করার পরে, এটিই অবশিষ্ট থাকে।
আমাদের অ্যাপের সঙ্কোচন স্কোর ৪২.৫%, যা Google Play-এর ২৫% থ্রেশহোল্ডের উপরে, তবে এটি এখনও আমরা যেখানে চাই সেখানে নেই।
এবং এটি মূলত অ্যানালিটিক্স লাইব্রেরি থেকে প্যাকেজ-ওয়াইড কিপ রুলের কারণে হয়।
আর সেই কারণে বাকি ৪০টি ক্লাসের মধ্যে মাত্র ১৭টি ক্লাস আরও ছোট করা যাবে।
আমি এখানে যে বিষয়টি বলতে চাইছি তা হল, একটি খারাপ কিপ রুল আপনার অ্যাপের অপ্টিমাইজেশনের ক্ষেত্রে বড় সমস্যা তৈরি করতে পারে।
তাই, এই বিষয়টি মাথায় রেখে, লাইব্রেরির উপর দ্রুত ফোকাস করা যাক।
গত বছর, আমাদের টিম লাইব্রেরি কনজিউমার কিপ রুল কীভাবে ইকোসিস্টেমের অপ্টিমাইজেশনে প্রভাব ফেলছে তা বোঝার জন্য অনেক বিনিয়োগ করেছে।
অনেক লাইব্রেরি স্থায়িত্বের নামে অতিরিক্ত বিস্তৃত নিয়ম সহ শিপ করবে।
অথবা, অ্যাপ ডেভেলপাররা যাতে নিজেরাই তাদের অ্যাপে যোগ করতে পারেন, তার জন্য তারা তাদের ডকুমেন্টেশনে বিস্তৃত নিয়মও উল্লেখ করবেন।
তবে, স্থায়িত্বের জন্য ব্রড রুল সহ শিপিং করার প্রয়োজন নেই এবং এটি পারফর্ম্যান্সের উপর নেতিবাচক প্রভাব ফেলে, যেখানে খরচ মূলত অ্যাপ ডেভেলপার হিসেবে আপনাকে বহন করতে হয়।
আমরা এমন কিছু ঘটনা দেখেছি যেখানে অ্যানালিটিক্স ব্লট লাইব্রেরির মতো প্যাকেজ-ওয়াইড কিপ রুল দ্বারা সম্পূর্ণ লাইব্রেরিটি সংরক্ষিত হয় এবং সেই লাইব্রেরির ক্ষেত্রে, এটি আমার অ্যাপের অপ্টিমাইজেশন সম্ভাবনার উপর সুদূরপ্রসারী প্রভাব ফেলেছিল।
আমি আরও উল্লেখ করতে চাই যে Google-এ আমাদের নিজস্ব SDK-ও এর থেকে রেহাই পায়নি এবং এক ডজনেরও বেশি Google লাইব্রেরি গত এক বছরে অত্যধিক বিস্তৃত নিয়ম ঠিক করেছে।
তাই আপনি যদি কনফিগারেশন অ্যানালাইজার ব্যবহার করতে চান, তাহলে এটি সরাসরি AGP 9.3-এ ইন্টিগ্রেট করা হয়েছে, তবে আপনি যদি নিজের R8 ভার্সন আপগ্রেড করেন, তাহলে এটি পুরনো ভার্সনের জন্যও উপলভ্য।
তাই এই কমান্ড রান করলে, আমরা যে HTML রিপোর্ট দেখিয়েছি সেটি জেনারেট হবে, যেখানে আপনার কিপ রুল, সঙ্কোচন, অপ্টিমাইজেশন, স্বতন্ত্র রুলের জন্য অবফাসকেশন এবং সামগ্রিকভাবে আপনার অ্যাপের উপর প্রভাব দেখানো হবে।
আপনি যদি সম্পূর্ণ R8 রিলিজ বিল্ড রান করেন, তাহলে রিপোর্টটি অটোমেটিক তৈরি হয় এবং এই ফাইল পাথে পাওয়া যায়।
এই স্ট্যান্ডঅ্যালোন টাস্কের সুবিধা হল যে এটি সম্পূর্ণ বিল্ডের চেয়ে তৈরি করতে কম সময় নেয়।
WHOOP-এর টিম, যারা হয়ত এখানে কোথাও আছেন, কনফিগারেশন অ্যানালাইজার রান করে দেখেছেন যে সঙ্কোচন ৭৪%, অস্পষ্টতা ৭৪% এবং অপ্টিমাইজেশন ০।
আর আপনি যেমনটা কল্পনা করতে পারেন, টিম এই অপ্টিমাইজেশন ভ্যালু দেখে সত্যিই অবাক হয়ে গিয়েছিল।
কনফিগারেশন অ্যানালাইজার থেকে পাওয়া ভিজিবিলিটি তাদের এটি বুঝতে সাহায্য করেছে যে তাদের বিল্ড একটি পুরনো ProGuard ফাইল ব্যবহার করছে যা অপ্টিমাইজেশন বন্ধ করে দিয়েছে। তাই, আপডেট করা ভার্সনে পরিবর্তন করে তারা নিজেদের অ্যাপের জন্য অপ্টিমাইজেশন চালু করতে পেরেছে।
এর ফলে ৫৫% অপ্টিমাইজেশন এবং APK সাইজে ৪% হ্রাস হয়েছে।
বেসলাইন প্রোফাইলের সাথে একত্রিত হয়ে, তারা কোল্ড স্টার্টের সময় ৩৮% উন্নত করেছে এবং অতিরিক্ত ফ্রোজেন ফ্রেম ২৮% কমিয়েছে।
বাঁদিকের স্ক্রিনে বেসলাইন প্রোফাইল নেই এবং ডানদিকের স্ক্রিনে আছে এবং আপনি দেখতে পাচ্ছেন যে বেসলাইন প্রোফাইল যোগ করার ফলে অ্যাপ আরও দ্রুত চালু হচ্ছে।
এটি আরও একবার চালাতে দিন।
ঠিক আছে।
এই লিঙ্কটি আপনাকে কনফিগারেশন বিশ্লেষকের গাইডে নিয়ে যাবে এবং ম্যানুয়ালি রিপোর্ট পর্যালোচনা করা দারুণ, তবে আপনি যেমন কল্পনা করতে পারেন, আপনার অ্যাপের R8 কনফিগারেশনের অবস্থার উপর নির্ভর করে এতে অনেক সময় লাগতে পারে।
তাই, কাজ দ্রুত করার জন্য আমাদের কাছে R8 Analyzer স্কিল আছে।
$ android skills list
$ android skills add r8-analyzerআপনি এজেন্টকে বলতে পারেন, আমার R8 কনফিগারেশন বিশ্লেষণ করো এবং স্কিলটি শুধু R8 কনফিগারেশন বিশ্লেষক রিপোর্ট তৈরি করে না, এটি আপনাকে সবচেয়ে প্রভাবশালী নিয়মের দিকেও নির্দেশ করে।
এখানে একটি আউটপুটের উদাহরণ দেওয়া হল এবং কিছু বিবরণ উল্লেখ করার জন্য, এটি ৪২% অপ্টিমাইজেশন স্কোর নোট করে, যার অর্থ হল কোডবেসের ৫২%-- ৫৮%, বরং, R8 দ্বারা অপ্টিমাইজ করা যাবে না।
এবং আমরা যদি বিশেষভাবে 'কিপ রুল' বিভাগটি দেখি, তাহলে অ্যানালিটিক্স ব্লট লাইব্রেরির জন্য প্যাকেজ-ওয়াইড 'কিপ রুল' কোডবেসের ২২% অপ্টিমাইজেশন থেকে বাধা দেয় এবং স্কিলটি আমাদের এই 'কিপ রুল' রিফাইন করার সাজেশনও দেয় কারণ, আমরা আগেই জানি, এটি খুব ব্রড।
তাই এখন আপনি R8 কনফিগারেশন অ্যানালাইজার এবং স্কিল ব্যবহার করে অ্যাপ ব্লট এবং DEX অপ্টিমাইজেশন ব্লক করার কারণ নির্ণয় করতে পারবেন।
কিন্তু আপনি একবার খুব বড় একটি রাখার নিয়ম খুঁজে পেলে, কীভাবে আপনি এটিকে নিরাপদে প্রতিস্থাপন করবেন যাতে কোনও অপ্রত্যাশিত ক্র্যাশ না ঘটে?
আমি ক্রিসকে হ্যান্ড অফ করতে যাচ্ছি, যিনি আপনাকে দেখাবেন কীভাবে পুরনো রাখার নিয়ম থেকে নতুন টাইপ-নিরাপত্তা অটোমেটিক জেনারেট করা রাখার অ্যানোটেশনে ট্রানজিশন করতে হয়।
ধন্যবাদ, Breana.
ঠিক আছে, তাহলে আমরা এখানে কীভাবে এলাম সেই বিষয়ে কথা বলা যাক, তাই তো?
এই স্ফীত অ্যানালিটিক্স লাইব্রেরির সাথে, এই সমস্ত রাখার নিয়মগুলির সাথে যা-- এই রাখার নিয়মের সাথে যা মূলত সবকিছু রাখছে, আমরা যে মূল সমস্যার সম্মুখীন হচ্ছি তা হল প্রতিফলন।
তাই, এখানে একটু মনে করিয়ে দেওয়ার জন্য রিফ্লেক্টিভ কোডের একটু অংশ দেখে নেওয়া যাক।
fun createWithReflection(className: String): MyObject {
return Class.forName(className)
.getDeclaredConstructor()
.newInstance() as MyObject
}এটি একটি অবজেক্ট ইনস্ট্যান্টিয়েশন ফাংশন যা শুধুমাত্র একটি নির্দিষ্ট অবজেক্টকে ইনফ্লেট করছে এবং রিফ্লেক্টিভভাবে তা করছে।
DEX অপ্টিমাইজেশনের ক্ষেত্রে এর নেতিবাচক দিক হল, রানটাইম পর্যন্ত এটি এইসব স্ট্যাটিক কলকে বিলম্বিত করে।
এই ক্লাসের নাম কোথা থেকে আসছে তার উপর নির্ভর করে, শুধু কোড দেখে আমাদের R8 অপ্টিমাইজার বুঝতে পারছে না যে এখানে কী ঘটছে।
এইসব পরিস্থিতিতে ডিফল্ট হিসেবে Dex অপ্টিমাইজেশন কাজ করে না, তাই সাধারণত আপনাকে Keep সংক্রান্ত নিয়ম যোগ করতে হবে।
এখন, আদর্শগতভাবে আপনি এটি এড়িয়ে যান, তাই তো?
যেমন, আদর্শগতভাবে আমরা রিফ্লেকশন সম্পূর্ণভাবে এড়িয়ে চলি, বিশেষ করে এখন ২০২৬ সালে যেখানে আমাদের কাছে সিরিয়ালাইজেশন, ডিপেন্ডেন্সি ইনজেকশনের জন্য অসাধারণ কোডজেন টুল আছে, যা এই ধরনের সমস্যা সম্পূর্ণভাবে দূর করতে পারে।
কিন্তু এটাই তো সব নয়, তাই না?
আমরা এখনও কিছু ক্ষেত্রে প্রতিফলন কিছুটা প্রয়োজনীয় পেয়েছি।
তাহলে, আসুন এই স্ফীত লাইব্রেরিতে ঠিক কীভাবে এটি ঘটেছে তা জেনে নেওয়া যাক।
সুতরাং, এখানে একটি নির্দিষ্ট উদাহরণ রয়েছে এবং এটি একটি সাধারণ উদাহরণ, এটি একটি ঐচ্ছিক নির্ভরতা।
// Problem 1 - optional dependency
fun tryInitCrashReporter(context: Context): EventTransport? {
return runCatching {
Class.forName("com.example.analytics.bloat.crash.CrashReporterTransport")
.getDeclaredConstructor(Context::class.java)
.newInstance(context) as? EventTransport
}.getOrNull()
}
// Problem 2 - dynamically instantiate subclass
fun createEventFilter(className: String): EventFilter {
return Class.forName(className)
.getDeclaredConstructor()
.newInstance() as? EventFilter
}
// Problem 3 - serialization
fun serializeEvent(event: UserActionEvent): JSONObject {
val json = JSONObject()
for (field in event.javaClass.declaredFields) {
field.isAccessible = true
json.put(field.name, field.get(event))
}
return json
}এখানে, আমরা একটি নির্দিষ্ট ধরন ইনফ্লেট করছি যা লাইব্রেরি ক্লাসপাথে অ্যাক্সেসযোগ্য নয়, তাই আমাদের এটি একটি স্ট্রিং দিয়ে করতে হবে।
আমরা এটি এই নির্দিষ্ট ক্লাসকে রেফারেন্স করছে যা লাইব্রেরি তৈরি করার সময় উপস্থিত থাকতে পারে বা নাও থাকতে পারে, তাই খুব যুক্তিসঙ্গতভাবে এই লাইব্রেরিটি একটি সম্পর্কিত লাইব্রেরির মধ্যে একজন মেম্বারকে খুঁজে পেতে রিফ্লেকশন ব্যবহার করছে।
আমাদের কাছে আরেকটি কেস আছে যা ডাইনামিক্যালি সাবক্লাস ইনস্ট্যান্টিয়েট করে।
তাই, বিশেষ করে যদি এটি কোনও ম্যানিফেস্ট ফাইল থেকে আসে বা সার্ভার-সাইড থেকে পাঠানো হয়, তাহলে আমরা স্থানীয় অ্যাপকে জানাই, "আরে, অনুগ্রহ করে এই জিনিসটি ইনফ-ইনফ্লেট করুন, এই জিনিসটি পরে তৈরি করুন।"
এবং তারপরে, এই তৃতীয় বিভাগটি যা আমরা এখানে দেখছি তা হল লজ্জাজনক সিরিয়ালাইজেশন।
I mean, I have a JSON serialization that's happening by iterating through all the members.
এখন, এটি একেবারেই বিরক্তিকর, তবে, আপনি জানেন, এই ধরনের জিনিস কখনও কখনও থেকে যায়।
যখনই প্রয়োজন তখনই যে ঠিক হয়ে যায় তা নয়।
তবে, এর ফলাফল কী হতে পারে তা নিয়ে আলোচনা করা যাক, তাই তো?
এই রিফ্লেক্টিভ কলগুলি জমা হওয়ার সাথে সাথে, আপনি সহজ কাজটি করার জন্য আরও বেশি অনুপ্রেরণা পান এবং আপনার ব্যবহারকারীর অভিজ্ঞতার উপর ডেভেলপারের অভিজ্ঞতা অপ্টিমাইজ করেন।
মানে, এটা তো খুবই খারাপ, তাই আমি একটি টু-ডু যোগ করব এবং এটি ঠিক হয়ে যাবে, আপনি জানেন।
# TODO: clean this up
-keep com.example.analytics.bloat.** { * };কোনও কোনও একদিন, আমি আমি আসব এবং এটি ঠিক করব, তাই না?
তাহলে, আমাদের কী করা উচিত, অন্তত আজকের আগে আমাদের কী করা উচিত ছিল, আপনি কীভাবে এখানে একটি আদর্শ Keep রুল লিখবেন, সেই সম্পর্কে কথা বলা যাক।
fun createEventFilter(className: String): EventFilter {
return Class.forName(className)
.getDeclaredConstructor()
.newInstance() as? EventFilter
}ঠিক আছে, সিটবেল্ট বেঁধে নিন।
সুতরাং, এখানে প্রথম অংশটি হল যে আমরা এই রিফ্লেক্টিভ ফাংশন থেকে যে নির্দিষ্ট ক্লাসটি রিটার্ন করছি সেটি রাখতে চাই।
### Keep the EventFilter
-keep class com.example.analytics.bloat.EventFilter {
*
}না, দাঁড়াও।
তুমি কি জানো?
এটি আসলে সঠিক নয়।
This is a Kotlin class, I need to make sure this is stringly-typed, let me get the KT in there.
### Keep the actual EventFilter
-keep class com.example.analytics.bloat.EventFilterKt {
*
}And in fact, the important thing here is that I'm not actually returning an EventFilter, I'm actually returning a subclass of EventFilter, so I need to keep classes that extend EventFilter in order to be able to inflate this correctly.
### Keep the EventFilter subclasses
-keep class ** extends com.example.analytics.bloat.EventFilterKt {
*
}এবং সত্যিই, আমি যদি এটি আরও দক্ষতার সাথে করতে চাই এখন, এটি কাজ করবে, তবে এটিকে আরও দক্ষ করে তুলতে, একজন ভাল লাইব্রেরি ডেভেলপার হিসাবে, আমার আরও কী করা উচিত?
Well, I should say -keepclasseswithmembers, and only keep those classes that have this particular <init> constructor that I'm using to inflate the object.
### Keep the EventFilter subclasses, if they have this constructor
-keepclasseswithmembers class ** extends com.example.analytics.bloat.EventFilterKt {
void <init>();
}And really, if I want to be really nice, I'll add allowaccessmodification to allow even further optimization to happen on all the APIs that are being targeted by this Keep rule because I, you know, don't want to prevent further optimization from R8, I want to try and be a good citizen.
### Keep the EventFilter subclasses, if they have this constructor, allow more optimization
-keepclasseswithmembers,allowaccessmodification class ** extends com.example.analytics.bloat.EventFilterKt {
void <init>();
}এবং তারপরে, শেষ ধাপটি, যেমন, প্রায় কেউই করে না, কারণ এটি একটি অতিরিক্ত কাজ, তবে এটি একটি লাইব্রেরির জন্য দুর্দান্ত, এটি শর্তসাপেক্ষ করে তোলে।
### -if makes this conditional, critical for libraries
-if class com.example.analytics.bloat.EventToolsKt {
class com.example.analytics.bloat.EventFilterKt createEventFilter(java.lang.String);
}
### Keep the EventFilter subclasses, if they have this constructor, allow more optimization
-keepclasseswithmembers,allowaccessmodification class ** extends com.example.analytics.bloat.EventFilterKt {
void <init>();
}এখন, ধারণাগতভাবে, আপনি যদি এই ফাংশনে কল না করেন, তাহলে আপনাকে সেই সব সাবক্লাস রাখতে হবে না এবং Keep নিয়মের উপরে এই if স্টেটমেন্টটি এটিই করে।
কিন্তু, আপনি জানেন, আমি হাত তুলতে বলতে পারি, কিন্তু আমি জানি প্রায় কেউই এটি করে না, তাই না?
যেমন, আসলে এটি লেখা, এটিকে শর্তসাপেক্ষ করা।
তাহলে, আমরা যদি এটিকে, আপনি জানেন, একটু ভালো করতে পারতাম?
Let's Let's first look at see, like, okay.
আচ্ছা, হয়ত এটি করার অন্য কোনও উপায় আছে।
তাহলে, আমরা এই বিষয়ে কথা বলেছি, যেমন, আমি যদি একটি সংকীর্ণ, ম্যানুয়ালি লেখা Keep নিয়ম লিখি তাহলে কী হবে?
Well, it's possible technically, but it's hard to write, it's hard to read, and it's very difficult to maintain because there're lots of little stringly-typed bits in there that are easy to break when something underneath it happens.
Underneath something underneath it changes.
এবং আমাদের কাছে বিকল্প আছে।
আমাদের কাছে @Keep-এর মতো অ্যানোটেশন আছে।
এখন, @Keep অনেক দিন ধরে AndroidX অ্যানোটেশন প্যাকেজে রয়েছে, তবে আজকাল এটি আসলে এক ধরণের নিরুৎসাহিত।
@androidx.annotation.Keep
class EventFilter { … }
@androidx.annotation.Keep
class SizeBasedEventFilter { … }
@androidx.annotation.Keep
class DropLogsEventFilter { … }লাইব্রেরি ডেভেলপারদের জন্য এটি খুব একটা ভালো সমাধান নয় এবং আসলে এটি ইন্টার্নালি এক ধরনের নিষিদ্ধ, কারণ এটি বজায় রাখা সহজ হলেও, এটি খুব অনমনীয় এবং এটিতে এমন কিছু অপ্টিমাইজেশন নেই যা আমি আগে বলছিলাম।
এটি এক ধরনের 'পারবে না'।
এটি সেই কল সাইট বিশ্লেষণের অনুমতি দেয় না।
এখন, আমরা যদি আগে থেকে থাকা সেই নির্দিষ্ট কেসটি দেখি, "ওহ, আচ্ছা, আমি শুধু আমার EventFilter-এ @Keep রাখতে পারি, আমি ঠিক আছি, তাই না?"
@Keep
class EventFilter { … }না, তা নয়।
@Keep আসলে সাবক্লাসকে টার্গেট করে না, এটি শুধু সেই ক্লাসকে টার্গেট করে।
@Keep
class EventFilter { … }
@Keep
class SizeBasedEventFilter { … }
@Keep
class DropLogsEventFilter { … }তাই, আসলে আমাকে সব সাবক্লাস টার্গেট করতে হবে এবং এখন আমি আসলে আরও বেশি @Keep অ্যানোটেশন লিখছি যা আমি সেই একটি Keep রুল দিয়ে লিখতাম যা লেখা কঠিন ছিল।
তাহলে, এই দুটিতেই সমস্যা আছে, তাই তো?
এগুলি ভুলে যাওয়া সহজ, আপনি এগুলি সম্পর্কে দেরিতে জানতে পারেন এবং ডিফল্ট হিসেবে এগুলি কলারের উপর শর্তাধীন নয়।
বিশেষ করে লাইব্রেরির জন্য, আমরা আরও ভালো উপায় চাই।
আমরা চাই যে, কোনও কিছুকে একদম ঠিকঠাকভাবে পাওয়ার আরও সহজ উপায় থাকুক।
এবং আমরা এখানে যে বড় সমস্যাগুলির মধ্যে একটি দেখতে পাচ্ছি তা হল এই জিনিসগুলি আমরা আসলে যে প্রতিফলন থেকে রক্ষা করছি তার থেকে অনেক দূরে বসবাস করছে।
শেষ পর্যন্ত, আমরা এমন একটি সমাধান চাই যা, আপনি জানেন, এখানে দুষ্টু প্রতিফলন কোডের বিরুদ্ধে কাজ করে যা আসলে টার্গেট করা জিনিসগুলিকে বাড়িয়ে তুলছে।
তাই, Jetpack Keep সম্পর্কে কথা বলা শুরু করা যাক, AndroidX-এর নতুন লাইব্রেরি ও প্লাগ-ইন যা ঠিক এই সমস্যার সমাধান করার জন্য ডিজাইন করা হয়েছে।
তাই, ব্যবহার করার জন্য-- Jetpack Keep-এর মূল লক্ষ্য হল, প্রথমে অ্যানোটেশন থেকে কিপ রুল তৈরি করা, তবে তার থেকেও বেশি, আপনার কোডবেসে আগে থেকেই থাকা রিফ্লেকশন থেকে সরাসরি অ্যানোটেশন তৈরি করা।
তাই, সেট-আপ করা খুবই সহজ।
plugins {
id("com.android.library")
id("androidx.annotation.keep")
}
dependencies {
implementation("androidx.annotation.keep:keep")
...
}এটি একটি Gradle প্লাগ-ইন, এটি একটি অ্যানোটেশন লাইব্রেরি।
তবে, এটি কীভাবে আসলে রান করে-- বিল্ড টাইমে রান করে তা দেখা যাক।
সুতরাং, আমার কাছে যদি এই পূর্ববর্তী উদাহরণ থাকে যেখানে আমি একটি পরিচিত প্রকারের সাবক্লাস ইনফ্লেট করছি, তাহলে আমাকে কী করতে হবে?
আমি শুধু lintFix রান করি।
./gradlew analyticsbloat:lintFixLint এখানে এই পদ্ধতির মধ্যে ঘটছে এমন সমস্ত প্রতিফলন বিশ্লেষণ করে এবং এটি বলে, "আরে, আপনার এই অ্যানোটেশন প্রয়োজন।"
@UsesReflectionToConstruct(
classConstant = EventFilter::class,
parameterTypes = []
)
fun createEventFilter(className: String): EventFilter {
return Class.forName(className)
.getDeclaredConstructor()
.newInstance() as? EventFilter
}এই অ্যানোটেশনটি এই কলের মধ্যে থাকা প্রতিফলন ঠিক কী করছে তা প্রকাশ করে।
এটি বলে, "আমি এই ধরনের একটি ক্লাসকে স্ফীত করছি।"
এই নির্দিষ্ট ক্ষেত্রে, EventFilter হল একটি পরিচিত ক্লাস।
আমি এটি রেফারেন্স হিসেবে ব্যবহার করার জন্য স্ট্রিং ব্যবহার করছি না কারণ আমাকে তা করতে হবে না।
এটি অতিরিক্ত নিরাপত্তা এবং এটি-- আমি এমনকি parameterTypes এনকোড করছি যাতে এটি ঠিক কোন কনস্ট্রাক্টর রাখা হচ্ছে তা বলে।
কিন্তু এটি কী করে?
যেমন, এটি কীভাবে একটি চূড়ান্ত R8 বিল্ডকে জানায়?
এটি রাখার নিয়ম তৈরি করে এবং আপনি যদি আগে দেখে থাকেন, তাহলে জানবেন যে এটি ঠিক সেই আদর্শ রাখার নিয়ম যা আমাদের আগে ছিল।
# Generated Keep Rule
-if class com.example.analytics.bloat.EventTools {
class com.example.analytics.bloat.EventFilter createEventFilter(java.lang.String);
}
-keepclasseswithmembers,allowaccessmodification class ** extends class com.example.analytics.bloat.EventFilter {
void <init>();
}এটি ঠিক একই ক্লাস, সাবক্লাসকে টার্গেট করছে এবং এই প্রতিফলন পয়েন্টের উপর শর্তাধীন।
আর আমাকে-- সব সাবক্লাস অ্যানোটেট করার ব্যাপারে চিন্তা করতে হয় না।
আমাকে অন্য কোথাও আলাদা করে রাখার নিয়ম ফাইল রাখতে হবে না।
এটি আপনার রাখার নিয়ম ম্যানেজ করার অনেক সহজ উপায়।
আসুন, ঐচ্ছিক ডিপেন্ডেন্সি সংক্রান্ত উদাহরণটি আবার দেখা যাক।
@UsesReflectionToConstruct(
className = "com.example.analytics.bloat.crash.CrashReporterTransport",
parameterTypes = [Context::class]
)
fun tryInitCrashReporter(context: Context): EventTransport? {
return runCatching {
Class.forName("com.example.analytics.bloat.crash.CrashReporterTransport")
.getDeclaredConstructor(Context::class.java)
.newInstance(context) as? EventTransport
}.getOrNull()
}এখন, সেখানে, ক্লাসটি ক্লাস পাথে ছিল না, তবে আমি এখনও এটি রেফারেন্স করতে পারি।
আমি শুধু একটি স্ট্রিং ব্যবহার করে এটি করি, তবে অন্য সবকিছু, প্যারামিটারের ধরন, ক্লাস রেফারেন্সের মাধ্যমে রেফার করা যেতে পারে, তাই আমি করি।
এবং আবার, আমরা এই ক্ষেত্রে আদর্শ রাখার নিয়ম তৈরি করি।
# Generated Keep Rule
-if class com.example.analytics.bloat.CrashConnection {
class com.example.analytics.bloat.EventTransport tryInitCrashReporter(android.content.Context);
}
-keepclasseswithmembers,allowaccessmodification class com.example.analytics.bloat.crash.CrashReporterTransport {
void <init>(android.content.Context);
}এটি শর্তসাপেক্ষ, নির্দিষ্ট এবং টার্গেট করা এবং এটি টার্গেট করা ক্লাসে যেখানেই সম্ভব অপ্টিমাইজেশন চালু করে।
এখানে ফিল্ডের উপর রিফ্লেকশন আছে এবং এই নির্দিষ্ট ক্ষেত্রে, আমি একটি অবজেক্টের মধ্যে সমস্ত ফিল্ডের উপর ইটারেট করছিলাম, কিন্তু এখানে, আবার, আমরা এই স্টার অপারেটরের সাথে সমস্ত ফিল্ড টার্গেট করে এটি ঠিকভাবে ম্যানেজ করি।
// TODO: migrate to Kotlinx Serialization or Moshi codegen.
@UsesReflectionToAccessField(
classConstant = UserActionEvent::class,
fieldName = "*"
)
fun serializeEvent(event: UserActionEvent): JSONObject {
val json = JSONObject()
for (field in event.javaClass.declaredFields) {
field.isAccessible = true
json.put(field.name, field.get(event))
}
return json
}এটি আমাকে সব ফিল্ড টার্গেট করতে দেয় এবং দেখুন, এটি ঠিক সেই কিপ রুল তৈরি করে যা আপনি এর জন্য আশা করবেন, আপনি জানেন, অবিশ্বাস্যভাবে দুর্ভাগ্যজনক প্রতিফলন যা আমার অ্যাপে থাকা উচিত নয়, তবে, আরে, আমি ফিরে আসব এবং এটি পরিষ্কার করব, আমি সম্পূর্ণ প্রতিশ্রুতি দিচ্ছি।
# Generated Keep Rule
-if class com.example.analytics.bloat.serialization.EventSerializer {
class org.json.JSONObject serializeEvent(com.example.analytics.bloat.model.UserActionEvent);
}
-keepclasseswithmembers,allowaccessmodification class com.example.analytics.bloat.model.UserActionEvent {
<fields>;
}তাহলে, Jetpack Keep কী করে?
এটি নিয়ম রক্ষণাবেক্ষণের কাজকে অনেক কমিয়ে দেয়।
আপনি অটোমেটিক যা কিছু জেনারেট করতে পারেন, তার জন্য এটি আপনার উদ্বেগের ক্ষেত্র এবং নিয়ম মেনে চলার জন্য ঝুঁকির ক্ষেত্র থেকে এটিকে সরিয়ে দেয়।
এটি আপনাকে লিন্টের ডি-- ডিটারমিনিজমও দেয়।
আপনি যখন কোড ইটারেট ও মাইগ্রেট করার চেষ্টা করছেন, তখন এটি শুধু আপনার জন্য সমস্যার সমাধান করছে না, বরং নতুন রিফ্লেকশন এবং কোড পরিবর্তন করার সময় রিফ্লেক্টিভ পরিবর্তনও ক্যাচ করছে এবং এই নিরাপত্তা অত্যন্ত গুরুত্বপূর্ণ।
আমি কি এটি ব্যবহার করতে পারি?
তাহলে, এখানে ডিপেনডেন্সিগুলি কী কী?
প্লাগ-ইন ও লিন্টের জন্য AGP 8.7 প্রয়োজন।
তাহলে প্রায় সবাইকেই কভার করা যাবে।
তবে, আপনি এটি কোথায় ব্যবহার করতে পারবেন সেই ব্যাপারেও এটি খুব নমনীয়।
আপনি এটি অ্যাপের মধ্যে ব্যবহার করতে পারবেন।
আপনি এটি Android লাইব্রেরি, AAR, KMP লাইব্রেরি এবং JAR-এর মধ্যে ব্যবহার করতে পারবেন-- সেক্ষেত্রে একটু ম্যানুয়াল ওয়্যারিং করতে হবে।
তবে, এর মূল ফিচার হল, লাইব্রেরি ডেভেলপার হিসেবে এটি ব্যবহার করা নিরাপদ।
এমনকি কেউ যদি R8-এর একটি প্রাচীন ভার্সন বা এমনকি একটি থার্ড-পার্টি টুল ব্যবহার করে যা আপনার লাইব্রেরি ব্যবহার করছে, তবুও আমরা প্রকৃত স্ট্যান্ডার্ড কনজিউমার রুল এক্সপোর্ট করি যাতে যে কেউ এখনও আপনাকে ব্যবহার করতে পারে এবং নতুন কোনও বেমানান ফর্ম্যাট ব্যবহার করার মতো চিন্তা করতে না হয়।
তাহলে, এখন আমি Jetpack Keep-এর ব্যাপারে কথা বলেছি যা আপনাকে অপ্টিমাইজ করতে এবং আপনার রিফ্লেক্টিভ কোড কল সুরক্ষিত করার ব্যাপারে কম চিন্তা করতে সাহায্য করে, আসুন আমরা নতুন ফিচারগুলির ব্যাপারে কথা বলি যা আমরা সম্প্রতি Android CLI-তে লঞ্চ করেছি, যা শেষ রিলিজ স্টেজে ডবল চেক করার মতো।
Breana আগেই উল্লেখ করেছেন যে Play Findings-এ এমন টুল আছে যা আপনার সাইজ, DEX সাইজ ও অপ্টিমাইজেশন শতাংশের ব্যাপারে তথ্য দেবে।
এখন আমরা Android CLI-এর মাধ্যমেও এটি উপলভ্য করার কাজ শুরু করেছি।
এটি Play Store-এর মতো একই লজিক ব্যবহার করে, তাই Play Store-এ বান্ডেল আপলোড না করেই, আপনি CLI-তে আপনার বান্ডেল বিশ্লেষণ করতে পারবেন।
$ android analyze <mybundle.aab>
$ android analyze <mybundle.apk>এবং আপনি সরাসরি এবং অবিলম্বে স্থানীয় বিশ্লেষণের সাথে আপনাকে রিপোর্ট করা ঠিক একই তথ্য পাবেন।
আপনি এখানে এই নির্দিষ্ট ক্ষেত্রে দেখতে পাচ্ছেন যে, এই অ্যাপটি খুব কম অপ্টিমাইজেশন রেট থাকার বিষয়ে একটি সতর্কতা পাচ্ছে এবং এটিও বলা হচ্ছে, "আরে, এটি আপনার DEX সাইজ এবং এখানে আপনার অপ্টিমাইজেশন শতাংশ রয়েছে," ঠিক যেমন Play Store আপনাকে বলবে।
তবে, এখানে একটি JSON আউটপুটও আছে, যাতে আপনি যদি কোনও স্ক্রিপ্ট লিখতে চান, যাতে এই ভ্যালুগুলির মধ্যে কোনও একটির মান হঠাৎ কমে গেলে রিগ্রেশন টেস্টিং অটোমেট করা যায়, কারণ, আবার, এগুলি হল সেই সংখ্যা যা Play এনফোর্স করে, আপনি সেগুলির ট্র্যাক রাখতে চান, আপনি এই JSON ভ্যালু-- এই JSON রিপোর্টের মাধ্যমে তা খুব সহজেই করতে পারবেন।
{
"optimization": {
"bundleAnalysisResult": {
"agpVersion": "9.3.2",
"r8Json": {
"version": "9.3.16",
"options": {
"isRepackageClassesEnabled": true,
"isProguardCompatibilityModeEnabled": false
},
"stats": {
"shrinkingRatio": 0.2306,
"optimizationRatio": 0.2351,
"obfuscationRatio": 0.2322
},
...
}
"mappingObfuscationRatio": 0.21631891,
"dexHeuristicObfuscationRatio": 0.25118626,
"uncompressedDexSizeBytes": "16198152"
}এখানে আমরা দেখতে পাচ্ছি যে ঠিক একই নম্বরও রয়েছে।
এখন এটি android analyze-এর একমাত্র ফিচার নয়।
android analyze লাইব্রেরিতেও চলতে পারে।
$ android analyze <library.aar>
$ android analyze <library.jar>আর Breana যেমন বলেছেন, লাইব্রেরি চালানোর ক্ষেত্রে অনেক সমস্যা হয়, কারণ সেগুলির মধ্যে দুর্ভাগ্যজনকভাবে 'কিপ রুল' থাকে।
তাই, আসুন এই টুল ব্যবহার করে এই ধরনের কিছু সমস্যা খতিয়ে দেখি।
তাই, আমার কাছে থাকা কোনও নির্দিষ্ট AAR-এ এটি রান করলে, আমি দেখতে পাব যে, এই লাইব্রেরিতে বেশ কিছু সমস্যা আছে যা একদম উপরে হাইলাইট করা হয়েছে।
এই লাইব্রেরির কিপ রেশিও বেশি, এর অর্থ হল এটি সীমাহীন কিপ রুল সহ অনেক বেশি রাখছে।
এতে এমন কিছু ব্রড কিপ রুল আছে যা এটিকে অপ্টিমাইজ হতে দেয় না এবং নিচে কিছু সংখ্যা আছে যা আপনাকে ঠিক কতটা খারাপ তা বলে দেয় এবং এতে এমন কিছু কিপ রুলও আছে যা, সম্ভবত এই সবের মধ্যে সবচেয়ে ভীতিকর, কোনও কিছুকে টার্গেট করছে না, যার অর্থ সম্ভবত তারা অন্য কাউকে কোনও ধরনের সীমাহীন উপায়ে টার্গেট করছে।
খুবই বিপজ্জনক কোড স্মেল।
তাই, আপনি AAR-এ এই সব চেক করতে পারবেন, বিশেষ করে লাইব্রেরি প্রকাশক হিসেবে, এটি নিশ্চিত করার একটি দুর্দান্ত উপায় যে আপনি খারাপ কিপ রুল শিপিং করছেন না এবং আপনি সেগুলি লিক হতে দিচ্ছেন না যখন কেউ হয়ত কিপ রুল লেখার ক্ষেত্রে ততটা দক্ষ নয়, লাইব্রেরিতে কাজ করছে।
এখানে, আপনি দেখতে পাবেন যে আমরা একটি কিপ রেশিও আউটপুট করি।
আপনার লাইব্রেরির সামগ্রিক কোয়ালিটি সম্পর্কে চিন্তা করার এটি একটি ভালো উপায়।
If I just optimize a individual library completely standalone, no references, with R8, I should see that being zero bytes, right?
যেমন, এটি মূলত খালি থাকা উচিত কারণ এর ভিতরে কিছুই রাখা নেই।
সেটি আদর্শ হবে।
এই ক্ষেত্রে, এটি নয়।
আপনি সরাসরি R8 এবং D8 সাইজ দেখতে পারবেন।
এবং আসলে, আমরা কিছু সবচেয়ে খারাপ রাখার নিয়মও হাইলাইট করি যাতে সেগুলি আপনার কাছে আরও একটু বেশি দৃশ্যমান হয়।
এটিরও JSON ফর্ম্যাট আছে, তাই আপনি যদি লাইব্রেরি আপডেট গেট করার চেষ্টা করা অ্যাপ ডেভেলপার হন বা আপনার লাইব্রেরির কোয়ালিটিতে বড় রিগ্রেশন প্রতিরোধ করার চেষ্টা করা লাইব্রেরি ডেভেলপার হন, তাহলে আপনি একই বিশ্লেষণ করতে পারেন এবং Android CLI র্যাপ করে এমন একটি সাধারণ স্ক্রিপ্টের মাধ্যমে এই সমস্যাগুলি প্রতিরোধ করতে পারেন।
{
"optimization": {
"libraryAnalysisResult": {
"dex": {
"d8CompileResult": {
"dexSizeBytes": 4523352
},
"r8CompileResult": {
"dexSizeBytes": 3266596,
"configuration": {
"usedPackageWideKeepRules": [
{
"source": "-keep class com.example.analytics.bloat.** { *; }",
"keptItemCount": 125579,
},
...
],
"unusedPackageWideKeepRules": [
{
"source": "-keep class some.other.library.** { *; }",
},
...
]
},
...
}তাই, আপনি যদি কোনও লাইব্রেরির সমস্যা খুঁজে পান, তাহলে প্রথমে সেটি আপডেট করার চেষ্টা করুন।
As Breana mentioned earlier, we've fixed more than a dozen Google libraries to fix some very unfortunate keep rules and there other libraries out there that have had fixes, they just aren't getting updated by the apps that use them.
এটি একটি দুর্দান্ত প্রথম পদক্ষেপ।
দ্বিতীয়ত, আপনি কোনও সমস্যা খুঁজে বের করে লাইব্রেরি ডেভেলপারের সাথে যোগাযোগ করতে পারেন এবং সম্ভবত একটি অ্যানালাইজার রিপোর্ট অ্যাটাচ করে তাদের বলতে পারেন, "আপনি অনেক বেশি ডেটা রাখছেন।
আমার অ্যাপ অপ্টিমাইজ করতে সাহায্য করুন।"
সুতরাং, এখানে এক ধরনের রিক্যাপ করতে, আমরা R8 কনফিগারেশন অ্যানালাইজার সম্পর্কে কথা বলেছি, যা আপনাকে আপনার R8 বিল্ডে যা ঘটছে তা দেখতে সাহায্য করে, সবকিছু যা অপ্টিমাইজ করা যায় এবং কী নয় তা জানায়।
আমরা Jetpack Keep সম্পর্কে কথা বলেছি, এটি হল নতুন লাইব্রেরি যা আপনাকে কোনও পরিশ্রম ছাড়াই অটোমেটিক Keep রুল লিখতে সাহায্য করে।
এবং Android CLI, নতুন টুল যা আপনাকে চেক করতে এবং নিশ্চিত করতে সাহায্য করে, শেষ ধাপের সাথে ডবল চেক করে এবং নিশ্চিত করে যে আপনি, আপনার এজেন্ট, আপনার টিমের জুনিয়র ইঞ্জিনিয়ার আপনার গ্রাহকদের জন্য কিছু নষ্ট করেননি।
তাই, আপনি আরও জানতে চাইলে, R8, কনফিগারেশন অ্যানালাইজার এবং Jetpack Keep-এর জন্য ডকুমেন্টেশন আছে।
এছাড়াও, Android CLI-এর ডকুমেন্টেশনে বিশ্লেষণ কমান্ড এবং এটি কীভাবে ব্যবহার করতে হয় সেই সম্পর্কে বলা আছে।
আপনাদের ধন্যবাদ জানাই।
android {
buildTypes {
release {
optimization {
// no need to specify default
// rules and resource shrinking
enabled = true
}
}
}
...
}android {
buildTypes {
release {
// Enables code optimizations.
isMinifyEnabled = true
// Enables resource shrinking.
isShrinkResources = true
proguardFiles(
// Default rules.
getDefaultProguardFile(
"proguard-android-optimize.txt"
),
... // list out each file with keep rules
)
...
}
}
...
}$ android skills list
$ android skills add r8-analyzerfun createWithReflection(className: String): MyObject {
return Class.forName(className)
.getDeclaredConstructor()
.newInstance() as MyObject
}// Problem 1 - optional dependency
fun tryInitCrashReporter(context: Context): EventTransport? {
return runCatching {
Class.forName("com.example.analytics.bloat.crash.CrashReporterTransport")
.getDeclaredConstructor(Context::class.java)
.newInstance(context) as? EventTransport
}.getOrNull()
}
// Problem 2 - dynamically instantiate subclass
fun createEventFilter(className: String): EventFilter {
return Class.forName(className)
.getDeclaredConstructor()
.newInstance() as? EventFilter
}
// Problem 3 - serialization
fun serializeEvent(event: UserActionEvent): JSONObject {
val json = JSONObject()
for (field in event.javaClass.declaredFields) {
field.isAccessible = true
json.put(field.name, field.get(event))
}
return json
}# TODO: clean this up
-keep com.example.analytics.bloat.** { * };fun createEventFilter(className: String): EventFilter {
return Class.forName(className)
.getDeclaredConstructor()
.newInstance() as? EventFilter
}### Keep the EventFilter
-keep class com.example.analytics.bloat.EventFilter {
*
}### Keep the actual EventFilter
-keep class com.example.analytics.bloat.EventFilterKt {
*
}### Keep the EventFilter subclasses
-keep class ** extends com.example.analytics.bloat.EventFilterKt {
*
}### Keep the EventFilter subclasses, if they have this constructor
-keepclasseswithmembers class ** extends com.example.analytics.bloat.EventFilterKt {
void <init>();
}### Keep the EventFilter subclasses, if they have this constructor, allow more optimization
-keepclasseswithmembers,allowaccessmodification class ** extends com.example.analytics.bloat.EventFilterKt {
void <init>();
}### -if makes this conditional, critical for libraries
-if class com.example.analytics.bloat.EventToolsKt {
class com.example.analytics.bloat.EventFilterKt createEventFilter(java.lang.String);
}
### Keep the EventFilter subclasses, if they have this constructor, allow more optimization
-keepclasseswithmembers,allowaccessmodification class ** extends com.example.analytics.bloat.EventFilterKt {
void <init>();
}@androidx.annotation.Keep
class EventFilter { … }
@androidx.annotation.Keep
class SizeBasedEventFilter { … }
@androidx.annotation.Keep
class DropLogsEventFilter { … }@Keep
class EventFilter { … }@Keep
class EventFilter { … }
@Keep
class SizeBasedEventFilter { … }
@Keep
class DropLogsEventFilter { … }plugins {
id("com.android.library")
id("androidx.annotation.keep")
}
dependencies {
implementation("androidx.annotation.keep:keep")
...
}./gradlew analyticsbloat:lintFix@UsesReflectionToConstruct(
classConstant = EventFilter::class,
parameterTypes = []
)
fun createEventFilter(className: String): EventFilter {
return Class.forName(className)
.getDeclaredConstructor()
.newInstance() as? EventFilter
}# Generated Keep Rule
-if class com.example.analytics.bloat.EventTools {
class com.example.analytics.bloat.EventFilter createEventFilter(java.lang.String);
}
-keepclasseswithmembers,allowaccessmodification class ** extends class com.example.analytics.bloat.EventFilter {
void <init>();
}@UsesReflectionToConstruct(
className = "com.example.analytics.bloat.crash.CrashReporterTransport",
parameterTypes = [Context::class]
)
fun tryInitCrashReporter(context: Context): EventTransport? {
return runCatching {
Class.forName("com.example.analytics.bloat.crash.CrashReporterTransport")
.getDeclaredConstructor(Context::class.java)
.newInstance(context) as? EventTransport
}.getOrNull()
}# Generated Keep Rule
-if class com.example.analytics.bloat.CrashConnection {
class com.example.analytics.bloat.EventTransport tryInitCrashReporter(android.content.Context);
}
-keepclasseswithmembers,allowaccessmodification class com.example.analytics.bloat.crash.CrashReporterTransport {
void <init>(android.content.Context);
}// TODO: migrate to Kotlinx Serialization or Moshi codegen.
@UsesReflectionToAccessField(
classConstant = UserActionEvent::class,
fieldName = "*"
)
fun serializeEvent(event: UserActionEvent): JSONObject {
val json = JSONObject()
for (field in event.javaClass.declaredFields) {
field.isAccessible = true
json.put(field.name, field.get(event))
}
return json
}# Generated Keep Rule
-if class com.example.analytics.bloat.serialization.EventSerializer {
class org.json.JSONObject serializeEvent(com.example.analytics.bloat.model.UserActionEvent);
}
-keepclasseswithmembers,allowaccessmodification class com.example.analytics.bloat.model.UserActionEvent {
<fields>;
}$ android analyze <mybundle.aab>
$ android analyze <mybundle.apk>{
"optimization": {
"bundleAnalysisResult": {
"agpVersion": "9.3.2",
"r8Json": {
"version": "9.3.16",
"options": {
"isRepackageClassesEnabled": true,
"isProguardCompatibilityModeEnabled": false
},
"stats": {
"shrinkingRatio": 0.2306,
"optimizationRatio": 0.2351,
"obfuscationRatio": 0.2322
},
...
}
"mappingObfuscationRatio": 0.21631891,
"dexHeuristicObfuscationRatio": 0.25118626,
"uncompressedDexSizeBytes": "16198152"
}$ android analyze <library.aar>
$ android analyze <library.jar>{
"optimization": {
"libraryAnalysisResult": {
"dex": {
"d8CompileResult": {
"dexSizeBytes": 4523352
},
"r8CompileResult": {
"dexSizeBytes": 3266596,
"configuration": {
"usedPackageWideKeepRules": [
{
"source": "-keep class com.example.analytics.bloat.** { *; }",
"keptItemCount": 125579,
},
...
],
"unusedPackageWideKeepRules": [
{
"source": "-keep class some.other.library.** { *; }",
},
...
]
},
...
}