Post updated on 12th October, 2024 at 08:59 pm
Android App development শুরু করার পর আমরা খুব আশ্চর্য হয়ে লক্ষ্য করি gradle নামক বস্তুটার কাজ-কারবার। ২০১৫ সালের দিকে যখন অ্যান্ড্রয়েড শুরু করি তখন দিনের মধ্যে কয়েক ঘন্টা সময় কাটতো “gradle build running…” এই লেখাটার দিকে তাকিয়ে থেকে।
আজকের এই পোস্ট এ আলোচনা করব অ্যান্ড্রয়েড APK ফাইলের জীবন চক্র। অর্থাৎ জাভা বা কটলিন থেকে, UI xml আর রিসোর্স সহ কিভাবে একটা APK file build হয় Android Studio-তে। আরো জানবো এই APK file generate করতে gradle কী কাজ করে? জানতে পারব সবুজ রঙের তিনকোণা Run button এ ক্লিক করলে আসলে কী ম্যাজিক ঘটে? Gradle সম্পর্কে কিছু খুঁটিনাটি তথ্য জানা যাবে। আরো দেখতে পাব কমান্ড লাইন থেকে কিভাবে প্রোজেক্ট বিল্ড করা যায়। আর সবশেষে অতি শর্টকাটে দেখব কিভাবে Android Gradle build time কমিয়ে প্রায় অর্ধেকে নিয়ে আসা যায়। চলুন শুরু করা যাক…
আপনাকে যদি প্রশ্ন করা হয় APK file কিভাবে বিল্ড হয়? আপনি হয়ত আমার মত বলে বসবেন “Android Studio বানায় দেয়। It’s magic!”
Android Studio এর আসলে কোনো ধারণাই নাই কিভাবে জাভা বা কটলিন কোড থেকে একটা APK ফাইল জেনারেট করা যায়…
“…ও না না… আমরা প্রোজেক্ট বিল্ড/রান দেয়ার সময় তো দেখি “Gradle build running”! তার মানে গ্র্যাডলই এই গুরু দায়িত্বটা পালন করে! কী এবার তো ঠিক বলেছি?” – অ্যাঁ… ন্নাহ… ঠিক বলেন নাই!
Gradle tool-ও জানে না জাভা বা কটলিন কোড কিভাবে compile করে তা APK এর অভ্যন্তরে প্রবেশ করানো যায়!
অ্যাঁ… gradle ওস্তাদের নামে এত বড় কথা? তাহলে রান দেয়ার পর ঘন্টার পর ঘন্টা এরা করেটা কী?
জ্বি, আমরা একে একে সব কিছু নিয়েই কথা বলব। শুধু খানিকটা ধৈর্য ধরে এগিয়ে যান। আপাতত দেখে নেয়া যাক “APK এর জীবন চক্র”।
APK file এর জীবন চক্র – APK file কিভাবে জেনারেট হয়?
আমরা ছোটবেলায় হয়ত পড়েছি মৌমাছি বা অন্যান্য পতঙ্গের জীবন চক্র। মৌমাছির জীবন চক্রে জেনেছি প্রথমে ডিম থাকে। ডিম থেকে লার্ভায় পরিণত হয়। লার্ভা থেকে শুককীট ও পরে শুককীট থেকে পূর্ণাঙ্গ মৌমাছিতে পরিণত হয়। এখন আমরা দেখব আমাদের সাধনার APK ফাইল কিভাবে কোন কোন স্টেপ পার হয়ে এসে Android device এ ইন্সটল করে রান করার উপযোগি হয়।
উপরের ছবিতে দেখানো হয়েছে একটা অ্যান্ড্রয়েড অ্যাপ কিভাবে বিল্ড করা হয়। নিচে স্টেপগুলোর অল্প বিস্তর আলোচনা করা হলো।
Create Java class file from Java source file
APK build করার জন্য এটা প্রথম স্টেপ। প্রথমে জাভা সোর্স ফাইলগুলোকে জাভা ক্লাস ফাইলে কনভার্ট করা হয়। এই কাজটা করে জাভা কম্পাইলার। এক্ষেত্রে আমাদের প্রোজেক্টে অ্যাড করা সকল Activity, Fragment সহ অন্যান্য সকল জাভা রিসোর্স ফাইলগুলো জাভা ক্লাস ফাইলে কনভার্ট করা হয়। একই সাথে আমাদের প্রোজেক্টে যত থার্ড পার্টি লাইব্রেরি, dependency, module অ্যাড করি সেগুলোর জাভা রিসোর্সগুলোও জাভা ক্লাসে কম্পাইল করে জাভা কম্পাইলার।
Create .DEX file from Java class file
Java class file তৈরি হয়ে যাবার পর Android SDK এর dx নামের একটা tool ঐ ক্লাস ফাইলগুলোকে .dex ফাইলে কনভার্ট করে। DEX এর পূর্ণ রূপ হচ্ছে Dalvik Executable (মানে Dalvik virtual machine এ এক্সিকিউট বা রান করা যায় এমন একটা ফাইল)।
আমরা জানি যে, আমাদের পিসিতে একটা জাভা দিয়ে ডেভেলপ করা সফটওয়্যার বা গেম ইন্সটল করে রান করলে সেটা পিসিতে থাকা ভার্চুয়াল ম্যাশিনে রান হয়। সেই ভার্চুয়াল ম্যাশিনের নাম হচ্ছে JVM বা Java Virtual Machine. উইন্ডোজ, লিনাক্স, ম্যাক সব প্ল্যাটফর্মের জন্য এরকম আলাদা আলাদা JVM রয়েছে। তাই সকল প্ল্যাটফর্মে যদি JVM install করা থাকে তাহলে জাভা দিয়ে ডেভেলপ করা যে কোনো সফটওয়্যারই সবগুলো অপারেটিং সিসটেমে চলবে। ইউন্ডোজ, লিনাক্স, ম্যাকের জন্য জাভা সফটওয়্যার বা গেমটার আলাদা আলাদা ভার্সন ডেভেলপ বা বিল্ড করার দরকার হবে না।
- Dependency Injection with Hilt এর উপর আমার একটি কোর্স রয়েছে। কোর্সটিতে এনরোল হতে ভিজিট করুন
তাহলে আমাদের অ্যান্ড্রয়েড ফোনেও কি JVM এর মধ্যে অ্যাপ রান হয়? আমাদের অ্যাপগুলোও তো আমরা জাভা দিয়ে ডেভেলপ করি। উত্তর হচ্ছে “না”। অ্যান্ড্রয়েড ফোনে JVM নয় বরং DVM – Dalvik Virtual Machine এর মধ্যে App রান হয়। JVM এর তুলনায় DVM অনেক বেশি অপটিমাইজড আর মোবাইল ডিভাইস ফ্রেন্ডলি। এই DVM এর ভিতরে যেন আমাদের অ্যাপটা এক্সিকিউট করা যায় সেজন্য dx টুলের মাধ্যমে জাভা ক্লাস ফাইলগুলোকে .dex ফাইলে কনভার্ট করা হয়। এই কনভার্সনের সময় বেশ কিছু অপটিমাইজেশনের কাজ করে এই dx tool. যেমন redundant বিভিন্ন ডেটাকে রিমুভ করে। ধরা যায় আমাদের অ্যাপের দশ জায়গায় আমরা “SAVE” স্ট্রিংটা ইউজ করেছি। DEX conversion এর সময় দশটা আলাদা স্ট্রিংয়ের বদলে একটা স্ট্রিং এ “SAVE” কথাটা রেখে বাকি জায়গাগুলোতে শুধু এর রেফারেন্সটা ব্যবহার করা হবে। এরকম আরো অনেক অপটিমাইজেশনের ফলে দেখা যায় .dex file size তার class file size এর তুলনায় অনেকটাই কমে গেছে।
Packaging .dex and resources into .apk file
Android SDK এর একটা টুল হচ্ছে AAPT – Android Asset Packaging Tool. এই aapt tool এর কাজ হচ্ছে ক্লাস ফাইল থেকে জেনারেট হওয়া optimized .dex ফাইল ও প্রোজেক্ট ব্যবহৃত resource file (যেমন image, icon, audio, video ইত্যাদি) এবং XML file-গুলোকে একটা .apk file এর মধ্যে প্যাকেট করা।
ব্যস! তৈরি হয়ে গেল .apk file! তাহলে কি এই ফাইলটা এখন ডিভাইসে ইন্সটল করে রান করা যাবে? জ্বি না! এখনো একটা গুরুত্বপূর্ণ কাজ বাকি। উপরের ছবিতে দেখা যাচ্ছে App signing এর একটা স্টেপ আছে। এই APK file টা ইন্সটল করার যোগ্য হবে একে debug বা release keystore দিয়ে সাইন করার পরে। এই APK packager-ই এই সাইন করার কাজটা করে থাকে। আমরা যখন debug apk build করার জন্য রান করি তখন এই টুল একটা ডিফল্ট keystore দিয়ে অ্যাপকে সাইন করে দেয়। তাই প্রতিবার ডিবাগ বিল্ড করার সময় কী স্টোর দিয়ে সাইন করার দরকার হয় না, কিন্তু রিলিজ বিল্ড করার সময় ম্যানুয়াল্যি আমাদেরকে কী স্টোর এর পাথ বলে দিতে হয়। APK packager তখন সেই পাথে থাকা কী স্টোর দিয়ে অ্যাপ সাইন করে।
Optimize .apk by zipalign tool
ফাইনাল্যি APK জেনারেট করার আগে APK packager module টা zipalign নামক একটা tool ব্যবহার করে APK file-টা কে আরো অপটিমাইজ করে যেন এটি ডিভাইসে কম মেমরি ইউজ করে।
এর মাধ্যমে সফল ভাবে একটা debug বা release APK ফাইল তৈরি হয়। যা আমরা ফোন বা ইমুলেটরে ইন্সটল করে রান করতে পারি বা ইউজারদের মধ্যে ডিসট্রিবিউট করতে পারি।
এই কাজগুলো যতটা সহজে লিখে দিলাম ততটা সহজে হয় না। আরো অনেক অনেক প্রসেস, সাব-প্রসেসের সমন্বয়ে এই কাজগুলো একটার পর একটা হয়। এই যে একটার পর একটা স্টেপ পার হয়ে সব শেষে একটা executable APK ফাইল পাওয়া যায় এই প্রসেসগুলো কিভাবে মেইনটেইন হয়? পোস্টের শুরুতে বলে আসা দুইটা quote এর কথা মনে আছে নিশ্চয়ই? Android Studio বা Gradle কোনোটাই আসলে কোড কম্পাইল বা প্যাকেজিং করে না। তাহলে গ্র্যাডল কী করে? সে কী করে সেটা জানার আগে চলুন জেনে নেয়া যাক গ্র্যাডল জিনিসটা আসলে কী?
Gradle আসলে কী জিনিস?
Gradle হচ্ছে একটা general purpose build tool. যা শুধু অ্যান্ড্রয়েড ডেভেলপমেন্টের মধ্যে সীমাবদ্ধ নয়। আমরা অনেকেই হয়ত গ্র্যাডলের নাম শোনার সৌভাগ্য হয়েছে অ্যান্ড্রয়েড ডেভেলপমেন্টের সূত্র ধরে। কিন্তু এর মাধ্যমে প্রায় যে কোনো সফটওয়্যার ডেভেলপমেন্টের বিল্ড প্রসেসকে অটোমেট করা যায়। উল্লেখ্য যে, গ্র্যাডল হচ্ছে JVM based একটা open source বিল্ড সিসটেম।
গ্র্যাডলের গিটহাব রিপোজিটরি থেকে জানা যায়ঃ
Gradle is a build tool with a focus on build automation and support for multi-language development. If you are building, testing, publishing, and deploying software on any platform, Gradle offers a flexible model that can support the entire development lifecycle from compiling and packaging code to publishing web sites. Gradle has been designed to support build automation across multiple languages and platforms including Java, Scala, Android, C/C++, and Groovy, and is closely integrated with development tools and continuous integration servers including Eclipse, IntelliJ, and Jenkins.
আমরা হয়ত টুকটাক সবাই-ই কমবেশি ওয়েব ডেভেলপমেন্টের জন্য PHP এর সাথে পরিচিত। এরকম অ্যান্ড্রয়েড ডেভেলপার হয়ত খুঁজে পাওয়া দুষ্কর হবে যিনি কখনো PHP এর স্বাদ আস্বাদন করেন নাই। তাই PHP এর উদাহরণ দিই।
ধরা যাক আমরা আমাদের লোকাল পিসির কোনো একটা ফোল্ডারে ২-১ টা HTML, PHP ফাইল ক্রিয়েট করে কিছু কোড লিখলাম। এখন আমরা যদি এগুলোকে লোকালহোস্টে রান করতে চাই তাহলে কী করতে হবে? যদি উইন্ডোজ হয় তাহলে আমাদেরকে এই ফাইলগুলো htdocs folder এর ভিতরে আরেকটা ফোল্ডার করে কপি-পেস্ট করতে হবে। লোকাল সার্ভারে এই ছোট্ট পিএইচপি প্রোজেক্ট ডেপ্লয় করার জন্য কিন্তু এটুকুই যথেষ্ট। তাহলে এই সফটওয়্যারের বিল্ড বা ডেপ্লয়মেন্টের জন্য আমরা গ্র্যাডল ইউজ করতে পারি। আমরা গ্র্যাডলের স্ক্রিপ্টে বলে দিব অমুক লোকেশন থেকে ফাইলগুলো কপি করে htdocs এ নিয়ে পেস্ট করতে। তাহলেই কিন্তু মোটামুটি ডেপ্লয়মেন্টের জন্য গ্র্যাডল ইউজ করা হলো। হয়ত উদাহরণটা খুব একটা জুতসই হলো না কিন্তু সিমপ্লি বুঝার জন্য হয়ত যথেষ্ট হবে।
একই ভাবে কোনো ডট নেট বা জাভা প্রোজেক্ট বিল্ড করার জন্য কি কি কাজ করা লাগবে বিল্ড হয়ে গেলে সেটার আউটপুট কোথায় রাইট করতে হবে, আউটপুট ফাইলের নাম কী হবে ইত্যাদি বিষয়গুলো গ্র্যাডল দিয়ে হ্যান্ডেল করা যায়।
তাই গ্র্যাডলের পরিচয়ে বলা হয়েছে এটা একটা জেনারেল পারপাস বিল্ড টুল। বিভিন্ন ল্যাঙ্গুয়েজের সফটওয়্যার প্রোজেক্টের build automation এর জন্য ঐ ল্যাঙ্গুয়েজের plugin গ্র্যাডলে ইউজ করে build process-কে অটোমেট করা যায়। ব্যাপারটা অনেকটা এরকম যে, আমরা যেমন অ্যান্ড্রয়েডে বিভিন্ন থার্ড পার্টি লাইব্রেরি অ্যাড করে আমাদের প্রোজেক্টের ক্যাপাবিলিটি extend করি, একই রকম ভাবে গ্র্যাডলে বিভিন্ন ল্যাঙ্গুয়েজের জন্য ডিফাইন করা প্লাগিন অ্যাড করে গ্র্যাডলের ক্যাপাবিলিটি এক্সটেন্ড করা হয়। একেক ল্যাঙ্গুয়েজের প্লাগিনে হয়ত একেক ধরণের টাস্ক ডিফাইন করা থাকবে। প্রোগ্রামিং ল্যাঙ্গুয়েজের ধরণ বা প্রকৃতির উপর এটা ভিন্ন ভিন্ন হবে।
Android plugin for Gradle – The ultimate hero
Android এর জন্য যেই gradle plugin-টা রয়েছে সেটিই মূলত প্রোজেক্ট বিল্ড করে APK জেনারেটের মত কাজগুলো করার ব্যবস্থা করে। এই প্লাগিনটি gradle কে জাভা কোড কম্পাইল, অ্যাপ সাইন করা ইত্যাদি কাজগুলো করার জন্য যেই সক্ষমতা দরকার সেগুলো প্রদান করে। এই প্লাগিনটাই আসলে পুরো বিল্ড সিসটেমটা হ্যান্ডেল করে। কোন কাজের পর কোন কাজ করতে হবে, জাভা ফাইল কখন কম্পাইল করতে হবে, সেটাকে dx tool দিয়ে কখন .dex এ কনভার্ট করতে হবে, কখন সেটাকে AAPT দিয়ে প্যাকেজিং করতে হবে ইত্যাদি কর্মযজ্ঞ সব কিছু এই প্লাগিনের নখদর্পণে রয়েছে।
এই প্লাগিনটা ছাড়া gradle এর পক্ষে আসলে জানা সম্ভব না আমাদের কোডগুলো কোথা থেকে কিভাবে নিয়ে কিভাবে কম্পাইল করে সেটাকে .apk এর ভিতর ঢুকাতে হবে। এজন্যেই এই পোস্টের শুরুতে বলেছিলাম যে gradle আসলে জানে না একটা সিঙ্গেল জাভা বা কটলিন কোড কিভাবে কম্পাইল করতে হয়। আর Android Studio সে নিজেও জানে না কিভাবে একটা APK ফাইল জেনারেট করতে হয়। এই প্লাগিনটাই আসলে Android Studio ও Gradle build system এর মাঝে যোগসূত্র স্থাপন করেছে। অ্যান্ড্রয়েড স্টুডিও থেকে যখন আমরা প্রোজেক্ট রান করি তখন এই প্লাগিনের সাহায্যেই গ্র্যাডল একটার পর একটা কাজ করে সফটওয়্যারটা বিল্ড করে। আর শুধু অ্যান্ড্রয়েড নয়, অন্যান্য ল্যাঙ্গুয়েজ বা সফটওয়্যার ডেভেলপমেন্টের জন্যও এরকম বিশেষায়িত gradle plugin রয়েছে যার মাধ্যমে গ্র্যাডল মোটামুটি সব ধরণের সফটওয়্যার ডেভেলপমেন্ট প্রসেসের বিল্ড অটোমেশন করতে পারে। এজন্যই কিন্তু গ্র্যাডলকে general purpose build automation system বলা হয়।
Understanding Gradle scripts of Android Studio
আমাদের এতক্ষণে নিশ্চয়ই জানা হয়ে গেছে যে একটা অ্যান্ড্রয়েড প্রোজেক্ট কিভাবে কিভাবে কোন স্টেপের পর কোন স্টেপের মাধ্যমে একটা APK জেনারেট করে। আমরা আরো জেনেছি gradle আসলে generally কী কাজ করে, আর Android Studio এর সাথে এর যোগসূত্র কী। উপরে মূলত তাত্ত্বীক আলোচনা হয়েছে এগুলোর উপর। এখন দেখব আমাদের অ্যান্ড্রয়েড স্টুডিওতে থাকা গ্র্যাডলের বিভিন্ন স্ক্রিপ্টগুলো আসলে কোনটা কী mean করে। এই স্ক্রিপ্টগুলোর বেশির ভাগই প্রোজেক্ট ওপেন করার সময় অটোমেটিক্যাল্যি ক্রিয়েট হয়ে যায়। হাতে ধরে এগুলোর খুব বেশি চেঞ্জ আমরা সাধারণত করি না। শুধু থার্ড পার্টি লাইব্রেরি অ্যাড করার সময় ডিপেন্ডেন্সি অ্যাড করি। এখন আমরা গ্র্যাডলের বিভিন্ন স্ক্রিপ্টের বিভিন্ন পার্ট নিয়ে সচিত্র আলোচনা করব।
উপরের ছবিতে যেই ফাইলগুলো দেখা যাচ্ছে এই সবগুলোর সমন্বয়ে গ্র্যাডল আমাদের অ্যান্ড্রয়েড প্রোজেক্ট বিল্ড করার কাজটা করে থাকে। যেই সাতটা ফাইল রয়েছে এগুলো প্রতিটিই আলাদা আলাদা purpose serve করে। সবগুলো মিলে গ্র্যাডলের কনফিগারেশনটা সম্পন্ন হয়।
Gradle wrapper
gradle-wrapper.properties ফাইলটার মূল কাজ হচ্ছে প্রোজেক্টটা বিল্ড করার জন্য gradle এর কোন ভার্সনটা আমরা ইউজ করতে চাই সেটা উল্লেখ করা। এখানে গ্র্যাডলের যেই ভার্সনটা মেনশন করা হবে সেটা অটোমেটিক্যাল্যি ডাউনলোড হয়ে যাবে এবং প্রোজেক্টে ঠিকঠাক মত ইউজও হবে। আপনি যদি লিনাক্স বা ম্যাক ইউজার হন তাহলে ls ~/.gradle/wrapper/dists/ এই কমান্ডটা টারমিনালে রান করলে এ পর্যন্ত গ্র্যাডলের যে কয়টা ভার্সন আপনার পিসিতে ডাউনলোড করা হয়েছে সেগুলোর লিস্ট দেখতে পারবেন।
এখানে গ্র্যাডলের 5.1.1 ভার্সনটি ব্যবহার করা হয়েছে। Android studio দিয়ে কোনো প্রোজেক্ট ওপেন করলে সেটার ডিফল্ট গ্র্যাডল ভার্সন ভিন্ন হতে পারে। পরে যে কোনো সময় আপনি গ্র্যাডলের লেটেস্ট ভার্সন এখানে দিয়ে দিতে পারেন। বা দরকার অনুযায়ী অ্যান্ড্রয়েড স্টুডিও নিজেই আপনাকে এই ভার্সন আপডেট করার জন্য pop up dialog দেখাতে পারে।
settings.gradle
এই ফাইলে আমরা আমাদের প্রোজেক্টে কী কী সাব-প্রোজেক্ট বা মডিউল ইউজ করতে চাই সেটা লিখে দিতে হয়। কোনো নতুন প্রোজেক্ট ওপেন করলে দেখতে পাবেন এই ফাইলে শুধু include : ‘:app’ কমান্ডটা লেখা আছে। এর অর্থ হচ্ছে আমরা আমাদের প্রোজেক্টে ডাউনলোড হওয়া গ্র্যাডলকে বলছি যে আমাদের প্রোজেক্টে শুধু app নামের একটা মডিউল আছে। যদি অন্য কোনো মডিউল আমরা ম্যানুয়াল্যি প্রোজেক্টে অ্যাড করি তাহলে অ্যান্ড্রয়েড স্টুডিও অটোমেটিক্যাল্যি এই ফাইলে সেই মডিউলের নাম include করে দিবে।
build.gradle
Gradle আমাদের অ্যান্ড্রয়েড প্রজেক্টকে কনসিডার করে একটা multi-project build হিসাবে। যেখানে একটা root প্রোজেক্ট থাকবে আর এক বা একাধিক sub-project থাকবে। Android development এর দৃষ্টিতে এই সাব-প্রোজেক্টগুলোকে আমরা modules বলে থাকি। এজন্যেই আমাদের যে কোনো অ্যান্ড্রয়েড প্রোজেক্টে দুইটা build.gradle ফাইল থাকে। একটা ফাইল থাকে আমাদের root project এর জন্য। আর অপরটি থাকে app modules এর জন্য। এই অ্যাপ মডিউলটিই আসলে আমাদের মূল প্রোজেক্ট। অ্যান্ড্রয়েড স্টুডিও দিয়ে একটা অ্যান্ড্রয়েডের প্রোজেক্ট ওপেন করলে এই app মডিউল তৈরি হয়ে যায়। প্রথমে দেখে নেয়া যাক root project এর build.gradle ফাইল বা প্রায় সময় যাকে আমরা project level gradle file বলে থাকি।
ছবিতে কয়েকটি কোড ব্লককে নাম্বারিং করা হয়েছে। নিচে সেই ব্লকগুলোর কোনটা কী বুঝাচ্ছে তা তুলে ধরা হলো।
- এই পুরো buildscript{} ব্লকটা গ্র্যাডলের নিজের জন্যই ব্যবহৃত হয়। এর মাধ্যমে গ্র্যাডলকে জানানো হচ্ছে যে এই প্রোজেক্টটা কম্পাইল করার জন্য কী কী জিনিস প্রয়োজন
- এখানে আমরা gradle এর জন্য Android plugin টা মেনশন করেছি। এই অ্যান্ড্রয়েড প্লাগিনই অ্যান্ড্রয়েড স্টুডিও ও গ্র্যাডল বিল্ড সিসটেমের মধ্যে সংযোগ ঘটাবে। যাকে আমরা ইতিপূর্বে The ultimate hero বলে উল্লেখ করেছি। শেষের 3.0.0 বলতে এই প্লাগিনের ভার্সনকে বুঝাচ্ছে
- আমরা gradle কে বলে দিচ্ছি আমাদের কোনো লাইব্রেরি বা মডিউল দরকার হলে কোথায় খুঁজতে হবে। এক্ষেত্রে
google() Maven repository ও
jcenter() repository এর উল্লেখ করা হয়েছে - Gradle project এ এটা একটা extra property. এর মাধ্যমে গ্লোবাল্যি আমরা একটা ভ্যালু স্টোর করে রাখছি যেন অন্যান্য জায়গা থেকে এই একই ভ্যালুটা অ্যাক্সেস করা যায়। এটা মূলত কটলিনের গ্র্যাডল প্লাগিনের ভার্সনটা উল্লেখ করা হয়েছে জাস্ট একটা উদাহরণ হিসাবে। আমরা কাজ করতে গেলে দেখব অ্যাপ লেভেল গ্র্যাডল ফাইলে কটলিনের কয়েকটা ডিপেনডেন্সি অ্যাড করার দরকার হতে পারে। প্রতিটা ডিপেন্ডেন্সি বা লাইব্রেরির নাম লেখার শেষে ভার্সন নাম্বার বলে দিতে হয়। আমরা সেক্ষেত্রে প্রতিটা ডিপেন্ডেন্সির জন্য হার্ড কোডেড ভার্সন নেম না দিয়ে এই kotlin_version ভ্যারিয়েবলটা ইউজ করতে পারি। যখন ভার্সন আপডেট করতে চাইব তখন শুধু এই চার নাম্বার মার্কিং করা স্থানে এসে আপডেট করব। তাহলে পুরো প্রোজেক্টে যত জায়গায় এই ভ্যারিয়েবল ইউজ করা হয়েছে সব জায়গাতেই ভার্সন নাম্বার আপডেট হয়ে যাবে
- allprojects{} ব্লক দিয়ে গ্র্যাডলকে বুঝিয়ে দিচ্ছি যে, সকল সাব-প্রোজেক্টগুলো কম্পাইল করার জন্য যদি কোনো ডিপেন্ডেন্সি বা লাইব্রেরি-মডিউল দরকার হয় তাহলে সেগুলো এই set of repositories এর মাধ্যমে কম্পাইল করতে হবে
এখন দেখে নেয়া যাক app level build.gradle ফাইলের কোন অংশ দিয়ে কী বুঝায়।
- এখানে আসলে আমরা অ্যান্ড্রয়েড, কটলিন ও কটলিনের এক্সটেনশন প্লাগিনগুলো apply করেছি। কটলিনের এক্সটেনশন প্লাগিন অ্যাপ্লাই করার মাধ্যমে আমাদের প্রোজেক্টে কটলিনের বাড়তি কিছু ফিচার পাওয়া যেতে পারে
- android{} নামের যেই ব্লকটা দেখা যাচ্ছে সেটা কিন্তু মূল গ্র্যাডলের অংশ না। এই ব্লকটা কাজ করছে শুধুমাত্র আমরা উপরে অ্যান্ড্রয়েড প্লাগিন অ্যাপ্লাই করে এসেছি সে কারণে। এজন্যই আগে বলেছিলাম গ্র্যাডলের ফাংশনালিটি এক্সটেন্ড করা সম্ভব হয় এর সমৃদ্ধ প্লাগিনগুলোর জন্য। গ্র্যাডল টুলটা যথেষ্ট ফ্লেক্সিবল। তাই দরকার অনুযায়ী প্লাগিন অ্যাড করে নিলেই এর মাধ্যমে সকল ধরণের কাস্টমাইজড টাস্ক সহজেই হাসিল করা যায়। android{} ব্লকের ভিতর শুধু defaultConfig{} ও buildType{} নামক দুইটা properties উল্লেখ করা হয়েছে। আমরা কিন্তু এই দুইটা প্রোপার্টিজের সাথে পরিচিত। প্রায়ই এই প্রোপার্টিজগুলোর ভিতর দরকার অনুযায়ী পরিবর্তন করি। এই দুইটা ছাড়া আর কি কোনো প্রোপার্টিজ সম্পর্কে আমরা জানি? অনুসন্ধিৎসু পাঠক চাইলে এখান থেকে ঢুঁ মেরে আসতে পারেন
- এই অংশটা আমাদের কাছে সুপরিচিত। আমাদের পছন্দের অসংখ্য লাইব্রেরি আমরা এখানে অ্যাড করি জাস্ট একটা লাইন লেখার মাধ্যমে। দেখেন, এখানে কিন্তু আমরা আমাদের লাইব্রেরিগুলোর রিপোজিটরিগুলো বা কোথা থেকে ডাউনলোড করতে হবে সেটা উল্লেখ করে দেই নি। কিন্তু গ্র্যাডল কিভাবে বুঝবে? কি মনে পড়ে? root project এর build.gradle এর allprojects{} এর ভিতরে আমরা বলে দিয়েছি আমাদের রিপোজিটরিগুলোর নাম। তাই আর এই ব্লকে আলাদা করে রিপোজিটরিগুলোর উল্লেখ করার দরকার হয় নাই
- root build.gradle ফাইলে আমরা কটলিনের ভার্সন বলে রেখেছিলাম। সেটা এখানে ইউজ করা হলো। প্রায় সময়ই দেখা যায় android এর support library-গুলোর ভার্সন প্রতিটা লাইব্রেরির জন্য একটা একটা করে ডিফাইন করে দিতে হয়। আমরা জানি সাপোর্ট লাইব্রেরিগুলোর ভার্সন সবগুলোর একই রাখতে হয়। কিন্তু প্রতিটা সাপোর্ট লাইব্রেরির ভার্সন আলাদা আলাদা হার্ডকোডেড ভাবে ডিফাইন করলে ভুল হবার আশংকা থাকে। তাই ৩-৪ টা বা যে কয়টা সাপোর্ট লাইব্রেরি অ্যাড করা হবে সেগুলো সবগুলোর ভার্সন একই রাখার জন্য এরকম গ্লোবাল একটা ভ্যারিয়েবলে ভার্সনের নাম স্টোর করে সেটাকে ইউজ করা যায়
proguard-rules.pro
ProGuard rules কী ও কিভাবে কাজ করে সেটা আসলে সরাসরি আমাদের এই ব্লগ পোস্টের সাথে সম্পৃক্ত নয়। তাই বিস্তারিত এখানে বলে পোস্টের কলেবর বাড়াচ্ছি না। এই টপিকটা জানা থাকলে অ্যাপের সোর্স কোডের সিকিউরিটির জন্য কিছুটা কাজ করা যায়। APK decompile করে কেউ যেন খুব সহজেই আমাদের অ্যাপের সোর্স কোডগুলো পেয়ে না যায় সেজন্য এটা জানা জরুরি। এ বিষয়ে আমার এই ব্লগসাইটেই একটি বিস্তারিত পোস্ট রয়েছে। আগ্রহীরা দেখতে পারেন।
local.properties
এই ফাইলে বলা থাকে কম্পিউটারের কোন লোকেশনে Android SDK-টা রয়েছে। অর্থাৎ Android SDK এর path বলে দেয়া থাকে এই ফাইলে।
gradle.properties
এই ফাইলে গ্র্যাডলের বিভিন্ন প্রোপার্টিজ বলা থাকে যেগুলো বিল্ড করার সময় গ্র্যাডল ফলো করে। এখানে heap size বাড়ানোর, on demand configuration সহ আরো কিছু প্রোপার্টিজ বলা যায় যেগুলোর কারণে গ্র্যাডল বিল্ডের running time অনেকটা কমে আসে। এই পোস্টেই gradle build হবার জন্য সময় কিভাবে কমানো যায় তা শেষের দিকে আলোচনা করা হবে।
Gradle script এর ফাইলগুলোর পরিচিতি পর্ব শেষ। এখন গ্র্যাডলের ডিপেন্ডেন্সি বিষয়ক ২-১ টা টপিক একটু টাচ করতে চাই। এগুলো সরাসরি হয়ত এই পোস্টের সাথে সম্পৃক্ত নয় কিন্তু বিভিন্ন জব ইন্টারভিউতে কনসেপচুয়াল এই বিষয়গুলো জিজ্ঞেস করতে পারে। বা এমনি একজন অ্যান্ড্রয়েড ডেভেলপার হিসাবে এই কনসেপ্টগুলো জানা থাকা ভাল।
Implementation Vs API in Android Gradle plugin dependency
ধরা যাক, MyApplication নামের একটা প্রোজেক্টে LibraryA নামের একটা লাইব্রেরি (হতে পারে এটা Retrofit, Picasso, Glide বা যে কোনো লাইব্রেরি) গ্র্যাডলের dependencies{} ব্লকের মধ্যে অ্যাড করা হলো। Sync করার পর দেখলাম আমাদের অ্যাপ থেকে LibraryA এর কোডগুলো অ্যাক্সেস করতে পারছি, মানে লাইব্রেরিটা কাজ করছে। এখন আমরা জানতে পারলাম LibraryA এই লাইব্রেরিটা LibraryB নামের আরেকটা লাইব্রেরি ইউজ করেছে। এখন প্রশ্ন হচ্ছে আমাদের প্রোজেক্ট থেকে আমরা সরাসরি LibraryB এর কোনো class-method অ্যাক্সেস করতে পারব?
পারব তবে “শর্ত প্রযোজ্য”। সেই শর্তটা কী? শর্তটা হচ্ছে যদি LibraryA তার গ্র্যাডল ডিপেন্ডেন্সিতে LibraryB কে api কী-ওয়ার্ডের মাধ্যমে অ্যাড করে থাকে আর আমরাও আমাদের প্রোজেক্টের ডিপেন্ডেন্সিতে LibraryA কে api কী-ওয়ার্ড দিয়ে অ্যাড করে থাকি তাহলে আমাদের প্রোজেক্ট থেকে LibraryB এর কোড অ্যাক্সেস করা যাবে।
# use `api` keyword # in MyApplication dependencies { . . . . api project(path: ':libraryA') } # in LibraryA dependencies { . . . . api project(path: ':libraryB') } # উপরে api keyword দিয়ে library add করা হয়েছে, # তাই MyApplication থেকে LibraryB এর কোড access করা যাবে -------------------------------------------- # use `implementation` keyword # in MyApplication dependencies { . . . . implementation project(path: ':libraryA') } # in LibraryA dependencies { . . . . implementation project(path: ':libraryB') } # এখানে implementation keyword দিয়ে library add করা হয়েছে, # তাই MyApplication থেকে LibraryB এর কোড access করা যাবে না
কিন্তু আমরা এখন বেশির ভাগ dependency add করার জন্য api এর পরিবর্তে implementation কী-ওয়ার্ড ইউজ করি। এটা ইউজ করে dependency add করা হলে সেক্ষেত্রে আমাদের প্রোজেক্টে বসে LibraryB এর কোড অ্যাক্সেস করা যাবে না।
আমরা জানি যে এটা gradle 3 থেকে compile keyword টা deprecated করে এর পরিবর্তে লাইব্রেরি অ্যাড করার জন্য implementation keyword নিয়ে আসা হয়েছে। এই compile কমান্ডটা ছিল এখনকার api command এর similar. অর্থাৎ এখন api command লিখলে যেই কাজ হবে আগে compile লিখলে একই কাজ হত। কিন্তু implementation command টা বা এই ফিচারটা গ্র্যাডল 3 তে আনা হয়েছে।
এখন প্রশ্ন হচ্ছে আমাদের অ্যাপের কোড থেকে LibraryA, LibraryB সবগুলোর কোড অ্যাক্সেস করা যাবে api কমান্ড দিয়ে লাইব্রেরি অ্যাড করলে। তা না করে প্রায় সব লাইব্রেরি আমরা implementation দিয়ে অ্যাড করি কেন? এত সুন্দর কোড অ্যাক্সেস করার ফিচারটা আমরা কেন ইউজ করি না বা কম করি? এতে একটা সমস্যা আছে!
সমস্যাটা হচ্ছে গ্র্যাডল বিল্ড রিলেটেড। আমরা যদি api command ইউজ করে আমাদের অ্যাপে LibraryA, LibraryA আবার LibraryB কে অ্যাড করে তাহলে আমাদের অ্যাপ থেকে LibraryB এর কোড অ্যাক্সেস করা যাবে। এখন LibraryB এর কোড যদি আপডেট হয় তখন আমরা যখন প্রোজেক্ট বিল্ড দিব তখন গ্র্যাডল LibrarB এর কোড প্রথমে বিল্ড করবে। এরপর LibraryA কে বিল্ড করবে। কারণ LibraryA, LibraryB কে api command দিয়ে অ্যাড করেছে। এরপর বিল্ড করবে আমাদের app module কে। আমরা এই লাইব্রেরির চেইনটা যদি আরো লম্বা কল্পনা করি MyApplication > LibraryA > LibraryB > LibraryC > LibraryD > LibraryE. LibraryE আপডেট হলে তার আগের সবগুলো মডিউল সহ MyApplication বিল্ড হবে।
অপরপক্ষে, MyApplication > LibraryA > LibraryB > LibraryC > LibraryD > LibraryE এই লাইব্রেরিগুলোকে যদি আমরা implementation keyword দিয়ে অ্যাড করি তাহলে LibraryE এর আপডেট হলে গ্র্যাডল শুধু LibraryE ও LibraryD এর কোডকে নতুন করে বিল্ড করবে। প্রতিবার LibraryE এর আপডেটের জন্য আর তার আগের সকল caller module-কে বিল্ড করা লাগবে না। এতে গ্র্যাডল বিল্ড হবার time অনেকটাই কমে আসে। এজন্য আমরা api এর পরিবর্তে implementation command ব্যবহার করে dependency add করি।
What is Transitive dependency?
উপরের উদাহরণে আমরা প্রোজেক্টে সরাসরি LibraryA কে অ্যাড করেছি। এটা হচ্ছে direct artifact. LibraryA আবার অ্যাড করেছে LibraryB কে। এই লাইব্রেরিটা আমাদের প্রোজেক্টের সাপেক্ষে transitive dependency. অনেকটা এভাবে চিন্তা করতে পারি যে প্রোজেক্টে ডিরেক্ট অ্যাড হওয়া লাইব্রেরিগুলো হচ্ছে direct dependency, আর ডিরেক্ট ডিপেন্ডেন্সির অ্যাড করা বাচ্চা-কাচ্চা লাইব্রেরি হচ্ছে transitive dependency.
Gradle Tasks – Android Studio এর প্রোজেক্ট build দিলে যা ঘটে
যখন অ্যান্ড্রয়েড স্টুডিও’র প্রোজেক্ট বিল্ড করা হয় তখন কী ঘটে সেগুলো আমরা খুব সংক্ষেপে কিছু হিন্টসের মত দিয়ে যাব। কী কী কাজ হয় সেটা দেখার জন্য আপনার পিসিতে থাকা যে কোনো একটা প্রোজেক্ট ওপেন করে বিল্ড দিন। এরপর অ্যান্ড্রয়েড স্টুডিও’র নিচের Build ট্যাবে ক্লিক করুন। বিল্ড শেষ হয়ে গেলে নিচের ছবির মত কিছু একটা দেখতে পাবেন।
এখানে দেখা যাচ্ছে কোন টাস্কগুলো successfully complete করার মাধ্যমে প্রোজেক্টটা ঠিকঠাক ভাবে বিল্ড হয়েছে। এই ছবির top left কর্ণারের মাঝের Toggle view বাটনে ক্লিক করলে plain text এ বিল্ড আউটপুটটা দেখা যাবে।
উপরের ছবি থেকে দেখা যাচ্ছে যে প্রথম লাইনে Executing tasks: [:app:assembleDebug] লেখা। অর্থাৎ Android Studio গ্র্যাডলকে কমান্ড দিয়েছে assembleDebug এই টাস্কটা এক্সিকিউট করার জন্য। আমরা চাইলে বিল্ড বাটনে ক্লিক না করে terminal এ ./gradlew assembleDebug –console plain কমান্ডটা লিখে এই একই কাজ করতে পারি। এজন্য অ্যান্ড্রয়েড স্টুডিওতে থাকা টার্মিনাল অথবা পিসির টার্মিনাল যে কোনোটাই ব্যবহার করতে পারেন। নিচে আমার লিনাক্স ম্যাশিনের টার্মিনালে উক্ত কমান্ড দেয়ার পর আউটপুট কী আসলো তার স্ক্রিনশট দিচ্ছিঃ
./gradlew assembleDebug –console plain কমান্ডটা একটু ব্যাখ্যা করা যাক। প্রথম অংশ ./gradlew দ্বারা বুঝানো হচ্ছে Gradle-এর vanilla ভার্সন ইউজ না করে wrapper ভার্সনটা ইউজ করে এই কমান্ডটা এক্সিকিউট করতে। এরপর assembleDebug হচ্ছে টাস্কের নাম, এই টাস্কটিই আমরা রান করতে চাচ্ছি। আর কমান্ডের শেষ অংশ –console plain দিয়ে টার্মিনালে এই টাস্ক এক্সিকিউশনের সব log message-গুলো প্রিন্ট করার জন্য নির্দেশ দেয়া হচ্ছে। এই অংশটার কারণে অ্যান্ড্রয়েড স্টুডিওতে যেই ফরমেটে লগ শো করে, আমাদের টার্মিনালেও একই ফরমেটে লগ শো করবে। তবে এটা পুরোপুরি অপশনাল। না দিলেও টাস্ক এক্সিকিউট হবে।
Gradle এর আরো অন্যান্য সব টাস্কগুলোর লিস্ট আর শর্ট ডেসক্রিপশন পেতে টার্মিনালে ./gradlew tasks –all কমান্ডটা চালিয়ে দেখতে পারেন।
এবার Android Studio এর top right কর্ণারের Gradle নামের যেই ট্যাব আছে সেটাতে ক্লিক করুন। নিচের মত একটা ট্যাব ওপেন হবার কথা।
কি চেনা চেনা লাগে? এতক্ষণ আমরা যেই টাস্কগুলো নিয়ে কথা বললাম সেগুলোর গ্রাফিক্যাল রিপ্রেজেন্টেশন। App > Tasks > build এর ভিতরে assemble আইটেমের উপর ডাবল ক্লিক করলে প্রোজেক্ট বিল্ড হবে। অর্থাৎ টার্মিনালে কমান্ড দিয়ে যখন অ্যাপ বিল্ড করেছিলাম, সেই কমান্ডটাই এখানে ডাবল ক্লিক করলে এক্সিকিউট হচ্ছে। আমরা অ্যান্ড্রয়েড স্টুডিওর Tools এ গিয়ে প্রোজেক্ট ক্লিন করি। সেই টাস্কটা আপনি চাইলে এখানকার Clean নামক আইটেমের উপর ডাবল ক্লিক করে করতে পারেন, আর টার্মিনালে তো পারবেনই! তো এখান থেকে বিভিন্ন টাস্কের উপর ডাবল ক্লিক করে দেখতে পারেন কোনটায় কী হয়।
Decrease your Gradle build time – গ্র্যাডল বিল্ডের সময় যেভাবে কমানো যায়
অ্যান্ড্রয়েড ডেভেলপারদের দিনের বড় একটা অংশ কাটে “Gradle build running…” এই লেখাটার দিকে তাকিয়ে তাকিয়ে। এখন জাস্ট কপি-পেস্ট একটা সলিউশন দিয়ে পোস্ট শেষ করব। এর মাধ্যমে গ্র্যাডল বিল্ডের রানিং টাইম প্রায় অর্ধেকে নেমে আসতে পারে। আপনার প্রোজেক্টের gradle.properties ফাইলে নিচের কোডগুলো লিখুন।
#Enable daemon org.gradle.daemon=true # Try and findout the best heap size for your project build. org.gradle.jvmargs=-Xmx3096m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 # Modularise your project and enable parallel build org.gradle.parallel=true # Enable configure on demand. org.gradle.configureondemand=true
এই পোস্টটা অনেক বড় হয়ে যাওয়ায় এখানে আর উপরে উল্লেখ করা প্রোপার্টিজগুলোর ব্যাখ্যা করা সম্ভব হলো না। আগামীতে চেষ্টা করব gradle build এর performance increase নিয়ে আলাদা পোস্ট লিখার।
পোস্টের শব্দ সংখ্যা হয়ে গেছে প্রায় সাড়ে চার হাজার! এত বড় পোস্ট আদৌ কেউ পড়বে কিনা সন্দেহ আছে। তাও পড়ে যদি কারো কাজে আসে তাহলে হয়ত এটি সাদকায়ে জারিয়া হিসাবে আমার মৃত্যুর পরও আমার আমলনামায় সওয়াব পাঠাতে থাকবে। লেখায় তথ্যগত বা কনসেপচুয়াল কোনো ভুলভ্রান্তি থেকে থাকলে আমাকে জানিয়ে বাধিত করবেন। এছাড়াও লেখাটি সম্পর্কে আপনার যে কোনো গঠনমূলক সমালোচনা একান্ত কাম্য।
আপনার দুয়ায় আমাকে রাখবেন, আল্লাহ যেন আমাকে দুনিয়া ও আখিরাতের কল্যান দান করেন।
Some resources:
- Beginner’s Guide to Gradle for Android Developers
- Introducing Gradle for new Android developers – The master builder
- Android 101: Gradle dependencies
- How Android Code İs Compiled?
- Android | build.gradle – Geeks For Geeks
- Building Android applications with Gradle – Vogella
- Implementation Vs Api in Android Gradle plugin 3.0
- Configure your build – Android Developers Official Doc
- How to decrease your Gradle build time by 65%?
Onek valo laglo vai. Oshadharon Likhesen . 2019 er first post mone hosse. majhe onk gap. aaro new post likhen. Apnar sob gulu post e onk maansommoto. Thanks
aro kichu code ase ase build time komanor jonno , add korte paren
org.gradle.caching = true
android.enableBuildCache=true
android.enableBuildScriptClasspathCheck=false
vai comment e copy paste er option ta raikkhen. code gulu type korte holo. Thanks