ارشیا مقیمی
ارشیا مقیمی
خواندن ۷ دقیقه·۴ سال پیش

پچ داغ(!) در اندروید با استفاده از ابزار تینکر

تینکر چیست؟

تینکر ابزار رسمی WeChat و توسعه داده شده توسط کمپانی چینی Tencent است که قابلیت توزیع پویای کد را در اختیار برنامه‌نویسان اندروید قرار می‌دهد. با استفاده از این ابزار می‌توان بدون نیاز به نصب مجدد، برنامه اندرویدی را به‌روز‌رسانی کرد و مشکلات برنامه را رفع کرد و قابلیت‌های مختلفی به آن افزود یا حذف کرد.



چرا تینکر؟

جز تینکر ابزارهای متنوعی برای hot patch وجود دارد. مانند Ali’s AndFix، American League’s Robust و QZone’s Super Patch که مقایسه این ابزارها در زیر آمده است:

جدول مقایسه ابزارهای hot patch
جدول مقایسه ابزارهای hot patch

به شکل خلاصه:

  • ابزار AndFix پایداری کمی دارد و مهم‌تر اینکه توانایی جایگزینی کلاس‌ها را ندارد و هزینه توسعه‌ی زیادی دارد.
  • ابزار Robust پایداری بالایی دارد اما همانند AndFix توانایی افزودن متغیرها و کلاس‌ها را ندارد و تنها به عنوان ابزار رفع باگ می‌توان از آن استفاده کرد.
  • ابزار QZone قابلیت‌های بهنری نسبت به دو ابزار بالا دارد اما مهم‌ترین نقطه ضعف آن، کاهش عملکرد برنامه‌هاست.
  • ابزار Tinker نه تنها از افزودن کلاس پشتیبانی می‌کند بلکه می‌توان با کمک آن کتابخانه‌های پویا و منابع دیگر را نیز به برنامه اضافه کرد و نه تنها برای رفع باگ‌های موجود بلکه برای افزودن ویژگی‌های جدید به برنامه نیز کاربرد دارد. هم‌جنین از این ابزار در برنامه‌ی WeChat که صدها میلیون نصب دارد استفاده شده است.

مشکلات تینکر

به علت محدودیت‌های سیستمی اندروید، ابزار تینکر در موارد زیر ناکارآمد است:

  • با این ابزار نمی‌توان Manifest برنامه اندرویدی را ویرایش کرد.
  • به علت محدودیت‌ها و قوانین انتشار در گوگل پلی، نمی‌توان برنامه‌های نوشته شده با این ابزار را در فروشگاه گوگل قرار داد.
  • روی اندروید نسخه‌ی N اعمال پچ باعث کاهش سرعت بالا آمدن برنامه می‌شود.
  • برخی گوشی‌های سامسونگ به دلیل سیاست‌های متفاوت این شرکت توسط این برنامه پشتیبانی نمی‌شود و در صورت اقدام به پچ با خطا مواجه می‌شود.
  • در جایگزینی منابع، ویرایش remoteView پشتیبانی نمی‌شود. انیمیشن‌های انتقال و آیکون‌های اعلان از این دست منابع هستند.


استفاده از تینکر چگونه است؟

برای استفاده از تینکر، در تنظیمات گردل پروژه وابستگی آن را اضافه می‌کنیم:

buildscript { dependencies { classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.9.1') } }

هم‌چنین در تنظیمات گردل برنامه نیز باید تنظیمات زیر اعمال شود:

dependencies { provided('com.tencent.tinker:tinker-android-anno:1.9.1') //tinker core library compile('com.tencent.tinker:tinker-android-lib:1.9.1') } ... ... apply plugin: 'com.tencent.tinker.patch'

ابزار تینکر با استفاده از فایل apk برنامه‌ی گذشته و مقایسه‌ی آن با فایل apk برنامه‌ی جدید، یک پچ شامل تغییرات تولید کرده و با استفاده از آن برنامه‌ی قدیمی نصب شده روی دستگاه کاربر را به روز می‌کند.




تنظیمات گردل تینکر

تنظیمات عمومی تینکر:

  • تنظیم tinkerEnable: به شکل پیش‌فرض فعال است و مشخص می‌کند که آیا ابزار تینکر فعال است یا خیر.
  • تنظیم oldApk: مسیر شامل فایل apk برنامه‌ی قدیمی. وجود این تنظیم برای عملکرد برنامه الزامی است.
  • تنظیم newApk: مسیر شامل فایل apk برنامه‌ی جدید. این تنظیم اختیاری است و در صورت عدم وجود آن برنامه‌ی نوشته شده کامپایل شده و از خروجی آن استفاده می‌شود. در غیر این صورت تنها از فایل‌های apk مشخص شده در تنظیمات oldApk و newApk استفاده می‌شود.
  • تنظیم outputFolder: مسیر ذخیره‌ی فایل‌های کامپایل شده را مشخص می‌کند. به شکل پیش‌فرض این فایل‌ها داخل مسیر build/outputs/tinkerPatch قرار می‌گیرند.
  • تنظیم useSign: به شکل پیش‌فرض فعال است و اطمینان حاصل می‌کند که امضای فایل apk پایه با فایل پچ تولید شده یکسان باشد.

تنظیمات مرتبط با مراحل کامپایل و بیلد:

  • تنظیم tinkerId: آی‌دی مخصوص به نسخه خاص از برنامه که مشخص می‌کند پچ تولید شده روی کدام نسخه‌ی apk گذشته اعمال شده است و پچ‌های مختلف برای نسخه‌های متفاوت apk قابل تمییز باشند.
  • تنظیم keepDexApply: اگر برنامه‌ بیش از یک dex داشته باشد، ممکن است به علت جابجایی کلاس‌ها حجم تغییرات و در نتیجه حجم فایل پچ خروجی بیشتر شود. با فعال کردن این ویژگی که به شکل پیش‌فرض غیرفعال است، برنامه‌ی جدید بر اساس توزیع قدیمی کلاس‌ها کامپایل می‌شود.

تنظیمات مرتبط با dex:

  • تنظیم dexMode: این تنظیم می‌تواند در دو حالت raw و jar اعمال شود. در حالت raw فرمت dex ورودی به همان شکل حفظ می‌شود. اما در حالت jar، dex ورودی به شکل فشرده‌ی jar تبدیل می‌شود که حجم کمتری دارد.
  • تنظیم loader: این تنظیم کلاس‌هایی که توسط تینکر قابل ویرایش نیستند را مشخص می‌کند و هم‌چنین شامل کلاس‌هایی است که باید در dex اصلی قرار بگیرند.

تنظیمات مرتبط با منابع:

  • تنظیم ignoreChange: شامل منابعی که می‌خواهیم تغییرات آن‌ها در کامپایل اعمال نشود.
  • تنظیم largeModSize: اگر منابع تغییر یافته از مقدار مشخص targetModSize که به طور پیش‌فرض ۱۰۰ کیلوبایت است بیشتر شود، فایل تغییر یافته به شکل فشرده در پچ قرار می‌گیرد که در نتیجه حجم پچ نهایی کاهش می‌یابد اما پیچیدگی آن بیشتر می‌شود.

در نهایت پس از کامپایل و تولید فایل‌های پچ موارد زیر در پوشه‌ی خروجی که به طور پیش‌فرض مسیر build/outputs/tinkerPatch است قرار می‌گیرند:

  • فایل patch_unsigned.apk: فایل پچ شده و امضا نشده
  • فایل patch_signed.apk: فایل پچ شده‌ی امضا شده
  • فایل patch_signed_7zip.apk: فایل پچ شده‌ی امضا شده و فشرده شده
  • فایل log.txt: لاگ‌های ابزار تینکر در مراحل کامپایل و تولید پچ
  • فایل dex_log.txt: لاگ‌های ابزار تینکر مرتبط با dex در مراحل کامپایل و تولید پچ
  • فایل so_log.txt: لاگ‌های ابزار تینکر مرتبط با کتابخانه‌ها در مراحل کامپایل و تولید پچ
  • پوشه tinker_result: محتویات پچ نهایی شامل تغییرات، کتابخانه‌ها و اطلاعات اضافه‌ی مورد نیاز

هم‌چنین بدون نیاز به ابزار gradle و با دستورات خط فرمان نیز می‌توان با داشتن فایل‌های apk جدید و قدیم فایل پچ نهایی را تولید کرد. برای این‌کار از ابزار tinker-patch-cli.jar توسعه داده شده توسط خود توسعه‌دهندگان تینکر می‌توان به شکل زیر استفاده کرد:

java -jar tinker-patch-cli.jar -old old.apk -new new.apk -config tinker_config.xml -out output_path

تنظیمات موجود در tinker_config.xml همان تنظیمات گردل توضیح داده شده در بالا هستند که مثالی از آن در این لینک موجود است با این تفاوت که در این حالت باید tinker_id داخل فایل manifest به شکل زیر قرار بگیرد:

<meta-data android:name=&quotTINKER_ID&quot android:value=&quottinker_id_b168b32&quot/>

مثال عملیاتی از تینکر

در این لینک نمونه‌ای از یک برنامه نوشته شده با تینکر وجود دارد که در ادامه آن را توضیح می‌دهیم.

برای تولید برنامه همراه با ابزار تینکر لازم است کلاس SampleApplicationLike که از کلاس DefaultApplicationLike به ارث می‌برد، در برنامه وجود داشته باشد که در آن تنظیمات گوناگونی شامل تنظیمات application و context و... قرار می‌گیرد. مرجع این فایل در این لینک قابل مشاهده است. هم‌چنین اگر کلاسی در برنامه داشته باشیم که از کلاس Application ارث‌بری می‌کند باید تمام موارد پیاده‌سازی شده در آن را به کلاس SampleApplicationLike اشاره شده در بالا منتقل کنیم و این کلاس را به شکل زیر تبدیل کنیم:

public class SampleApplication extends TinkerApplication { public SampleApplication() { super( ShareConstants.TINKER_ENABLE_ALL, // This is passed as a string so the shell application does not // have a binary dependency on your ApplicationLifeCycle class. &quottinker.sample.android.app.SampleApplicationLike&quot); } }

سایر تنظیمات مرتبط با تینکر داخل تنظیمات گردل قرار می‌گیرند که در نمونه‌ی اشاره شده قابل مشاهده است.

در کلاس MainActivity و در تعریف تابع مرتبط با کلیک روی دکمه‌ی Do Crash، به قسمتی از آرایه که وجود ندارد دسترسی اتفاق می‌افتد که در نتیجه برنامه با خطا روبرو شده و بسته می‌شود:

ArrayList<String> strings = new ArrayList<>(); Strings.get(0);

برای اعمال پچ، قسمت خطادار حذف شده و یک اعلان مبنی بر رفع خطا نمایش داده می‌شود و رنگ دکمه به رنگ سبز درمی‌آید:

Toast.makeText(MainActivity.this, “Bug Fixed”, Toast.LENGTH_SHORT).show(); bugTest.setBackgroundColor(getResources().getColor(android.R.color.halo_green_light));

حال به این می‌پردازیم که پچ چگونه تولید می‌شود و چگونه برنامه‌ی کاربر با استفاده از آن به روز می‌شود.

برای ساخت پچ جدید، ابتدا تنظیمات گردل را مطابق توضیحات تنظیمات آن اعمال می‌کنیم و مسیر فایل apk قدیمی را در تنظیم oldApk قرار می‌دهیم. صرفا با استفاده از ابزارهای موجود در گردل ذیل قسمت تینکر، فایل پچ جدید را ایجاد می‌کنیم.

ابزارهای تینکر درون گردل
ابزارهای تینکر درون گردل


برای این که برنامه بتواند به پچ جدید دسترسی داشته باشد، نیاز تا از طریقی از وجود پچ جدید آگاه شود. برای این‌کار، هر بار که برنامه شروع به کار می‌کند با فراخوانی تابع fetchConfig وجود پچ جدید از یک API Endpoint استعلام می‌شود و با کسب اجازه از کاربر این فایل جدید دانلود شده و با فراخوانی ابزار تینکر پچ جدید نصب می‌گردد:

TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), file.getAbsolutePath());

به عنوان API Endpoint از ابزار رایگان Beeceptor استفاده نمودیم و نمونه پاسخ این API به شکل زیر است:

{ &quotpatch_available&quot: true, &quotpatch_id&quot: &quotp_1157&quot, &quotpatch_url&quot:  &quothttps://bayanbox.ir/download/1689140552403622929/patch-signed-7zip.apk&quot }

در صورت true بودن مقدار patch_available و هم‌چنین جلوتر بودن patch_id از نسخه‌ی فعلی، فایل پچ دانلود شده و اعمال می‌شود. با اعمال پچ، برنامه ریستارت شده و برنامه‌ی جدید که شامل پچ جدید است شروع به کار می‌کند.

نویسندگان:

  • ارشیا مقیمی
  • حامد علی‌محمدزاده
  • علیرضا توفیقی محمدی

تهیه شده برای درس برنامه‌نویسی موبایل ارائه شده در نیم‌سال دوم تحصیلی ۰۰-۹۹ دانشگاه صنعتی شریف


منابع:

اندرویدبرنامه‌نویسیموبایللایبرریکتابخانه
شاید از این پست‌ها خوشتان بیاید