كانت الآلة الافتراضية Dalvik مكوناً رئيسياً في نظام التشغيل Android، المسؤولة عن تشغيل التطبيقات حتى الإصدار 4.4 KitKat. طورها دان بورنشتاين، هذه الآلة الافتراضية القائمة على السجلات حلت محل مفهوم JVM القياسي وسمحت بتحسين بدء تشغيل التطبيقات على الأجهزة المحمولة ذات الذاكرة العشوائية المحدودة. وفقاً لـ Google, 2024، ضمنت Dalvik توافق التطبيقات من خلال التجميع في الوقت المناسب JIT، محولةً DEX bytecode إلى تعليمات آلية مباشرة أثناء التنفيذ.
الخلاصة
Dalvik هي آلة افتراضية بمعمارية قائمة على السجلات، أُنشئت خصيصاً لمنصة Android. بدأ التطوير في عام 2005 بواسطة شركة دان بورنشتاين، وفي عام 2007 استحوذت Google على المشروع. ظهرت أول نسخة تجارية من Dalvik مع إصدار Android 1.0 في عام 2008.
على عكس الآلة الافتراضية لجافا القياسية (JVM)، لا تنفذ Dalvik bytecode لجافا. يحول مترجم جافا الكود المصدري إلى ملفات class، ثم تقوم الأداة dx بترجمتها إلى تنسيق Dalvik Executable (DEX). هذا التنسيق أكثر ضغطاً من ملفات class: تطبيق بحجم 10 ميجابايت بتنسيق class يشغل حوالي 6–7 ميجابايت في DEX.
كتب دان بورنشتاين Dalvik كمشروع لأنظمة التشغيل ذات الموارد المحدودة. الاسم مأخوذ من قرية دالفيك الأيسلندية. اختارت Google Dalvik بدلاً من JVM بسبب قيود الترخيص والحاجة إلى تحسين عميق للمعالجات المحمولة ذات معمارية ARM. سرعان ما اكتسب النظام شعبية: بحلول عام 2012، كان أكثر من 500 مليون جهاز Android يعمل بـ Dalvik.
يتم تشغيل كل تطبيق Android في عملية منفصلة مع مثيل خاص به من آلة Dalvik الافتراضية. يضمن ذلك عزل البيانات والحماية من الكود الخبيث على مستوى نظام التشغيل. يجمع هذا النهج بين مزايا المحاكاة الافتراضية و sandbox لينكس — لا يمكن للبرامج الضارة في تطبيق واحد التأثير على العمليات المجاورة.
المعمارية القائمة على السجلات لـ Dalvik تختلف جوهرياً عن المعمارية القائمة على المكدس لـ JVM. بدلاً من العمليات على قمة المكدس، تعمل Dalvik مع السجلات — خلايا افتراضية داخل الآلة الافتراضية. تحتوي كل تعليمة على عناوين سجلات المعاملات، مما يقلل عدد التعليمات لكل عملية.
آلة المكدس في JVM تستخدم تعليمات مثل push و pop و add — لجمع رقمين يلزم ثلاث تعليمات. Dalvik تحل نفس المهمة بتعليمة add-int واحدة مع ثلاثة سجلات. وفقاً لـ مشروع Android مفتوح المصدر، المعمارية القائمة على السجلات لـ DEX تقلل حجم bytecode بمتوسط 30% مقارنة بتنسيق class القائم على المكدس.
ملف DEX (Dalvik Executable) يحتوي على تمثيل مضغوط لجميع فئات التطبيق. يتضمن رأس الملف مجموع اختباري وأحجام الأقسام والإزاحات. الأقسام الرئيسية هي مجموعات السلاسل والأنواع ونماذج الدوال والحقول والـ bytecode نفسه. يمكن لملف DEX واحد تخزين حتى 65,536 دالة (تم رفع القيد بإدخال multi-dex في Android 5.0).
الأداة dx، المضمنة في أدوات بناء Android SDK، تُستخدم لتحويل ملفات class إلى DEX. مثال على الأمر: dx --dex --output=classes.dex myapp.jar. تستخدم المشاريع الحديثة D8، خليفة dx مع تحسين أفضل ودعم لميزات Java 8+.
# تحويل JAR إلى DEX باستخدام dx
dx --dex --output=classes.dex myapp.jar
# النسخة الحديثة عبر D8
d8 --lib android.jar --output dex/ myapp.jar
عملية Zygote هي عنصر حاسم في معمارية Dalvik. عند بدء تشغيل النظام، يقوم Zygote بتحميل جميع فئات SDK الخاصة بـ Android، ويفتح المكتبات المشتركة، وينشئ مجموعة من الموارد المحملة مسبقاً. عندما يفتح المستخدم تطبيقاً، يقوم النظام بنسخ عملية Zygote (fork)، منشئاً مثيلاً جديداً من آلة Dalvik الافتراضية مع إطار عمل مهيأ بالفعل. يقلل ذلك وقت بدء التطبيق من ~2–3 ثوانٍ إلى 300–500 مللي ثانية.
JIT (Just-In-Time) هي تقنية لتجميع bytecode إلى تعليمات آلية مباشرة أثناء تنفيذ التطبيق. في Dalvik، يقوم مترجم JIT بتحليل كود DEX المنفذ، ويحدد الدوال المستخدمة بشكل متكرر (hot) ويجمعها إلى كود أصلي لوحدة المعالجة المركزية.
كان اختيار JIT بدلاً من التجميع المسبق الكامل Ahead-Of-Time (AOT) في الإصدارات المبكرة من Android متعمداً. كانت الأجهزة المحمولة ذات ذاكرة فلاش محدودة (4–16 جيجابايت) — تجميع جميع التطبيقات مسبقاً كان سيستهلك مساحة كبيرة. بالإضافة إلى ذلك، كانت ذاكرة ROM في الأجهزة المبكرة أبطأ من RAM، وقراءة الكود المجمع مسبقاً قد تقلل الأداء.
عند بدء تشغيل تطبيق، تبدأ Dalvik في تفسير DEX bytecode. يتتبع ملف تعريف خاص أي الدوال تُستدعى بشكل متكرر. بعد تجاوز حد معين (عادةً ~200 استدعاء)، يقوم مترجم JIT بتحويل الدالة إلى كود آلي ويخزنه مؤقتاً في RAM. تستخدم الاستدعاءات اللاحقة النسخة المجمعة بالفعل دون إعادة تجميع.
// مثال على دالة hot سيقوم JIT بتجميعها
public class Calculator {
public int sumArray(int[] arr) {
int total = 0;
for (int i = 0; i < arr.length; i++) {
total += arr[i];
}
return total;
}
}
وفقاً لـ Google I/O 2013، أدى إدخال JIT في Android 2.2 Froyo إلى تسريع تنفيذ التطبيقات بمعدل 2–5 مرات مقارنة بالتفسير الخالص. ومع ذلك، يضيف JIT تأخيراً عند أول تشغيل: يحتاج التطبيق من 3 إلى 10 ثوانٍ للإحماء وتجميع الدوال hot. بعد الإحماء، يستقر الأداء عند مستوى قريب من الكود الأصلي.
Dalvik تختلف عن JVM بعدة جوانب أساسية. أولاً — المعمارية: JVM قائمة على المكدس، Dalvik قائمة على السجلات. ثانياً — تنسيق bytecode: JVM تستخدم ملفات class، Dalvik تستخدم DEX. ثالثاً — إدارة الذاكرة: Dalvik محسنة لـ الذاكرة العشوائية المحدودة للأجهزة المحمولة.
كلا النهجين لهما نقاط قوة. JVM القائمة على المكدس تتطلب مساحة أقل لتخزين التعليمات — كل تعليمة أقصر لأن المعاملات تؤخذ ضمنياً من المكدس. Dalvik القائمة على السجلات تنفذ تعليمات أقل لكل عملية، مما يوفر وقت المعالج ويقلل استهلاك الطاقة. للأجهزة المحمولة التي تعمل بالبطارية، هذا أمر بالغ الأهمية.
| المعامل | Dalvik | JVM |
|---|---|---|
| المعمارية | قائمة على السجلات | قائمة على المكدس |
| Bytecode | DEX | class |
| التجميع | JIT (Android 2.2+) | JIT / AOT |
| التحسين | استهلاك طاقة منخفض | توافق عالي |
| العزل | عبر عمليات لينكس | عبر ClassLoader |
كان اختيار Dalvik بدلاً من JVM مدفوعاً أيضاً بـ الترخيص. تمتلك Oracle حقوق Java SE و JVM، وسعت Google لتجنب رسوم الترخيص. إنشاء آلة افتراضية خاصة بها مع تنسيق bytecode بديل سمح لـ Android بالتطور بشكل مستقل عن Oracle. تحول هذا النزاع إلى معركة قانونية طويلة، Oracle ضد Google (2010–2021)، انتهت لصالح Google.
DEX (Dalvik Executable) هو تنسيق ثنائي يحتوي على الكود المجمع لتطبيق Android. يبدأ كل ملف DEX برأس، تتبعه أقسام: ثوابت السلاسل (string_ids)، الأنواع (type_ids)، نماذج الدوال (proto_ids)، الحقول (field_ids)، الدوال (method_ids)، تعريفات الفئات (class_defs)، ومنطقة بيانات.
الأداة dx تحول ملفات class لجافا إلى ملف DEX واحد أو أكثر. تتضمن الخوارزمية إزالة ازدواج الثوابت — السلاسل أو الأنواع المتطابقة تُخزن مرة واحدة وتُشار إليها بالفهرس. يقلل ذلك الحجم النهائي بشكل كبير. في المشاريع الحديثة، استبدلت dx بـ D8 (المقدمة في Android Studio 3.1)، وهي أسرع 2–3 مرات وتدعم desugaring لجافا 8.
// مثال على DEX bytecode مفكك عبر dexdump
// الكود المصدري: return a + b;
@Ldalvik/annotation/Code;
registers: 3
add-int v0, v1, v2
return v0
قيد تنسيق DEX البالغ 65,536 دالة (حد الفهرس 16 بت) أصبح مشكلة خطيرة للتطبيقات الكبيرة. جاء الحل مع Android 5.0: دعم multi-dex يسمح للتطبيق باحتواء ملفات DEX متعددة. ملف classes.dex الرئيسي يحتوي على نقاط الدخول، بينما تحتوي الملفات الإضافية classes2.dex و classes3.dex وهكذا على بقية الكود. يتم تفعيل تكوين multi-dex في build.gradle بالسطر multiDexEnabled true.
جمع القمامة في Dalvik مطبق كمجمّع أجيال مع وضع علامات ومسح (mark-and-sweep). تنقسم الذاكرة إلى منطقتين رئيسيتين: Heap (كومة) للكائنات و Stack (مكدس) للبدائيات والمراجع. عندما تمتلئ الكومة، تعلق Dalvik جميع الخيوط (STW — Stop-The-World)، وتضع علامات على الكائنات القابلة للوصول وتحرر غير القابلة للوصول.
قبل Android 2.2، استخدمت Dalvik مجمّعاً أحادي الخيط مع فترات توقف تصل إلى 100–200 مللي ثانية. Android 2.3 Gingerbread قدم مجمّعاً متزامناً قلل فترات التوقف النموذجية إلى 5–10 مللي ثانية. و Android 4.0 Ice Cream Sandwich أضاف مجمّعاً مع تنظيف تدريجي — Concurrent Mark and Sweep (CMS).
مشكلة نموذجية في تطبيقات Dalvik هي تسربات الذاكرة عبر مراجع ثابتة إلى Activity. إذا كان حقل ثابت يحتفظ بمرجع إلى Context أو View، لا يستطيع مجمّع القمامة تحرير Activity حتى بعد إغلاق الشاشة. أدوات مثل Eclipse MAT و LeakCanary تساعد في اكتشاف هذه التسربات: تحلل تفريغ الكومة وتظهر سلاسل المراجع التي تمسك بالكائن.
// مثال على تسرب ذاكرة عبر مرجع ثابت
public class Utils {
private static Context context;
public static void init(Context ctx) {
context = ctx; // يحتفظ بـ Activity بعد finish()
}
}
على الرغم من نجاحها، كان لـ Dalvik عدة عيوب. تطلب التجميع JIT وقتاً للإحماء — كانت الثواني الأولى من تشغيل التطبيق أبطأ. بالإضافة إلى ذلك، استهلك JIT طاقة المعالج أثناء التجميع، مما قلل من عمر البطارية. مع نمو أداء الأجهزة المحمولة وزيادة التخزين المدمج، انخفضت الحاجة إلى JIT.
في Android 4.4 KitKat، قدمت Google ART (Android Runtime) كبديل تجريبي لـ Dalvik. بدءاً من Android 5.0 Lollipop، أصبح ART بيئة التشغيل الوحيدة. الفرق الرئيسي هو التجميع AOT: بدلاً من التجميع أثناء التنفيذ، يتم تجميع جميع التطبيقات إلى كود آلي أثناء التثبيت. هذا أزال تأخيرات الإحماء وحسن كفاءة الطاقة.
كان الانتقال من Dalvik إلى ART شفافاً للمطورين: كلا البيئتين تنفذان نفس DEX bytecode. التطبيقات المجمعة لـ Dalvik تعمل على ART دون إعادة تجميع — system_server يجمعها إلى كود أصلي أثناء التثبيت. الاستثناء هو الكود الذي يستخدم الانعكاس للوصول إلى الأعضاء الداخليين لآلة Dalvik الافتراضية: قد يتعطل هذا الكود على ART بسبب تغييرات في المعمارية الداخلية.
الأسئلة الشائعة
Dalvik هو برنامج وسيط يشغل تطبيقات Android على الهاتف. يأخذ كود التطبيق ويحوله إلى أوامر تفهمها المعالجة، مما يفعل ذلك مباشرة أثناء عمل المستخدم.
تستخدم Dalvik معمارية قائمة على السجلات وتنسيق DEX، بينما تستخدم JVM معمارية قائمة على المكدس وتنسيق class. Dalvik محسنة للأجهزة المحمولة ذات الذاكرة وقوة المعالجة المحدودة، بينما JVM مصممة لأجهزة الكمبيوتر المكتبية والخوادم.
ART يوفر أداءً أعلى من خلال التجميع المسبق AOT — يتم تجميع التطبيق مرة واحدة أثناء التثبيت، وليس في كل مرة يُشغل. هذا يسرع التشغيل ويوفر البطارية مقارنة بنهج JIT في Dalvik.
نعم، ART متوافق تماماً مع DEX bytecode لـ Dalvik. أثناء التثبيت، يجمع ART ملفات DEX القديمة إلى كود أصلي. الاستثناء هو التطبيقات التي تستخدم الانعكاس للوصول إلى الآليات الداخلية لـ Dalvik.
DEX (Dalvik Executable) هو تنسيق ملف تنفيذي يحتوي على bytecode مضغوط لتطبيق Android. يمكن أن يحتوي APK واحد على ملفات DEX متعددة (multi-dex) إذا كان التطبيق يحتوي على أكثر من 65,536 دالة.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.