Skip to content

mostafa637/building_apk_on_termux

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

بناء تطبيقات أندرويد من الصفر: درس تعليمي مفصل باستخدام أدوات سطر الأوامر

يقدم هذا الدرس التعليمي شرحًا مفصلاً وموسعًا لعملية بناء تطبيق أندرويد أصلي (NDK) باستخدام مجموعة من أدوات سطر الأوامر، بدلاً من الاعتماد على بيئات التطوير المتكاملة مثل Android Studio. يستند الشرح على تحليل السكربت (Bash script) الذي قدمته، مع إضافة تفاصيل إضافية حول كل خطوة، أمثلة عملية، وشرح للخيارات الشائعة للأدوات، مما يجعله دليلاً عمليًا أكثر شمولاً لفهم العملية اليدوية. سنغطي أيضًا بعض النصائح لتصحيح الأخطاء الشائعة وتحسين الأداء.

لماذا نتعلم هذه الطريقة؟

على الرغم من أن أدوات مثل Android Studio و Gradle تبسط عملية البناء إلى حد كبير، فإن فهم العملية اليدوية يوفر رؤية أعمق لمكونات نظام أندرويد وكيفية تفاعلها. هذا الفهم ضروري لتصحيح الأخطاء المعقدة، تحسين أداء البناء، وتخصيص العملية لتناسب احتياجات متقدمة. على سبيل المثال، في مشاريع كبيرة، قد تحتاج إلى بناء مخصص لمعماريات معينة مثل ARM أو x86، أو دمج مكتبات خارجية دون الاعتماد على Gradle. بالإضافة إلى ذلك، يساعد هذا النهج في فهم كيفية عمل أدوات مثل ndk-build (التي هي في جوهرها غلاف حول أدوات مثل clang++) و CMake. إذا كنت تواجه مشكلات في البناء الآلي، مثل أخطاء الربط (linker errors) الغامضة، فإن الرجوع إلى العملية اليدوية يمكن أن يكشف عن أسباب مثل مشاكل في التبعيات أو عدم توافق المعماريات.


المرحلة الأولى: الإعداد وتهيئة البيئة

pkg install clang aapt2 d8 zipalign keytool apksigner openjdk libglvnd-dev

ما هو Android NDK؟ الـ NDK (مجموعة تطوير التطبيقات الأصلية) هي مجموعة من الأدوات التي تسمح للمطورين باستخدام كود مكتوب بلغات C و C++ داخل تطبيقات أندرويد. يُستخدم هذا النهج غالبًا لتحقيق أداء أعلى في المهام الحاسوبية المكثفة مثل الألعاب، معالجة الصوت والصورة، ومحاكاة الفيزياء، أو لإعادة استخدام مكتبات برمجية مكتوبة بالفعل بلغة C/C++. على سبيل المثال، في تطبيقات الألعاب، يمكن استخدام NDK لكتابة محركات الرسومات باستخدام واجهات برمجة التطبيقات الرسومية الأصلية مثل OpenGL ES أو Vulkan، مما يوفر أداءً أفضل بكثير من Java. NDK يدعم أيضًا بناء تطبيقات كاملة أصلية دون Java، باستخدام NativeActivity، وهو ما يتطلب فهمًا لدورة حياة التطبيق (لایف سايكل) مثل onCreate و onPause على المستوى الأصلي.

أدوات البناء الأساسية: السكربت المقدم يستخدم مجموعة من الأدوات المستقلة الموجودة ضمن حزمة أدوات بناء أندرويد (Android SDK Build Tools) و NDK:

  • clang++: مترجم (Compiler) لغة C++ المستخدم لتحويل الكود الأصلي إلى مكتبات مشتركة (.so). هو جزء من NDK ويدعم خيارات مثل --target لتحديد المعمارية (مثل aarch64-linux-android لـ ARM64). يمكن تخصيصه بفلاغات مثل -O3 لتحسين الأداء (optimization)، أو -g لإضافة معلومات التصحيح (debugging symbols)، أو -march=armv8-a لدعم ميزات معالجات محددة.
  • aapt2: أداة أندرويد لتغليف الأصول (Android Asset Packaging Tool)، وتقوم بتجميع موارد التطبيق (مثل الواجهات، الصور، السلاسل النصية) وربطها في ملف APK. تتم عمليتها في مرحلتين: compile لتحويل الموارد إلى صيغة ثنائية (.flat)، و link لربطها مع AndroidManifest.xml. هذا يحسن الأداء مقارنة بـ aapt القديم، حيث يسمح ببناء تدريجي (incremental build).
  • d8: أداة تقوم بتحويل ملفات Java bytecode (.class) إلى صيغة Dalvik Executable (.dex) التي تعمل على نظام أندرويد. هي بديل أسرع وأكثر كفاءة لأداة dx القديمة، وتدعم ميزات Java 8+ عبر عملية تسمى "desugaring" للتوافق مع إصدارات أندرويد القديمة. في حالة تجاوز حد 65 ألف دالة (multidex)، يمكن استخدام خيار --multi-dex.
  • zipalign: أداة لتحسين ملفات APK عن طريق محاذاة البيانات غير المضغوطة، مما يقلل من استهلاك الذاكرة عند تشغيل التطبيق. يجب تنفيذها دائمًا قبل التوقيع، وتستخدم خيار -v 4 لمحاذاة 4 بايت، مما يسرع الوصول إلى الموارد.
  • keytool: أداة Java لإنشاء وإدارة شهادات الأمان ومفاتيح التوقيع (Keystore). تستخدم لإنشاء keystore باستخدام -genkeypair، مع تحديد الاسم المستعار (alias) وكلمة المرور. يجب الحفاظ على أمان هذا الملف، حيث أنه هويتك كمطور ويُستخدم لنشر تحديثات التطبيق في Google Play.
  • apksigner: أداة لتوقيع ملفات APK باستخدام شهادة الأمان، وهي خطوة إلزامية لتثبيت التطبيق على الأجهزة. تدعم مخططات توقيع أحدث مثل v2 و v3 التي توفر أمانًا أعلى وحماية ضد التلاعب بالملف بعد توقيعه. يمكن التحقق من صحة التوقيع باستخدام الأمر apksigner verify.
  • libglvnd-dev: مكتبة libglvnd-dev و ليس libglvnd اى الملفات الخاصة بتطويرها و ليس المكتبة نفسها نحتاج هذه المكتبة لربط egl, gles الخاصة بالنظام الاولى ستساعد فى تشغيل التطبيق و الثانيه ستتسبب فى خطأ not fond libEGL.so.1 أو ما شابه

المرحلة الثانية: تحليل سكربت البناء خطوة بخطوة

دعنا الآن نحلل كل جزء من السكربت لفهم دوره في عملية البناء، مع إضافة أمثلة ونصائح للتعامل مع الأخطاء الشائعة.

1. إعداد المتغيرات والمسارات

# ---------- CONFIG ----------
APP_NAME="MyNDKApp"
P_DIR="$(pwd)"
ANDROID_JAR="$P_DIR/android.jar"
KEYSTORE="$P_DIR/mykey.keystore"
# ... (بقية المتغيرات)

يقوم هذا الجزء بتعريف متغيرات أساسية لتسهيل الوصول إلى الملفات والمجلدات. يتضمن ذلك اسم التطبيق، مسار ملف android.jar (الذي يحتوي على واجهات برمجة تطبيقات أندرويد)، وموقع ملف التوقيع (Keystore). نصيحة لتصحيح الأخطاء: تأكد من أن ANDROID_JAR يشير إلى الإصدار الصحيح لمنصة أندرويد التي تستهدفها (مثل platforms/android-33/android.jar) لتجنب أخطاء في التوافق أثناء الترجمة. إذا واجهت خطأ "file not found" أو "cannot find symbol"، تحقق من صحة المسارات عن طريق طباعتها باستخدام echo $ANDROID_JAR.

2. تنظيف وبناء الكود الأصلي (Native Code)

# ---------- [تنظيف المجلدات السابقة] ----------
rm -rf "$BUILD_DIR"
mkdir -p "$BUILD_DIR/lib/arm64-v8a" ...

echo "[*] Compiling native code..."
"$CLANG" -shared -fPIC \
  --target=aarch64-linux-android21 \
  --sysroot="$NDK_SYSROOT" \
  "$CPP_DIR/native-lib.cpp" \
  "$CPP_DIR/android_native_app_glue.c" \
  -o "$BUILD_DIR/lib/arm64-v8a/libnative-lib.so" \
  -llog -landroid -lEGL -lGLESv2
  • التنظيف: يبدأ السكربت بإزالة مجلد البناء السابق لضمان بناء جديد ونظيف. هذا يمنع مشاكل مثل استخدام ملفات قديمة تسبب أخطاء غير متوقعة في التنفيذ.
  • الترجمة (Compilation): يستخدم clang++ لترجمة ملفات C++ (native-lib.cpp و android_native_app_glue.c).
    • --target=aarch64-linux-android21: يحدد هذا الخيار أن الهدف هو بنية ARM 64-bit لأندرويد، مع تحديد الحد الأدنى لمستوى الـ API (هنا 21). هذا يضمن أن الكود المترجم لن يستخدم واجهات برمجية غير متوفرة في الإصدارات القديمة.
    • --sysroot="$NDK_SYSROOT": يحدد المسار إلى ملفات النظام والمكتبات الخاصة بـ NDK، وهو ضروري للوصول إلى الهيدرات (headers) والمكتبات القياسية مثل EGL و GLES.
    • -shared -fPIC: يُنتج مكتبة مشتركة (.so) يمكن تحميلها ديناميكيًا بواسطة التطبيق. -fPIC (Position-Independent Code) يجعل الكود قابلاً للنقل، وهو مطلب للمكتبات المشتركة.
    • android_native_app_glue.c: هو عبارة عن كود مساعد يوفره الـ NDK لتبسيط إدارة أحداث دورة حياة التطبيق (مثل الإنشاء، الإيقاف المؤقت) والتفاعلات مع المستخدم في تطبيق يعتمد بالكامل على الكود الأصلي. يقوم بإنشاء خيط منفصل لمعالجة الإدخال والأحداث، مما يمنع انسداد الخيط الرئيسي للتطبيق.
    • -l<name>: هذه الخيارات تربط المكتبات المشتركة الضرورية. على سبيل المثال, -llog لـ Android logging، -landroid للواجهات البرمجية الأساسية، و -lEGL/-lGLESv2 لرسوميات OpenGL.
    • خطأ شائع: "undefined reference to..." يشير هذا الخطأ إلى أن المترجم لم يجد تعريف دالة معينة. تأكد من أنك قمت بربط جميع المكتبات المطلوبة باستخدام -l أو أنك قمت بتضمين جميع ملفات المصدر (.cpp/.c) في أمر الترجمة.

3. تجميع الموارد وإنشاء R.java

echo "[*] Compiling resources and generating R.java..."
"$AAPT2" compile --dir "$RES_DIR" -o "$BUILD_DIR/compiled_res"
"$AAPT2" link \
  -o "$APK_UNSIGNED" \
  -I "$ANDROID_JAR" \
  --manifest "$SRC_DIR/AndroidManifest.xml" \
  --java "$GEN_JAVA_SRC_DIR" \
  --min-sdk-version $MIN_SDK \
  --target-sdk-version $TARGET_SDK \
  -R "$BUILD_DIR/compiled_res"/*.flat
  

هنا، تتم معالجة موارد التطبيق على خطوتين باستخدام aapt2:

  • aapt2 compile: تقوم هذه الخطوة بتجميع كل ملف مورد (XML, PNG, etc.) على حدة إلى تنسيق ثنائي محسن (.flat). هذا يسمح ببناء تدريجي، حيث يعاد تجميع الملفات المتغيرة فقط. يتم وضع المخرجات في مجلد وسيط.
    • نصيحة: يدعم aapt2 مؤهلات الموارد (resource qualifiers) مثل drawable-hdpi أو values-ar. سيقوم بتجميع كل نسخة من المورد بشكل منفصل، ويقرر النظام أي نسخة سيتم استخدامها وقت التشغيل بناءً على جهاز المستخدم.
  • aapt2 link: تربط هذه الخطوة جميع الملفات المجمّعة (.flat) مع ملف AndroidManifest.xml لإنشاء ملف APK أولي غير موقّع.
    • -I "$ANDROID_JAR": يربط مكتبة أندرويد للسماح باستخدام موارد النظام (مثل @android:color/white).
    • --java "$GEN_JAVA_SRC_DIR": يقوم بإنشاء ملف R.java، وهو ملف يحتوي على معرفات فريدة لكل مورد في التطبيق، مما يسمح بالوصول إليها من كود جافا (أو عبر JNI من الكود الأصلي).
    • -R .../*.flat: يحدد مسار الموارد المترجمة التي سيتم تضمينها في الـ APK.

4. تجميع كود جافا وتحويله إلى DEX

echo "[*] Compiling R.java and creating DEX file..."
javac -d "$BUILD_CLASSES_DIR" -classpath "$ANDROID_JAR" --release 8 "$GEN_JAVA_SRC_DIR/R.java"
"$D8_TOOL" --release --output "$BUILD_DIR" --lib "$ANDROID_JAR" --min-api $MIN_SDK "$BUILD_CLASSES_DIR"/**/*.class
  • javac: يقوم مترجم جافا بترجمة ملف R.java (وأي ملفات جافا أخرى إن وجدت) إلى Java bytecode (.class). خيار --release 8 يحدد أن الكود يجب أن يكون متوافقًا مع Java 8.
  • d8: تأخذ أداة d8 ملفات .class الناتجة وتحولها إلى ملف classes.dex. ملف DEX هو الصيغة التنفيذية التي يفهمها ويشغلها Android Runtime (ART).
    • --release: يشير إلى بناء مخصص للنشر، مما يمكّن تحسينات إضافية.
    • ملاحظة متقدمة: في المشاريع الحقيقية، غالبًا ما يتم استخدام أداة R8 بدلاً من d8 مباشرة. R8 هي أداة شاملة تقوم بالتحويل إلى DEX، تقليص الكود (shrinking) لإزالة الكود غير المستخدم، التعتيم (obfuscation) لحماية الكود، والتحسين (optimization).

5. إضافة ملف DEX والمكتبات الأصلية إلى الـ APK

echo "[*] Adding classes.dex and native libs to the APK..."
(cd "$BUILD_DIR" && zip -uj "$APK_UNSIGNED" "classes.dex")
(cd "$BUILD_DIR" && zip -ur "$APK_UNSIGNED" "lib")

يتم استخدام أداة zip القياسية لإضافة ملف classes.dex والمكتبات الأصلية (مثل lib/arm64-v8a/libnative-lib.so) إلى ملف APK الأولي الذي تم إنشاؤه بواسطة aapt2. ملف APK في جوهره هو عبارة عن أرشيف ZIP منظم.

6. محاذاة الـ APK

echo "[*] Aligning the APK..."
"$ZIPALIGN" -v 4 "$APK_UNSIGNED" "$APK_UNSIGNED_ALIGNED"

تقوم أداة zipalign بتحسين ملف APK عن طريق ضمان أن جميع البيانات غير المضغوطة (مثل الصور والموارد) تبدأ عند حدود 4 بايت. هذه العملية مهمة جدًا لأنها تقلل من استهلاك الذاكرة العشوائية (RAM) وتزيد من سرعة تشغيل التطبيق، حيث يمكن للنظام الوصول إلى الموارد مباشرة عبر mmap() بدلاً من فك ضغطها أولاً.

7. توقيع الـ APK

echo "[*] Signing the aligned APK..."
if [ ! -f "$KEYSTORE" ]; then
  echo "[*] Generating keystore..."
  "$KEYTOOL" -genkeypair -v -keystore "$KEYSTORE" -alias "$KEY_ALIAS" -keyalg RSA -keysize 2048 -validity 10000
fi

"$APKSIGNER" sign \
  --ks "$KEYSTORE" \
  --ks-key-alias "$KEY_ALIAS" \
  --ks-pass pass:"$KEY_PASS" \
  --out "$APK_SIGNED" \
  "$APK_UNSIGNED_ALIGNED"

هذه هي الخطوة النهائية والحاسمة:

  • إنشاء Keystore (إذا لم يكن موجودًا): يستخدم السكربت keytool لإنشاء ملف توقيع جديد (.keystore). يحتوي هذا الملف على زوج من المفاتيح (عام وخاص) يستخدم لتحديد هوية المطور.
    • نصيحة أمان: لا تقم بتخزين كلمات المرور كنص عادي في السكربتات في المشاريع الحقيقية. استخدم متغيرات البيئة أو أدوات إدارة الأسرار. احتفظ بنسخة احتياطية آمنة من ملف Keystore؛ ففقدانه يعني أنك لن تتمكن من نشر تحديثات لتطبيقك.
  • التوقيع: تستخدم أداة apksigner ملف الـ Keystore لتوقيع ملف APK الذي تمت محاذاته. التوقيع الرقمي يؤكد أن التطبيق لم يتم التلاعب به منذ توقيعه ويثبت ملكية المطور.
    • بشكل افتراضي، يستخدم apksigner مخططات التوقيع الحديثة (v2, v3, v4) التي توفر أمانًا أفضل وسرعة تحقق أعلى على الأجهزة. التوقيع باستخدام هذه المخططات يحمي محتوى APK بالكامل.

من البناء اليدوي إلى الأتمتة: CMake و Gradle

من الرائع فهم هذه العملية اليدوية، ولكن في التطوير اليومي، تُستخدم أدوات بناء عالية المستوى لأتمتة هذه الخطوات.

  • CMake: هو نظام بناء مفتوح المصدر شائع لبناء الكود الأصلي. عند استخدامه مع أندرويد، يمكنك كتابة ملف CMakeLists.txt بسيط لتحديد ملفات المصدر والمكتبات التي تريد ربطها. يتولى Gradle و NDK بعد ذلك استدعاء clang++ بالأعلام والمسارات الصحيحة.
  • Gradle: هو نظام الأتمتة الرسمي لبناء تطبيقات أندرويد. يقوم بقراءة ملفات build.gradle لتحديد كيفية بناء التطبيق. داخليًا، يقوم Gradle باستدعاء نفس الأدوات التي استخدمناها يدويًا (aapt2, d8/R8, apksigner)، لكنه يدير التبعيات المعقدة، متغيرات البناء (build variants)، والتكوينات الأخرى تلقائيًا.

معرفتك بالعملية اليدوية تمنحك القدرة على فهم ما يفعله Gradle "تحت الغطاء"، مما يساعدك على تشخيص المشاكل عندما تفشل عملية البناء الآلية.

الخلاصة

بعد اكتمال السكربت، يتم إنتاج ملف MyNDKApp-signed.apk النهائي، وهو جاهز للتثبيت على أجهزة أندرويد (استخدم adb install MyNDKApp-signed.apk للتثبيت). من خلال اتباع هذه الخطوات اليدوية، نكون قد أتممنا بنجاح جميع مراحل بناء التطبيق: من ترجمة الكود الأصلي وموارد التطبيق، إلى تجميع وتحويل كود جافا، وأخيرًا تجميع كل المكونات في حزمة واحدة وتأمينها بالتوقيع الرقمي. هذا الفهم العميق ليس مجرد تمرين أكاديمي، بل هو أساس قوي يمكّنك من حل المشكلات المتقدمة وتحسين عمليات البناء في أي مشروع أندرويد.

About

Build a native APK application from C source code using Termux and the Clang compiler.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors