نصائح وأدوات لتحسين تطبيقات Android

نشرت: 2022-03-11

تحتوي أجهزة Android على الكثير من النوى ، لذا فإن كتابة تطبيقات سلسة مهمة بسيطة لأي شخص ، أليس كذلك؟ خاطئ - ظلم - يظلم. نظرًا لأن كل شيء على Android يمكن القيام به بعدة طرق مختلفة ، فقد يكون اختيار الخيار الأفضل أمرًا صعبًا. إذا كنت ترغب في اختيار الطريقة الأكثر فاعلية ، عليك أن تعرف ما يحدث تحت الغطاء. لحسن الحظ ، لا يتعين عليك الاعتماد على مشاعرك أو حاسة الشم ، نظرًا لوجود الكثير من الأدوات التي يمكن أن تساعدك في العثور على الاختناقات من خلال قياس ووصف ما يحدث. تعمل التطبيقات المحسّنة والسلسة بشكل صحيح على تحسين تجربة المستخدم بشكل كبير ، كما تستنزف طاقة بطارية أقل.

دعونا نرى بعض الأرقام أولاً للنظر في مدى أهمية التحسين حقًا. وفقًا لمنشور Nimbledroid ، قام 86٪ من المستخدمين (بمن فيهم أنا) بإلغاء تثبيت التطبيقات بعد استخدامها مرة واحدة فقط بسبب ضعف الأداء. إذا كنت تقوم بتحميل بعض المحتويات ، فلديك أقل من 11 ثانية لعرضها للمستخدم. فقط كل مستخدم ثالث سوف يمنحك المزيد من الوقت. قد تحصل أيضًا على الكثير من المراجعات السيئة على Google Play بسبب ذلك.

إنشاء تطبيقات أفضل: أنماط أداء Android

يعد اختبار صبر المستخدمين اختصارًا لإلغاء التثبيت.
سقسقة

أول شيء يلاحظه كل مستخدم مرارًا وتكرارًا هو وقت بدء تشغيل التطبيق. وفقًا لمنشور Nimbledroid آخر ، من بين أفضل 100 تطبيق ، يبدأ 40 تطبيقًا في أقل من ثانيتين ، ويبدأ 70 تطبيقًا في أقل من 3 ثوانٍ. لذلك ، إذا أمكن ، يجب أن تعرض بعض المحتوى بشكل عام في أسرع وقت ممكن وأن تؤخر عمليات التحقق من الخلفية والتحديثات قليلاً.

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

نصائح حول الأداء

نظرية كافية ، إليك قائمة ببعض الأشياء التي يجب أن تضعها في اعتبارك إذا كان الأداء مهمًا لك.

1. String مقابل StringBuilder

لنفترض أن لديك سلسلة ، ولسبب ما تريد إلحاق المزيد من السلاسل بها 10 آلاف مرة. يمكن أن يبدو الرمز مثل هذا.

 String string = "hello"; for (int i = 0; i < 10000; i++) { string += " world"; }

يمكنك أن ترى على Android Studio Monitors كيف يمكن أن تكون بعض سلاسل السلاسل غير فعالة. هناك الكثير من مجموعات القمامة جارية.

String vs StringBuilder

تستغرق هذه العملية حوالي 8 ثوانٍ على جهازي الجيد إلى حد ما ، والذي يعمل بنظام Android 5.1.1. الطريقة الأكثر فعالية لتحقيق نفس الهدف هي استخدام StringBuilder ، مثل هذا.

 StringBuilder sb = new StringBuilder("hello"); for (int i = 0; i < 10000; i++) { sb.append(" world"); } String string = sb.toString();

يحدث هذا على نفس الجهاز على الفور تقريبًا ، في أقل من 5 مللي ثانية. تكون تصورات وحدة المعالجة المركزية والذاكرة مسطحة تمامًا تقريبًا ، لذا يمكنك تخيل مدى ضخامة هذا التحسين. لاحظ أنه لتحقيق هذا الاختلاف ، كان علينا إلحاق 10 آلاف سلسلة ، وهو ما ربما لا تفعله كثيرًا. لذلك إذا كنت تضيف سلسلتين فقط مرة واحدة ، فلن ترى أي تحسن. بالمناسبة ، إذا قمت بما يلي:

 String string = "hello" + " world";

يتم تحويله داخليًا إلى StringBuilder ، لذلك سيعمل بشكل جيد.

قد تتساءل ، لماذا يكون تسلسل الأوتار بالطريقة الأولى بطيئًا جدًا؟ إنه ناتج عن حقيقة أن السلاسل غير قابلة للتغيير ، لذلك بمجرد إنشائها ، لا يمكن تغييرها. حتى إذا كنت تعتقد أنك تقوم بتغيير قيمة سلسلة ، فأنت تقوم بالفعل بإنشاء سلسلة جديدة بالقيمة الجديدة. في مثال مثل:

 String myString = "hello"; myString += " world";

ما ستحصل عليه في الذاكرة ليس سلسلة واحدة "hello world" ، ولكن في الواقع سلسلتان. ستحتوي سلسلة myString على "hello world" ، كما تتوقع. ومع ذلك ، فإن السلسلة الأصلية بالقيمة "hello" لا تزال حية ، دون أي إشارة إليها ، في انتظار أن يتم جمع القمامة. هذا أيضًا هو السبب الذي يجعلك تخزن كلمات المرور في مصفوفة char بدلاً من سلسلة. إذا قمت بتخزين كلمة مرور كسلسلة ، فستبقى في الذاكرة بتنسيق يمكن للبشر قراءته حتى GC التالية لفترة زمنية غير متوقعة. بالعودة إلى الثبات الموصوف أعلاه ، ستبقى السلسلة في الذاكرة حتى إذا قمت بتعيينها قيمة أخرى بعد استخدامها. ومع ذلك ، إذا قمت بإفراغ مصفوفة char بعد استخدام كلمة المرور ، فستختفي من كل مكان.

2. اختيار نوع البيانات الصحيح

قبل البدء في كتابة التعليمات البرمجية ، يجب أن تقرر أنواع البيانات التي ستستخدمها لمجموعتك. على سبيل المثال ، هل يجب عليك استخدام Vector أو ArrayList ؟ حسنًا ، هذا يعتمد على حالة استخدامك. إذا كنت بحاجة إلى مجموعة خيط آمنة ، والتي ستسمح فقط بخيط واحد في وقت واحد للعمل معها ، فيجب عليك اختيار Vector ، لأنه متزامن. في حالات أخرى ، من المحتمل أن تلتزم ArrayList ، إلا إذا كان لديك سبب محدد لاستخدام المتجهات.

ماذا عن الحالة عندما تريد مجموعة ذات كائنات فريدة؟ حسنًا ، ربما يجب عليك اختيار Set . لا يمكن أن تحتوي على نسخ مكررة حسب التصميم ، لذلك لن تضطر إلى الاعتناء بها بنفسك. هناك أنواع متعددة من المجموعات ، لذا اختر واحدة تناسب حالة الاستخدام الخاصة بك. لمجموعة بسيطة من العناصر الفريدة ، يمكنك استخدام HashSet . إذا كنت تريد الاحتفاظ بترتيب العناصر التي تم إدراجها فيها ، فاختر مجموعة LinkedHashSet . TreeSet بفرز العناصر تلقائيًا ، لذلك لن تضطر إلى استدعاء أي طرق فرز عليها. يجب أيضًا فرز العناصر بكفاءة ، دون الحاجة إلى التفكير في فرز الخوارزميات.

تهيمن البيانات. إذا اخترت هياكل البيانات الصحيحة والأشياء المنظمة جيدًا ، فستكون الخوارزميات دائمًا تقريبًا بديهية. تعتبر هياكل البيانات ، وليس الخوارزميات ، أساسية في البرمجة.
- قواعد البرمجة الخمسة لروب بايك

يعتبر فرز الأعداد الصحيحة أو السلاسل أمرًا بسيطًا جدًا. ومع ذلك ، ماذا لو كنت تريد فرز فئة حسب بعض الممتلكات؟ لنفترض أنك تكتب قائمة بالوجبات التي تتناولها وتخزن أسمائها وطوابعها الزمنية. كيف يمكنك فرز الوجبات حسب الطابع الزمني من الأقل إلى الأعلى؟ لحسن الحظ ، الأمر بسيط جدًا. يكفي تنفيذ الواجهة Comparable في فئة Meal وتجاوز وظيفة compareTo() . لفرز الوجبات حسب الطابع الزمني الأدنى إلى الأعلى ، يمكننا كتابة شيء مثل هذا.

 @Override public int compareTo(Object object) { Meal meal = (Meal) object; if (this.timestamp < meal.getTimestamp()) { return -1; } else if (this.timestamp > meal.getTimestamp()) { return 1; } return 0; }

3. تحديثات الموقع

هناك الكثير من التطبيقات التي تجمع موقع المستخدم. يجب عليك استخدام Google Location Services API لهذا الغرض ، والذي يحتوي على الكثير من الوظائف المفيدة. هناك مقال منفصل عن استخدامه فلن أكرره.

أود فقط التأكيد على بعض النقاط المهمة من منظور الأداء.

بادئ ذي بدء ، استخدم فقط الموقع الأكثر دقة الذي تحتاجه. على سبيل المثال ، إذا كنت تقوم ببعض التنبؤات الجوية ، فلن تحتاج إلى الموقع الأكثر دقة. يعد الحصول على منطقة خشنة للغاية بناءً على الشبكة أسرع وأكثر كفاءة في استخدام البطارية. يمكنك تحقيق ذلك عن طريق تعيين الأولوية إلى LocationRequest.PRIORITY_LOW_POWER .

يمكنك أيضًا استخدام دالة LocationRequest تسمى setSmallestDisplacement() . سيؤدي تعيين هذا بالأمتار إلى عدم إعلام تطبيقك بتغيير الموقع إذا كان أصغر من القيمة المحددة. على سبيل المثال ، إذا كانت لديك خريطة تحتوي على مطاعم قريبة من حولك ، وقمت بتعيين أقل إزاحة على 20 مترًا ، فلن يقدم التطبيق طلبات للتحقق من المطاعم إذا كان المستخدم يتجول في الغرفة. ستكون الطلبات عديمة الفائدة ، حيث لن يكون هناك أي مطعم قريب جديد على أي حال.

القاعدة الثانية هي طلب تحديثات الموقع بقدر ما تحتاج إليها فقط. هذا واضح تماما. إذا كنت تقوم بالفعل بإنشاء تطبيق تنبؤات الطقس هذا ، فلن تحتاج إلى طلب الموقع كل بضع ثوانٍ ، لأنه من المحتمل ألا يكون لديك مثل هذه التنبؤات الدقيقة (اتصل بي إذا كنت تفعل ذلك). يمكنك استخدام وظيفة setInterval() الفاصل الزمني المطلوب الذي سيقوم فيه الجهاز بتحديث تطبيقك حول الموقع. إذا استمرت تطبيقات متعددة في طلب موقع المستخدم ، فسيتم إخطار كل تطبيق في كل تحديث جديد للموقع ، حتى إذا كان لديك مجموعة setInterval() أعلى. لمنع إشعارات تطبيقك كثيرًا ، تأكد دائمًا من تعيين أسرع فترة تحديث باستخدام setFastestInterval() .

وأخيرًا ، القاعدة الثالثة هي طلب تحديثات الموقع فقط إذا كنت في حاجة إليها. إذا كنت تعرض بعض الكائنات القريبة على الخريطة كل x ثانية وكان التطبيق يعمل في الخلفية ، فلن تحتاج إلى معرفة الموقع الجديد. لا يوجد سبب لتحديث الخريطة إذا كان المستخدم لا يمكنه رؤيتها على أي حال. تأكد من التوقف عن الاستماع لتحديثات الموقع عندما يكون ذلك مناسبًا ، ويفضل أن يكون ذلك في onPause() . يمكنك بعد ذلك استئناف التحديثات في onResume() .

4. طلبات الشبكة

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

الثاني هو البطارية. يمكن أن تستهلك كل من شبكات WiFi وشبكات الهاتف المحمول الكثير منها إذا تم استخدامها كثيرًا. لنفترض أنك تريد تنزيل 1 كيلوبايت. لتقديم طلب شبكة ، يجب عليك إيقاظ الراديو الخلوي أو اللاسلكي ، ثم يمكنك تنزيل بياناتك. ومع ذلك ، لن ينام الراديو فورًا بعد العملية. سيبقى في حالة نشطة إلى حد ما لمدة 20-40 ثانية أخرى ، اعتمادًا على جهازك ومشغل شبكة الجوال.

طلبات الشبكة

اذا ماذا تستطيع ان تفعل حيال ذلك؟ حزمة. لتجنب إيقاظ الراديو كل ثانيتين ، قم بإحضار الأشياء التي قد يحتاجها المستخدم في الدقائق القادمة مسبقًا. الطريقة الصحيحة للتجميع ديناميكية للغاية اعتمادًا على التطبيق الخاص بك ، ولكن إذا كان ذلك ممكنًا ، فيجب عليك تنزيل البيانات التي قد يحتاجها المستخدم في 3-4 دقائق القادمة. يمكن للمرء أيضًا تحرير معلمات الدُفعات بناءً على نوع الإنترنت الخاص بالمستخدم ، أو حالة الشحن. على سبيل المثال ، إذا كان المستخدم متصلاً بشبكة WiFi أثناء الشحن ، فيمكنك جلب الكثير من البيانات مسبقًا أكثر مما لو كان المستخدم متصلاً بالإنترنت عبر الهاتف المحمول وبطارية منخفضة. قد يكون أخذ كل هذه المتغيرات في الاعتبار أمرًا صعبًا ، ولن يفعله سوى عدد قليل من الناس. لحسن الحظ ، هناك مدير شبكة GCM لإنقاذ!

GCM Network Manager هي فئة مفيدة حقًا وتحتوي على الكثير من السمات القابلة للتخصيص. يمكنك بسهولة جدولة المهام المتكررة والمهام لمرة واحدة. عند تكرار المهام ، يمكنك تعيين أقل فترة تكرار وكذلك أعلى فترة تكرار. سيسمح هذا بتجميع ليس فقط طلباتك ، ولكن أيضًا الطلبات من التطبيقات الأخرى. يجب إيقاظ الراديو مرة واحدة فقط كل فترة ، وأثناء تشغيله ، تقوم جميع التطبيقات الموجودة في قائمة الانتظار بتنزيل ما يفترض أن تقوم بتحميله وتحميله. يدرك هذا المدير أيضًا نوع شبكة الجهاز وحالة الشحن ، لذا يمكنك الضبط وفقًا لذلك. يمكنك العثور على مزيد من التفاصيل والعينات في هذه المقالة ، أحثك ​​على التحقق منها. مثال على مهمة تبدو كالتالي:

 Task task = new OneoffTask.Builder() .setService(CustomService.class) .setExecutionWindow(0, 30) .setTag(LogService.TAG_TASK_ONEOFF_LOG) .setUpdateCurrent(false) .setRequiredNetwork(Task.NETWORK_STATE_CONNECTED) .setRequiresCharging(false) .build();

بالمناسبة ، منذ Android 3.0 ، إذا قمت بطلب شبكة على الخيط الرئيسي ، فستحصل على NetworkOnMainThreadException . سيحذرك ذلك بالتأكيد من عدم القيام بذلك مرة أخرى.

5. انعكاس

الانعكاس هو قدرة الفئات والكائنات على فحص المنشئات والحقول والأساليب الخاصة بهم وما إلى ذلك. يتم استخدامه عادةً للتوافق مع الإصدارات السابقة ، للتحقق مما إذا كانت طريقة معينة متاحة لإصدار معين من نظام التشغيل. إذا كان عليك استخدام الانعكاس لهذا الغرض ، فتأكد من تخزين الاستجابة مؤقتًا ، لأن استخدام الانعكاس بطيء جدًا. تستخدم بعض المكتبات المستخدمة على نطاق واسع Reflection أيضًا ، مثل Roboguice لحقن التبعية. هذا هو السبب الذي يجعلك تفضل Dagger 2. لمزيد من التفاصيل حول التفكير ، يمكنك التحقق من منشور منفصل.

6. Autoboxing

تعد Autoboxing و unboxing عمليات تحويل نوع بدائي إلى نوع كائن ، أو العكس. في الممارسة العملية ، هذا يعني تحويل int إلى عدد صحيح. لتحقيق ذلك ، يستخدم المترجم الدالة Integer.valueOf() داخليًا. التحويل ليس بطيئًا فحسب ، بل تأخذ الكائنات أيضًا ذاكرة أكثر بكثير من نظيراتها البدائية. دعونا نلقي نظرة على بعض التعليمات البرمجية.

 Integer total = 0; for (int i = 0; i < 1000000; i++) { total += i; }

في حين أن هذا يستغرق 500 مللي ثانية في المتوسط ​​، فإن إعادة كتابته لتجنب autoboxing سيؤدي إلى تسريع العملية بشكل كبير.

 int total = 0; for (int i = 0; i < 1000000; i++) { total += i; }

يعمل هذا الحل في حوالي 2 مللي ثانية ، وهو أسرع 25 مرة. إذا كنت لا تصدقني ، اختبرها. من الواضح أن الأرقام ستكون مختلفة لكل جهاز ، ولكن يجب أن تظل أسرع كثيرًا. وهي أيضًا خطوة بسيطة حقًا للتحسين.

حسنًا ، ربما لا تنشئ متغيرًا من النوع الصحيح مثل هذا كثيرًا. ولكن ماذا عن الحالات التي يصعب تجنبها؟ كما هو الحال في الخريطة ، حيث يتعين عليك استخدام الكائنات ، مثل Map<Integer, Integer> ؟ انظر إلى الحل الذي يستخدمه كثير من الناس.

 Map<Integer, Integer> myMap = new HashMap<>(); for (int i = 0; i < 100000; i++) { myMap.put(i, random.nextInt()); }

يستغرق إدخال 100 كيلو بايت عشوائي في الخريطة حوالي 250 ملي ثانية للتشغيل. انظر الآن إلى الحل باستخدام SparseIntArray.

 SparseIntArray myArray = new SparseIntArray(); for (int i = 0; i < 100000; i++) { myArray.put(i, random.nextInt()); }

يستغرق هذا وقتًا أقل بكثير ، حوالي 50 مللي ثانية. إنها أيضًا إحدى الطرق الأسهل لتحسين الأداء ، حيث لا يلزم القيام بأي شيء معقد ، كما تظل الشفرة قابلة للقراءة. أثناء تشغيل تطبيق واضح باستخدام الحل الأول ، استهلك 13 ميغا بايت من ذاكرتي ، استهلك استخدام ints البدائية شيئًا أقل من 7 ميغا بايت ، لذلك نصفها فقط.

SparseIntArray هي مجرد واحدة من المجموعات الرائعة التي يمكن أن تساعدك على تجنب autoboxing. يمكن استبدال خريطة مثل Map<Integer, Long> بـ SparseLongArray ، لأن قيمة الخريطة من النوع Long . إذا نظرت إلى الكود المصدري لـ SparseLongArray ، فسترى شيئًا مثيرًا للاهتمام. تحت الغطاء ، هو في الأساس مجرد زوج من المصفوفات. يمكنك أيضًا استخدام SparseBooleanArray بالمثل.

إذا قرأت التعليمات البرمجية المصدر ، فربما تكون قد لاحظت ملاحظة تفيد بأن SparseIntArray يمكن أن يكون أبطأ من HashMap . لقد كنت أجرب كثيرًا ، لكن بالنسبة لي ، كان SparseIntArray دائمًا أفضل من حيث الذاكرة والأداء. أعتقد أن اختيارك لا يزال متروكًا لك ، جرب حالات الاستخدام الخاصة بك واعرف أيها يناسبك أكثر. بالتأكيد لديك SparseArrays في رأسك عند استخدام الخرائط.

7. OnDraw

كما قلت أعلاه ، عندما تقوم بتحسين الأداء ، من المحتمل أن ترى أكبر فائدة في تحسين الكود الذي يتم تشغيله كثيرًا. إحدى الوظائف التي تعمل كثيرًا هي onDraw() . قد لا يفاجئك أنه مسؤول عن رسم المناظر على الشاشة. نظرًا لأن الأجهزة تعمل عادةً بمعدل 60 إطارًا في الثانية ، يتم تشغيل الوظيفة 60 مرة في الثانية. يحتوي كل إطار على 16 مللي ثانية ليتم التعامل معه بشكل كامل ، بما في ذلك إعداده ورسمه ، لذلك يجب تجنب الوظائف البطيئة حقًا. يمكن رسم الخيط الرئيسي فقط على الشاشة ، لذا يجب تجنب إجراء عمليات باهظة الثمن عليه. إذا جمدت سلسلة المحادثات الرئيسية لعدة ثوانٍ ، فقد تحصل على مربع حوار "التطبيق لا يستجيب" (ANR) سيئ السمعة. لتغيير حجم الصور وعمل قاعدة البيانات وما إلى ذلك ، استخدم مؤشر ترابط الخلفية.

إذا كنت تعتقد أن المستخدمين لن يلاحظوا هذا الانخفاض في معدل الإطارات ، فأنت مخطئ!
سقسقة

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

من الأشياء التي يجب تجنبها في onDraw() تخصيص كائنات مثل الرسام. جهز كل شيء في المنشئ ، حتى يكون جاهزًا عند الرسم. حتى إذا كنت قد قمت بتحسين onDraw() ، يجب عليك الاتصال به فقط كلما احتجت إلى ذلك. ما هو أفضل من استدعاء وظيفة محسنة؟ حسنًا ، لا تستدعي أي وظيفة على الإطلاق. إذا كنت تريد رسم نص ، فهناك وظيفة مساعدة أنيقة جدًا تسمى drawText() ، حيث يمكنك تحديد أشياء مثل النص والإحداثيات ولون النص.

8. ViewHolders

ربما تعرف هذا ، لكن لا يمكنني تخطيه. نمط تصميم Viewholder هو طريقة لجعل قوائم التمرير أكثر سلاسة. إنه نوع من التخزين المؤقت للعرض ، والذي يمكن أن يقلل بشكل خطير من المكالمات للعثور على findViewById() وتضخيم المشاهدات عن طريق تخزينها. يمكن أن تبدو مثل هذا.

 static class ViewHolder { TextView title; TextView text; public ViewHolder(View view) { title = (TextView) view.findViewById(R.id.title); text = (TextView) view.findViewById(R.id.text); } }

بعد ذلك ، داخل وظيفة getView() الخاصة بالمحول ، يمكنك التحقق مما إذا كان لديك طريقة عرض قابلة للاستخدام. إذا لم يكن كذلك ، قم بإنشاء واحد.

 ViewHolder viewHolder; if (convertView == null) { convertView = inflater.inflate(R.layout.list_item, viewGroup, false); viewHolder = new ViewHolder(convertView); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.title.setText("Hello World");

يمكنك العثور على الكثير من المعلومات المفيدة حول هذا النمط عبر الإنترنت. يمكن استخدامه أيضًا في الحالات التي تحتوي فيها طريقة عرض القائمة على عدة أنواع مختلفة من العناصر ، مثل بعض رؤوس الأقسام.

9. تحجيم الصور

من المحتمل أن يحتوي تطبيقك على بعض الصور. في حال كنت تقوم بتنزيل بعض ملفات JPG من الويب ، فيمكن أن يكون لها دقة عالية حقًا. ومع ذلك ، فإن الأجهزة التي سيتم عرضها عليها ستكون أصغر كثيرًا. حتى إذا التقطت صورة بكاميرا جهازك ، فيجب تصغير حجمها قبل عرضها لأن دقة الصورة أكبر بكثير من دقة الشاشة. يعد تغيير حجم الصور قبل عرضها أمرًا بالغ الأهمية. إذا حاولت عرضها بدقة كاملة ، فستنفد الذاكرة بسرعة كبيرة. هناك الكثير مكتوبًا حول عرض الصور النقطية بكفاءة في مستندات Android ، سأحاول تلخيصها.

إذن لديك صورة نقطية ، لكنك لا تعرف أي شيء عنها. هناك علامة مفيدة للصور النقطية تسمى inJustDecodeBounds في خدمتك ، والتي تتيح لك معرفة دقة الصورة النقطية. لنفترض أن الصورة النقطية الخاصة بك هي 1024 × 768 وأن ImageView المستخدم لعرضها هو 400 × 300 فقط. يجب أن تستمر في قسمة دقة الصورة النقطية على 2 حتى تظل أكبر من ImageView المحددة. إذا قمت بذلك ، فستختزل الصورة النقطية بمعامل 2 ، مما يمنحك صورة نقطية بحجم 512 × 384. تستخدم الصورة النقطية المصغرة ذاكرة أقل 4 مرات ، مما سيساعدك كثيرًا في تجنب خطأ OutOfMemory الشهير.

الآن بعد أن عرفت كيفية القيام بذلك ، يجب ألا تفعل ذلك. ... على الأقل ، ليس إذا كان تطبيقك يعتمد بشكل كبير على الصور ، وليس مجرد صورة أو صورتين. بالتأكيد تجنب أشياء مثل تغيير حجم الصور وإعادة تدويرها يدويًا ، استخدم بعض مكتبات الطرف الثالث لذلك. أشهرها Picasso by Square أو Universal Image Loader أو Fresco by Facebook أو المفضل لدي ، Glide. يوجد مجتمع نشط ضخم من المطورين حوله ، لذا يمكنك العثور على الكثير من الأشخاص المفيدين في قسم المشكلات على GitHub أيضًا.

10. الوضع الصارم

Strict Mode هي أداة مطور مفيدة للغاية لا يعرفها الكثير من الناس. يتم استخدامه عادةً لاكتشاف طلبات الشبكة أو الوصول إلى القرص من السلسلة الرئيسية. يمكنك تعيين المشكلات التي يجب أن يبحث عنها الوضع الصارم والعقوبة التي يجب أن يؤديها. تبدو عينة google كما يلي:

 public void onCreate() { if (DEVELOPER_MODE) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() .penaltyLog() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() .penaltyLog() .penaltyDeath() .build()); } super.onCreate(); }

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

أداء التصحيح: الطريقة الاحترافية

دعنا الآن نرى بعض الأدوات التي يمكن أن تساعدك في العثور على الاختناقات ، أو على الأقل إظهار أن هناك خطأ ما.

1. مراقب أندرويد

هذه أداة مضمنة في Android Studio. بشكل افتراضي ، يمكنك العثور على Android Monitor في الزاوية اليسرى السفلية ، ويمكنك التبديل بين علامتي تبويب هناك. لوجكات وشاشات. يحتوي قسم الشاشات على 4 رسوم بيانية مختلفة. الشبكة ووحدة المعالجة المركزية ووحدة معالجة الرسومات والذاكرة. إنها تشرح نفسها بنفسها ، لذا سأراجعها بسرعة. فيما يلي لقطة شاشة للرسوم البيانية التي تم التقاطها أثناء تحليل بعض ملفات JSON أثناء تنزيلها.

مراقب أندرويد

يعرض جزء الشبكة حركة المرور الواردة والصادرة بالكيلو بايت / ثانية. يعرض جزء وحدة المعالجة المركزية استخدام وحدة المعالجة المركزية بالنسبة المئوية. تعرض شاشة GPU مقدار الوقت المستغرق لعرض إطارات نافذة واجهة المستخدم. هذه هي الشاشة الأكثر تفصيلاً من بين هؤلاء الأربعة ، لذا إذا كنت تريد مزيدًا من التفاصيل حولها ، فاقرأ هذا.

أخيرًا ، لدينا شاشة الذاكرة ، والتي من المحتمل أن تستخدمها أكثر من غيرها. بشكل افتراضي ، يعرض المقدار الحالي للذاكرة المجانية والمخصصة. يمكنك إجبار مجموعة القمامة باستخدامها أيضًا ، لاختبار ما إذا كان مقدار الذاكرة المستخدمة ينخفض. يحتوي على ميزة مفيدة تسمى Dump Java Heap ، والتي ستنشئ ملف HPROF يمكن فتحه باستخدام HPROF Viewer and Analyzer. سيمكنك ذلك من معرفة عدد العناصر التي قمت بتخصيصها ، ومقدار الذاكرة التي يشغلها ، وربما الأشياء التي تسبب تسرب الذاكرة. تعلم كيفية استخدام هذا المحلل ليس أبسط مهمة هناك ، لكنه يستحق ذلك. الشيء التالي الذي يمكنك القيام به مع Memory Monitor هو القيام ببعض تتبع التخصيص الموقوت ، والذي يمكنك البدء فيه والتوقف كما يحلو لك. يمكن أن يكون مفيدًا في كثير من الحالات ، على سبيل المثال عند التمرير أو تدوير الجهاز.

2. GPU Overdraw

هذه أداة مساعدة بسيطة ، يمكنك تنشيطها في خيارات المطور بمجرد تمكين وضع المطور. حدد Debug GPU overdraw ، "إظهار مناطق السحب الزائد" ، وستحصل شاشتك على بعض الألوان الغريبة. لا بأس ، هذا ما يفترض أن يحدث. تعني الألوان عدد المرات التي تم فيها رسم منطقة معينة بشكل زائد. يعني اللون الحقيقي عدم وجود تجاوزات ، وهذا ما يجب أن تهدف إليه. الأزرق يعني رسم زائد واحد ، والأخضر يعني اثنين ، والوردي ثلاثة ، والأحمر أربعة.

GPU Overdraw

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

3. تقديم GPU

هذه أداة أخرى من خيارات Developer ، تسمى Profile GPU rendering. عند تحديده ، اختر "على الشاشة كأشرطة". ستلاحظ ظهور بعض الأشرطة الملونة على شاشتك. نظرًا لأن كل تطبيق يحتوي على أشرطة منفصلة ، فمن الغريب أن شريط الحالة له أشرطة خاصة به ، وفي حالة وجود أزرار تنقل بالبرنامج ، فإن لديهم أشرطة خاصة بهم أيضًا. على أي حال ، يتم تحديث الأشرطة أثناء تفاعلك مع الشاشة.

تقديم GPU

تتكون الأشرطة من 3-4 ألوان ، ووفقًا لمستندات Android ، فإن حجمها مهم حقًا. الأصغر ، كان ذلك أفضل. يوجد في الجزء السفلي اللون الأزرق ، والذي يمثل الوقت المستخدم لإنشاء وتحديث قوائم عرض طريقة العرض. إذا كان هذا الجزء طويلاً جدًا ، فهذا يعني أن هناك الكثير من رسم العرض المخصص ، أو أن هناك الكثير من العمل المنجز في وظائف onDraw() . إذا كان لديك Android 4.0+ ، فسترى شريطًا أرجوانيًا فوق الشريط الأزرق. يمثل هذا الوقت المنقضي في نقل الموارد إلى سلسلة الرسائل. ثم يأتي الجزء الأحمر ، والذي يمثل الوقت الذي يقضيه عارض Android ثنائي الأبعاد في إصدار أوامر لـ OpenGL لرسم وإعادة رسم قوائم العرض. يوجد في الأعلى الشريط البرتقالي ، والذي يمثل الوقت الذي تنتظر فيه وحدة المعالجة المركزية GPU لإنهاء عملها. إذا كان طويلاً جدًا ، فإن التطبيق يقوم بالكثير من العمل على وحدة معالجة الرسومات.

إذا كنت جيدًا بما يكفي ، فهناك لون آخر فوق البرتقالي. إنه خط أخضر يمثل عتبة 16 مللي ثانية. نظرًا لأن هدفك يجب أن يكون تشغيل تطبيقك بمعدل 60 إطارًا في الثانية ، فلديك 16 مللي ثانية لرسم كل إطار. إذا لم تقم بذلك ، فقد يتم تخطي بعض الإطارات ، وقد يصبح التطبيق متشنجًا ، وسيلاحظ المستخدم ذلك بالتأكيد. انتبه بشكل خاص للرسوم المتحركة والتمرير ، فهذا هو المكان الأكثر أهمية للنعومة. على الرغم من أنه يمكنك اكتشاف بعض الإطارات التي تم تخطيها باستخدام هذه الأداة ، إلا أنها لن تساعدك حقًا في معرفة مكان المشكلة بالضبط.

4. عارض التسلسل الهرمي

هذه إحدى الأدوات المفضلة لدي ، لأنها قوية حقًا. يمكنك بدء تشغيله من Android Studio من خلال الأدوات -> Android -> Android Device Monitor ، أو أنه موجود أيضًا في مجلد sdk / tools الخاص بك باعتباره "شاشة". يمكنك أيضًا العثور على برنامج hierarachyviewer قابل للتنفيذ هناك ، ولكن نظرًا لأنه تم إيقافه ، يجب عليك فتح الشاشة. كيفما تفتح Android Device Monitor ، قم بالتبديل إلى منظور Hierarchy Viewer. إذا كنت لا ترى أي تطبيقات قيد التشغيل مخصصة لجهازك ، فهناك بعض الأشياء التي يمكنك القيام بها لإصلاحها. حاول أيضًا التحقق من سلسلة المشكلات هذه ، فهناك أشخاص لديهم جميع أنواع المشكلات وجميع أنواع الحلول. شيء ما يجب أن يعمل من أجلك أيضًا.

باستخدام Hierarchy Viewer ، يمكنك الحصول على نظرة عامة رائعة حقًا على التسلسلات الهرمية لطرق العرض (من الواضح). إذا رأيت كل تخطيط في XML منفصل ، فقد تكتشف بسهولة طرق العرض غير المفيدة. ومع ذلك ، إذا واصلت الجمع بين التخطيطات ، فقد يصبح الأمر مربكًا بسهولة. أداة مثل هذه تجعل من السهل تحديد ، على سبيل المثال ، بعض RelativeLayout ، التي لديها طفل واحد فقط ، و RelativeLayout آخر. هذا يجعل واحد منهم قابل للإزالة.

تجنب استدعاء requestLayout() ، لأنه يتسبب في اجتياز التسلسل الهرمي لطريقة العرض بالكامل ، لمعرفة الحجم الذي يجب أن يكون عليه كل عرض. إذا كان هناك بعض التعارض مع القياسات ، فقد يتم اجتياز التسلسل الهرمي عدة مرات ، والذي إذا حدث أثناء بعض الرسوم المتحركة ، فإنه سيؤدي بالتأكيد إلى تخطي بعض الإطارات. إذا كنت ترغب في معرفة المزيد حول كيفية رسم Android لآرائه ، فيمكنك قراءة هذا. لنلق نظرة على عرض واحد كما هو موضح في Hierarchy Viewer.

عارض التسلسل الهرمي

يحتوي الركن الأيمن العلوي على زر لتكبير معاينة العرض المعين في نافذة قائمة بذاتها. تحته يمكنك أيضًا مشاهدة المعاينة الفعلية للعرض في التطبيق. العنصر التالي هو رقم يمثل عدد الأطفال في طريقة العرض المحددة ، بما في ذلك طريقة العرض نفسها. إذا حددت عقدة (يفضل أن تكون الجذر) وضغطت على "الحصول على أوقات التخطيط" (3 دوائر ملونة) ، فسيكون لديك 3 قيم أخرى مملوءة ، جنبًا إلى جنب مع الدوائر الملونة التي تظهر مقياسًا وتخطيطًا ورسمًا معنونًا. قد لا يكون صادمًا أن تمثل مرحلة القياس الوقت الذي استغرقته لقياس العرض المعطى. تدور مرحلة التخطيط حول وقت العرض ، بينما يمثل الرسم عملية الرسم الفعلية. هذه القيم والألوان مرتبطة ببعضها البعض. يعني اللون الأخضر أن العرض يتم عرضه في أعلى 50٪ من جميع طرق العرض في الشجرة. يعني اللون الأصفر التقديم في نسبة 50٪ أبطأ من جميع المشاهدات في الشجرة ، ويعني اللون الأحمر أن العرض المعطى هو الأبطأ. نظرًا لأن هذه القيم نسبية ، فسيكون هناك دائمًا قيم حمراء. لا يمكنك ببساطة تجنبهم.

تحت القيم ، يكون لديك اسم الفئة ، مثل "TextView" ، ومعرف العرض الداخلي للكائن ، و android: id للعرض ، والذي قمت بتعيينه في ملفات XML. أحثك على اكتساب عادة إضافة المعرفات إلى جميع الملفات الشخصية ، حتى لو لم تقم بالإشارة إليها في الشفرة. سيجعل تحديد طرق العرض في Hierarchy Viewer أمرًا بسيطًا حقًا ، وفي حالة إجراء اختبارات تلقائية في مشروعك ، فسيؤدي ذلك أيضًا إلى جعل استهداف العناصر أسرع كثيرًا. سيوفر ذلك بعض الوقت لك ولزملائك في كتابتها. تعد إضافة المعرفات إلى العناصر المضافة في ملفات XML واضحة جدًا. ولكن ماذا عن العناصر المضافة ديناميكيًا؟ حسنًا ، اتضح أنه بسيط حقًا أيضًا. ما عليك سوى إنشاء ملف ids.xml داخل مجلد القيم واكتب الحقول المطلوبة. يمكن أن تبدو كالتالي:

 <resources> <item name="item_title" type="id"/> <item name="item_body" type="id"/> </resources>

ثم في الكود ، يمكنك استخدام setId(R.id.item_title) . لا يمكن أن يكون أبسط.

هناك شيئان آخران يجب الانتباه إليهما عند تحسين واجهة المستخدم. يجب أن تتجنب بشكل عام التسلسلات الهرمية العميقة بينما تفضل التدرجات الضحلة ، وربما العريضة. لا تستخدم التنسيقات التي لا تحتاجها. على سبيل المثال ، يمكنك على الأرجح استبدال مجموعة LinearLayouts المتداخلة إما بـ RelativeLayout أو TableLayout . لا تتردد في تجربة تخطيطات مختلفة ، لا تستخدم دائمًا LinearLayout و RelativeLayout . حاول أيضًا إنشاء بعض طرق العرض المخصصة عند الحاجة ، يمكن أن تحسن الأداء بشكل ملحوظ إذا تم إجراؤها بشكل جيد. على سبيل المثال ، هل تعلم أن Instagram لا يستخدم TextViews لعرض التعليقات؟

يمكنك العثور على مزيد من المعلومات حول Hierarchy Viewer على موقع Android Developers مع وصف لأجزاء مختلفة ، باستخدام أداة Pixel Perfect ، وما إلى ذلك. هناك شيء آخر أود أن أشير إليه وهو التقاط العروض في ملف .psd ، والذي يمكن القيام به عن طريق زر "التقاط طبقات النافذة". ستكون كل طريقة عرض في طبقة منفصلة ، لذلك من السهل حقًا إخفاءها أو تغييرها في Photoshop أو GIMP. أوه ، هذا سبب آخر لإضافة معرف إلى كل عرض يمكنك. ستجعل الطبقات لها أسماء منطقية بالفعل.

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

يحتوي موقع مطوري Android على مجموعة من أفضل الممارسات للأداء. إنها تغطي الكثير من المجالات المختلفة ، بما في ذلك إدارة الذاكرة ، والتي لم أتحدث عنها حقًا. لقد تجاهلت الأمر بصمت ، لأن التعامل مع الذاكرة وتتبع تسريبات الذاكرة قصة منفصلة تمامًا. سيساعد استخدام مكتبة جهة خارجية لعرض الصور بكفاءة كثيرًا ، ولكن إذا كنت لا تزال تواجه مشكلات في الذاكرة ، فتحقق من Leak canary من صنع Square ، أو اقرأ هذا.

تغليف

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

تأكد من أنك تتعلم من أفضل المطورين والشركات الكبرى. يمكنك العثور على بضع مئات من المدونات الهندسية على هذا الرابط. من الواضح أنها ليست مجرد أشياء متعلقة بنظام Android ، لذلك إذا كنت مهتمًا فقط بنظام Android ، فعليك تصفية المدونة المحددة. أوصي بشدة بمدونات Facebook و Instagram. على الرغم من أن واجهة مستخدم Instagram على Android مشكوك فيها ، إلا أن مدونتهم الهندسية تحتوي على بعض المقالات الرائعة حقًا. إنه لأمر رائع بالنسبة لي أنه من السهل جدًا رؤية كيفية إنجاز الأشياء في الشركات التي تتعامل مع مئات الملايين من المستخدمين يوميًا ، لذا فإن عدم قراءة مدوناتهم يبدو أمرًا جنونيًا. يتغير العالم بسرعة كبيرة ، لذا إذا كنت لا تحاول باستمرار أن تتحسن وتتعلم من الآخرين وتستخدم أدوات جديدة ، فسوف تتخلف عن الركب. كما قال مارك توين ، الشخص الذي لا يقرأ لا يتمتع بميزة على من لا يستطيع القراءة.