ধীরে রেন্ডারিং

UI রেন্ডারিং হল আপনার অ্যাপ থেকে একটি ফ্রেম তৈরি করা এবং এটি স্ক্রিনে দেখানোর কাজ। আপনার অ্যাপের সাথে ব্যবহারকারীর ইন্টার‍্যাকশন যাতে মসৃণ হয় তা নিশ্চিত করতে, প্রতি সেকেন্ডে ৬০ ফ্রেম (fps) পেতে আপনার অ্যাপকে ১৬ মিলিসেকেন্ডের কম সময়ে ফ্রেম রেন্ডার করতে হবে। কেন ৬০ fps-কে বেশি পছন্দ করা হয় তা বুঝতে, Android পারফর্ম্যান্স প্যাটার্ন: কেন ৬০fps? দেখুন। আপনি যদি ৯০ fps অর্জন করার চেষ্টা করেন, তাহলে এই উইন্ডোটি ১১ms-এ নেমে আসে এবং ১২০ fps-এর জন্য এটি ৮ms হয়।

আপনি যদি এই উইন্ডোটি ১ মিলি সেকেন্ডের বেশি সময় ধরে চালান, তাহলে এর অর্থ এই নয় যে ফ্রেমটি ১ মিলি সেকেন্ড দেরিতে দেখানো হয়েছে, তবে Choreographer ফ্রেমটি সম্পূর্ণভাবে ড্রপ করে। আপনার অ্যাপের UI রেন্ডারিং ধীরগতির হলে, সিস্টেম ফ্রেম এড়িয়ে যেতে বাধ্য হয় এবং ব্যবহারকারী আপনার অ্যাপে স্টাটারিং দেখতে পান। একে জ্যাঙ্ক বলা হয়। এই পৃষ্ঠায় জ্যাঙ্ক কীভাবে ডায়াগনসিস ও সমাধান করতে হয় তা দেখানো হয়েছে।

আপনি যদি এমন গেম ডেভেলপ করেন যা View সিস্টেম ব্যবহার করে না, তাহলে আপনি Choreographer বাইপাস করেন। এই ক্ষেত্রে Frame Pacing Library, OpenGL এবং Vulkan গেমকে Android-এ মসৃণ রেন্ডারিং এবং সঠিক ফ্রেম পেসিং অর্জন করতে সাহায্য করে।

জ্যাঙ্ক শনাক্ত করুন

আপনার অ্যাপে জ্যাঙ্ক সৃষ্টি করছে এমন কোড খুঁজে বের করা কঠিন হতে পারে। এই বিভাগে জ্যাঙ্ক শনাক্ত করার তিনটি পদ্ধতি বর্ণনা করা হয়েছে:

ভিজ্যুয়াল ইন্সপেকশন আপনাকে কয়েক মিনিটের মধ্যে আপনার অ্যাপের সব ব্যবহারের কেস রান করতে দেয়, তবে এটি Systrace-এর মতো বিস্তারিত তথ্য প্রদান করে না। Systrace আরও বিস্তারিত তথ্য প্রদান করে, তবে আপনি যদি আপনার অ্যাপের সবকটি ব্যবহারের ক্ষেত্রে Systrace চালান, তাহলে আপনি এত বেশি ডেটা পাবেন যে তা বিশ্লেষণ করা কঠিন হতে পারে। ভিজ্যুয়াল ইন্সপেকশন এবং Systrace, দুটিই আপনার লোকাল ডিভাইসে জ্যাঙ্ক শনাক্ত করে। আপনি যদি লোকাল ডিভাইসে জ্যাঙ্ক রিপ্রোডিউস করতে না পারেন, তাহলে ফিল্ডে রান করা ডিভাইসে আপনার অ্যাপের নির্দিষ্ট অংশ পরিমাপ করার জন্য কাস্টম পারফর্ম্যান্স মনিটরিং তৈরি করতে পারেন।

ভিজ্যুয়াল পরিদর্শন

ভিজ্যুয়াল ইন্সপেকশন আপনাকে সেইসব ব্যবহারের কেস শনাক্ত করতে সাহায্য করে যেগুলি জ্যাঙ্ক তৈরি করছে। ভিজ্যুয়াল ইনস্পেকশন করতে, আপনার অ্যাপ খুলুন এবং ম্যানুয়ালি আপনার অ্যাপের বিভিন্ন অংশ চেক করে দেখুন এবং আপনার UI-তে জ্যাঙ্ক খুঁজুন।

ভিজ্যুয়াল ইন্সপেকশন করার জন্য এখানে কিছু পরামর্শ দেওয়া হল:

  • আপনার অ্যাপের একটি রিলিজ—বা অন্তত নন-ডিবাগেবল—ভার্সন চালান। ART রানটাইম ডিবাগিং ফিচারকে সাপোর্ট করার জন্য বেশ কিছু গুরুত্বপূর্ণ অপ্টিমাইজেশন বন্ধ করে দেয়, তাই আপনি যা দেখছেন তা যেন ব্যবহারকারীর দেখার মতো হয় তা নিশ্চিত করুন।
  • প্রোফাইল GPU রেন্ডারিং চালু করুন। প্রোফাইল GPU রেন্ডারিং স্ক্রিনে বার দেখায় যা আপনাকে ১৬-এমএস-প্রতি-ফ্রেম বেঞ্চমার্কের তুলনায় UI উইন্ডোর ফ্রেম রেন্ডার করতে কত সময় লাগে তার একটি ভিজ্যুয়াল প্রতিনিধিত্ব দেয়। প্রতিটি বারে রঙিন কম্পোনেন্ট আছে যা রেন্ডারিং পাইপলাইনের একটি স্টেজে ম্যাপ করে, যাতে আপনি দেখতে পারেন কোন অংশটি সবচেয়ে বেশি সময় নিচ্ছে। যেমন, ফ্রেম যদি ইনপুট ম্যানেজ করতে অনেক সময় নেয়, তাহলে আপনার অ্যাপের যে কোড ব্যবহারকারীর ইনপুট ম্যানেজ করে সেটি দেখুন।
  • জ্যাঙ্কের সাধারণ সোর্স হিসেবে পরিচিত কম্পোনেন্টগুলি চেক করুন, যেমন RecyclerView।
  • কোল্ড স্টার্ট থেকে অ্যাপ লঞ্চ করুন।
  • সমস্যাটি আরও ভালভাবে বুঝতে, আপনার অ্যাপটি একটি ধীরগতির ডিভাইসে চালান।

আপনি যখন এমন ব্যবহারের কেস খুঁজে পান যা জ্যাঙ্ক তৈরি করে, তখন আপনার অ্যাপে কী কারণে জ্যাঙ্ক হচ্ছে সে সম্পর্কে আপনার একটি ভাল ধারণা থাকতে পারে। আপনার আরও তথ্যের প্রয়োজন হলে, আপনি কারণটি আরও খতিয়ে দেখতে Systrace ব্যবহার করতে পারেন।

Systrace

Systrace হল এমন একটি টুল যা পুরো ডিভাইস কী করছে তা দেখায়, তবে এটি আপনার অ্যাপে জ্যাঙ্ক শনাক্ত করার জন্য উপযোগী হতে পারে। Systrace-এ ন্যূনতম সিস্টেম ওভারহেড আছে, তাই আপনি ইন্সট্রুমেন্টেশনের সময় বাস্তবসম্মত জ্যাঙ্কিনেস অনুভব করতে পারেন।

আপনার ডিভাইসে জ্যাঙ্কি ব্যবহারের কেস পারফর্ম করার সময় Systrace-এর মাধ্যমে ট্রেস রেকর্ড করুন। Systrace কীভাবে ব্যবহার করবেন সেই সংক্রান্ত নির্দেশাবলীর জন্য, কমান্ড লাইনে সিস্টেম ট্রেস ক্যাপচার করুন দেখুন। Systrace-কে প্রসেস ও থ্রেড অনুযায়ী বিভক্ত করা হয়। Systrace-এ আপনার অ্যাপের প্রসেস খুঁজুন, যা দেখতে অনেকটা ছবি ১-এর মতো।

Systrace-এর উদাহরণ
ছবি ১. Systrace-এর উদাহরণ।

ছবি ১-এ Systrace-এর উদাহরণে জ্যাঙ্ক শনাক্ত করার জন্য নিম্নলিখিত তথ্য রয়েছে:

  1. Systrace দেখায় যে প্রতিটি ফ্রেম কখন আঁকা হয়েছে এবং ধীরে রেন্ডার করার মোট সময় হাইলাইট করতে প্রতিটি ফ্রেমকে কালার কোড করে। এটি আপনাকে ভিজ্যুয়াল ইন্সপেকশনের চেয়ে আরও নির্ভুলভাবে আলাদা আলাদা জ্যাঙ্কি ফ্রেম খুঁজে পেতে সাহায্য করে। আরও তথ্যের জন্য, UI ফ্রেম ও অ্যালার্ট পরীক্ষা করুন।
  2. Systrace আপনার অ্যাপে সমস্যা শনাক্ত করে এবং আলাদা আলাদা ফ্রেম ও সতর্কতা প্যানেল, দুটিতেই সতর্কতা দেখায়। সতর্কতায় দেওয়া নির্দেশাবলী অনুসরণ করা সবচেয়ে ভালো।
  3. Android ফ্রেমওয়ার্ক ও লাইব্রেরির কিছু অংশে, যেমন RecyclerView-এ ট্রেস মার্কার থাকে। তাই, systrace টাইমলাইন দেখায় যে কখন সেইসব পদ্ধতি UI থ্রেডে এক্সিকিউট করা হয় এবং এক্সিকিউট করতে কত সময় লাগে।

Systrace আউটপুট দেখার পরে, আপনার অ্যাপে এমন কিছু পদ্ধতি থাকতে পারে যা আপনার সন্দেহ অনুযায়ী জ্যাঙ্ক সৃষ্টি করছে। যেমন, টাইমলাইন যদি দেখায় যে RecyclerView-এর অনেক সময় নেওয়ার কারণে ফ্রেম স্লো হয়ে গেছে, তাহলে আপনি প্রাসঙ্গিক কোডে কাস্টম ট্রেস ইভেন্ট যোগ করতে এবং আরও তথ্যের জন্য Systrace আবার চালাতে পারেন। নতুন Systrace-এ, টাইমলাইন দেখায় আপনার অ্যাপের পদ্ধতি কখন কল করা হয় এবং সেগুলি কার্যকর করতে কতক্ষণ সময় লাগে।

UI থ্রেড কাজ করতে কেন অনেক সময় নিচ্ছে সেই সম্পর্কে Systrace আপনাকে বিস্তারিত তথ্য না দেখালে, স্যাম্পেল করা বা ইনস্ট্রুমেন্ট করা মেথড ট্রেস রেকর্ড করতে Android CPU Profiler ব্যবহার করুন। সাধারণত, মেথড ট্রেস জ্যাঙ্ক শনাক্ত করার জন্য উপযুক্ত নয় কারণ এটি অতিরিক্ত ওভারহেডের কারণে ফলস-পজিটিভ জ্যাঙ্ক তৈরি করে এবং থ্রেড কখন চলছে বনাম ব্লক করা আছে তা দেখতে পারে না। তবে, মেথড ট্রেস আপনাকে অ্যাপের সেইসব মেথড শনাক্ত করতে সাহায্য করতে পারে যেগুলি সবচেয়ে বেশি সময় নিচ্ছে। এইসব পদ্ধতি শনাক্ত করার পরে, ট্রেস মার্কার যোগ করুন এবং Systrace আবার রান করে দেখুন এইসব পদ্ধতির কারণে জ্যাঙ্ক হচ্ছে কিনা।

আরও তথ্যের জন্য, Systrace বোঝা দেখুন।

কাস্টম পারফর্ম্যান্স মনিটরিং

আপনি যদি কোনও লোকাল ডিভাইসে জ্যাঙ্ক রিপ্রোডিউস করতে না পারেন, তাহলে ফিল্ডে থাকা ডিভাইসে জ্যাঙ্কের সোর্স শনাক্ত করতে সাহায্য করার জন্য আপনার অ্যাপে কাস্টম পারফর্ম্যান্স মনিটরিং তৈরি করতে পারেন।

এটি করতে, FrameMetricsAggregator-এর সাহায্যে আপনার অ্যাপের নির্দিষ্ট অংশ থেকে ফ্রেম রেন্ডার করার মোট সময় সংগ্রহ করুন এবং Firebase পারফর্ম্যান্স মনিটরিং ব্যবহার করে ডেটা রেকর্ড ও বিশ্লেষণ করুন।

আরও জানতে, Android-এর জন্য পারফর্ম্যান্স মনিটরিং শুরু করুন দেখুন।

ফ্রোজেন ফ্রেম

ফ্রোজেন ফ্রেম হল এমন UI ফ্রেম যা রেন্ডার করতে ৭০০ মিলিসেকেন্ডের বেশি সময় লাগে। এটি একটি সমস্যা কারণ ফ্রেম রেন্ডার করার সময় প্রায় এক সেকেন্ডের জন্য আপনার অ্যাপটি হ্যাং হয়ে গেছে এবং ব্যবহারকারীর ইনপুটে সাড়া দিচ্ছে না বলে মনে হচ্ছে। আমরা সাজেস্ট করি যে, মসৃণ UI নিশ্চিত করতে ১৬ মিলিসেকেন্ডের মধ্যে ফ্রেম রেন্ডার করার জন্য অ্যাপ অপ্টিমাইজ করুন। তবে, অ্যাপ চালু করার সময় বা অন্য স্ক্রিনে ট্রানজিশন করার সময়, প্রাথমিক ফ্রেম আঁকতে ১৬ মিলিসেকেন্ডের বেশি সময় লাগা স্বাভাবিক, কারণ আপনার অ্যাপকে ভিউ ইনফ্লেট করতে, স্ক্রিন লে-আউট করতে এবং একদম শুরু থেকে প্রাথমিক ড্র পারফর্ম করতে হয়। সেই জন্য, Android ধীরে রেন্ডারিং থেকে আলাদাভাবে ফ্রোজেন ফ্রেম ট্র্যাক করে। আপনার অ্যাপের কোনও ফ্রেম রেন্ডার করতে ৭০০ মিলিসেকেন্ডের বেশি সময় লাগা উচিত নয়।

ফ্রোজেন ফ্রেম হল ধীরে রেন্ডারিংয়ের একটি চরম রূপ, তাই সমস্যাটি ডায়াগনসিস ও সমাধান করার পদ্ধতি একই।

ট্র্যাকিং জ্যাঙ্ক

Perfetto-তে FrameTimeline ধীরগতির বা ফ্রোজেন ফ্রেম ট্র্যাক করতে সাহায্য করতে পারে।

স্লো ফ্রেম, ফ্রোজেন ফ্রেম ও ANR-এর মধ্যে সম্পর্ক

স্লো ফ্রেম, ফ্রোজেন ফ্রেম এবং ANR হল বিভিন্ন ধরনের জ্যাঙ্ক যা আপনার অ্যাপে দেখা যেতে পারে। পার্থক্য বুঝতে নিচের টেবিলটি দেখুন।

স্লো ফ্রেম ফ্রোজেন ফ্রেম ANRগুলি
রেন্ডার করার সময় ১৬ মিলি সেকেন্ড থেকে ৭০০ মিলি সেকেন্ডের মধ্যে ৭০০ মিলি সেকেন্ড থেকে ৫ সেকেন্ডের মধ্যে ৫ সেকেন্ডের বেশি
ব্যবহারকারীর উপর প্রভাবের দৃশ্যমান ক্ষেত্র
  • RecyclerView স্ক্রল ঠিকভাবে কাজ করছে না
  • জটিল অ্যানিমেশন সহ স্ক্রিনে সঠিকভাবে অ্যানিমেট হচ্ছে না
  • অ্যাপ স্টার্ট-আপের সময়
  • এক স্ক্রিন থেকে অন্য স্ক্রিনে যাওয়া—যেমন, স্ক্রিন ট্রানজিশন করা
  • আপনার অ্যাক্টিভিটি ফোরগ্রাউন্ডে চলাকালীন, আপনার অ্যাপ কোনও ইনপুট ইভেন্ট বা BroadcastReceiver-এর উত্তর দেয়নি—যেমন কী প্রেস করা বা স্ক্রিন ট্যাপ ইভেন্ট—পাঁচ সেকেন্ডের মধ্যে।
  • আপনার কোনও অ্যাক্টিভিটি ফোরগ্রাউন্ডে না থাকলেও, আপনার BroadcastReceiver যথেষ্ট সময়ের মধ্যে এক্সিকিউট করা শেষ করেনি।

স্লো ফ্রেম ও ফ্রোজেন ফ্রেম আলাদাভাবে ট্র্যাক করুন

অ্যাপ চালু করার সময় বা অন্য স্ক্রিনে ট্রানজিশন করার সময়, প্রাথমিক ফ্রেম আঁকতে ১৬ মিলিসেকেন্ডের বেশি সময় লাগা স্বাভাবিক, কারণ অ্যাপকে ভিউ ইনফ্লেট করতে, স্ক্রিন লে-আউট করতে এবং স্ক্র্যাচ থেকে প্রাথমিক ড্র করতে হয়।

জ্যাঙ্ককে অগ্রাধিকার দেওয়া ও সমাধান করার পেশাদার পদ্ধতি

আপনার অ্যাপে জ্যাঙ্ক সংক্রান্ত সমস্যার সমাধান করার সময় নিম্নলিখিত পেশাদার পদ্ধতিগুলি মনে রাখবেন:

  • জ্যাঙ্কের সবচেয়ে সহজে রিপ্রোডিউস করা যায় এমন ইনস্ট্যান্স শনাক্ত ও সমাধান করুন।
  • ANR-কে অগ্রাধিকার দিন। স্লো বা ফ্রোজেন ফ্রেমের কারণে অ্যাপের পারফর্ম্যান্স ধীরগতির মনে হতে পারে, তবে ANR-এর কারণে অ্যাপ কাজ করা বন্ধ করে দেয়।
  • ধীরে রেন্ডারিং আবার তৈরি করা কঠিন, তবে আপনি ৭০০ মিলি সেকেন্ডের ফ্রোজেন ফ্রেম বন্ধ করে শুরু করতে পারেন। অ্যাপ চালু হওয়ার সময় বা স্ক্রিন পরিবর্তন করার সময় এটি সবচেয়ে বেশি হয়।

জ্যাঙ্ক ঠিক করা হচ্ছে

জ্যাঙ্ক ঠিক করতে, কোন ফ্রেম ১৬ মিলিসেকেন্ডে সম্পূর্ণ হচ্ছে না তা পরীক্ষা করুন এবং কী ভুল হচ্ছে তা দেখুন। Record View#draw বা Layout কিছু ফ্রেমে অস্বাভাবিকভাবে বেশি সময় নিচ্ছে কিনা তা চেক করুন। এইসব সমস্যা ও অন্যান্য সমস্যার জন্য জ্যাঙ্কের সাধারণ সোর্স দেখুন।

জ্যাঙ্ক এড়াতে, UI থ্রেডের বাইরে অ্যাসিঙ্ক্রোনাসভাবে দীর্ঘ-চলমান টাস্ক চালান। আপনার কোড কোন থ্রেডে চলছে সে সম্পর্কে সর্বদা সচেতন থাকুন এবং প্রধান থ্রেডে গুরুত্বপূর্ণ নয় এমন টাস্ক পোস্ট করার সময় সতর্কতা অবলম্বন করুন।

আপনার অ্যাপের জন্য জটিল এবং গুরুত্বপূর্ণ প্রাথমিক UI থাকলে—যেমন সেন্ট্রাল স্ক্রলিং লিস্ট—ইনস্ট্রুমেন্টেশন টেস্ট লেখার কথা বিবেচনা করুন যা অটোমেটিক ধীরে রেন্ডার করার সময় শনাক্ত করতে পারে এবং রিগ্রেশন প্রতিরোধ করতে ঘন ঘন টেস্ট চালাতে পারে।

জ্যাঙ্কের সাধারণ সোর্স

নিম্নলিখিত বিভাগগুলি View সিস্টেম ব্যবহার করে অ্যাপে জ্যাঙ্কের সাধারণ সোর্স এবং সেগুলি সমাধান করার জন্য পেশাদার পদ্ধতি ব্যাখ্যা করে। Jetpack Compose-এর সাথে পারফর্ম্যান্স সংক্রান্ত সমস্যা সমাধান করার ব্যাপারে তথ্যের জন্য, Jetpack Compose পারফর্ম্যান্স দেখুন।

স্ক্রল করা যায় এমন তালিকা

ListView—এবং বিশেষ করে RecyclerView—সাধারণত জটিল স্ক্রলিং তালিকার জন্য ব্যবহার করা হয় যা সবচেয়ে বেশি জ্যাঙ্ক হওয়ার সম্ভাবনা থাকে। দুটিতেই Systrace মার্কার থাকে, তাই আপনার অ্যাপে জ্যাঙ্ক হওয়ার পিছনে এগুলি দায়ী কিনা তা দেখতে Systrace ব্যবহার করতে পারেন। RecyclerView-এ ট্রেস বিভাগ—এবং আপনার যোগ করা যেকোনও ট্রেস মার্কার—দেখতে কমান্ড-লাইন আর্গুমেন্ট -a <your-package-name> পাস করুন। উপলভ্য থাকলে, Systrace আউটপুটে জেনারেট হওয়া অ্যালার্টের নির্দেশিকা অনুসরণ করুন। Systrace-এর মধ্যে, আপনি RecyclerView-ট্রেস করা বিভাগে ক্লিক করে RecyclerView কী কাজ করছে তার ব্যাখ্যা দেখতে পারেন।

RecyclerView: notifyDataSetChanged()

আপনার RecyclerView-এর প্রতিটি আইটেম যদি রিবাউন্ড করা হয়—এবং এইভাবে একটি ফ্রেমে আবার লে-আউট করা এবং আবার আঁকা হয়—তাহলে নিশ্চিত করুন যে আপনি ছোটখাটো আপডেটের জন্য notifyDataSetChanged(), setAdapter(Adapter), অথবা swapAdapter(Adapter, boolean) কল করছেন না। এইসব পদ্ধতি থেকে বোঝা যায় যে সম্পূর্ণ তালিকার কন্টেন্টে পরিবর্তন করা হয়েছে এবং Systrace-এ RV FullInvalidate হিসেবে দেখা যায়। পরিবর্তে, কন্টেন্ট পরিবর্তন বা যোগ করা হলে ন্যূনতম আপডেট জেনারেট করতে SortedList বা DiffUtil ব্যবহার করুন।

যেমন, এমন একটি অ্যাপের কথা ভাবুন যেটি সার্ভার থেকে খবরের কন্টেন্টের তালিকার নতুন ভার্সন রিসিভ করে। আপনি এই তথ্য অ্যাডাপ্টারে পোস্ট করলে, notifyDataSetChanged()-কে কল করা সম্ভব, যেমন নিম্নলিখিত উদাহরণে দেখানো হয়েছে:

Kotlin

fun onNewDataArrived(news: List<News>) {
    myAdapter.news = news
    myAdapter.notifyDataSetChanged()
}

জাভা

void onNewDataArrived(List<News> news) {
    myAdapter.setNews(news);
    myAdapter.notifyDataSetChanged();
}

এর অসুবিধা হল, যদি কোনও তুচ্ছ পরিবর্তন হয়, যেমন উপরে একটি আইটেম যোগ করা, তাহলে RecyclerView তা জানতে পারে না। তাই, এটিকে ক্যাশে করা আইটেমের সম্পূর্ণ অবস্থা ড্রপ করতে বলা হয়েছে এবং তাই সবকিছু আবার বাইন্ড করতে হবে।

আমরা আপনাকে DiffUtil ব্যবহার করার সাজেশন দিই, যা আপনার জন্য ন্যূনতম আপডেট গণনা করে এবং পাঠায়:

Kotlin

fun onNewDataArrived(news: List<News>) {
    val oldNews = myAdapter.items
    val result = DiffUtil.calculateDiff(MyCallback(oldNews, news))
    myAdapter.news = news
    result.dispatchUpdatesTo(myAdapter)
}

জাভা

void onNewDataArrived(List<News> news) {
    List<News> oldNews = myAdapter.getItems();
    DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldNews, news));
    myAdapter.setNews(news);
    result.dispatchUpdatesTo(myAdapter);
}

DiffUtil-কে আপনার তালিকা কীভাবে পরীক্ষা করতে হবে তা জানাতে, আপনার MyCallback-কে Callback ইমপ্লিমেন্টেশন হিসেবে সংজ্ঞায়িত করুন।

RecyclerView: নেস্ট করা RecyclerView

RecyclerView-এর একাধিক ইনস্ট্যান্স নেস্ট করা সাধারণ ব্যাপার, বিশেষ করে অনুভূমিকভাবে স্ক্রল করা তালিকার উল্লম্ব তালিকার সাথে। এর একটি উদাহরণ হল Play Store-এর মূল পৃষ্ঠায় অ্যাপের গ্রিড। এটি দারুণ কাজ করতে পারে, তবে এটি অনেক ভিউ এদিক ওদিক করে।

আপনি যদি প্রথমবার পৃষ্ঠাটি নিচের দিকে স্ক্রল করার সময় অনেক ইনার আইটেম ইনফ্লেট হতে দেখেন, তাহলে আপনি RecyclerView-এর ইনার (অনুভূমিক) ইনস্ট্যান্সের মধ্যে RecyclerView.RecycledViewPool শেয়ার করছেন কিনা তা চেক করতে পারেন। ডিফল্ট হিসেবে, প্রতিটি RecyclerView-এর নিজস্ব আইটেমের পুল আছে। তবে, স্ক্রিনে একই সাথে এক ডজন itemViews থাকলে, সব রোতে একই ধরনের ভিউ দেখানো হলে, বিভিন্ন হরাইজন্টাল লিস্টের মাধ্যমে itemViews শেয়ার করা না গেলে সমস্যা হয়।

Kotlin

class OuterAdapter : RecyclerView.Adapter<OuterAdapter.ViewHolder>() {

    ...

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        // Inflate inner item, find innerRecyclerView by ID.
        val innerLLM = LinearLayoutManager(parent.context, LinearLayoutManager.HORIZONTAL, false)
        innerRv.apply {
            layoutManager = innerLLM
            recycledViewPool = sharedPool
        }
        return OuterAdapter.ViewHolder(innerRv)
    }
    ...

জাভা

class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> {
    RecyclerView.RecycledViewPool sharedPool = new RecyclerView.RecycledViewPool();

    ...

    @Override
    public void onCreateViewHolder(ViewGroup parent, int viewType) {
        // Inflate inner item, find innerRecyclerView by ID.
        LinearLayoutManager innerLLM = new LinearLayoutManager(parent.getContext(),
                LinearLayoutManager.HORIZONTAL);
        innerRv.setLayoutManager(innerLLM);
        innerRv.setRecycledViewPool(sharedPool);
        return new OuterAdapter.ViewHolder(innerRv);

    }
    ...

আপনি আরও অপ্টিমাইজ করতে চাইলে, setInitialPrefetchItemCount(int) কল করতে পারেন LinearLayoutManager ইনার RecyclerView-এর। যেমন, আপনার যদি সবসময় একটি সারিতে ৩.৫টি আইটেম দেখানো হয়, তাহলে innerLLM.setInitialItemPrefetchCount(4) কল করুন। এটি RecyclerView-কে সিগন্যাল দেয় যে স্ক্রিনে অনুভূমিক সারি আসার সময় হলে, UI থ্রেডে অতিরিক্ত সময় থাকলে, এটিকে অবশ্যই আইটেম প্রিফেচ করার চেষ্টা করতে হবে।

RecyclerView: খুব বেশি ইনফ্লেশন বা তৈরি করতে অনেক সময় লাগছে

বেশিরভাগ ক্ষেত্রে, RecyclerView-এ প্রিফেচ ফিচার UI থ্রেড আইডল থাকার সময় আগে থেকে কাজ করে ইনফ্লেশনের খরচ এড়াতে সাহায্য করতে পারে। আপনি যদি কোনও ফ্রেমের সময় ইনফ্লেশন দেখতে পান এবং RV Prefetch লেবেলযুক্ত কোনও বিভাগে না পান, তাহলে নিশ্চিত করুন যে আপনি একটি কাজ করে এমন ডিভাইসে পরীক্ষা করছেন এবং সাপোর্ট লাইব্রেরি-এর সাম্প্রতিক ভার্সন ব্যবহার করছেন। প্রিফেচ শুধুমাত্র Android 5.0 API লেভেল ২১ এবং তার পরবর্তী ভার্সনে কাজ করে।

স্ক্রিনে নতুন আইটেম আসার সাথে সাথে আপনি যদি প্রায়শই ইনফ্লেশন-জনিত জ্যাঙ্ক দেখতে পান, তাহলে যাচাই করুন যে আপনার কাছে প্রয়োজনের চেয়ে বেশি ভিউ টাইপ নেই। RecyclerView-এর কন্টেন্টে যত কম ভিউ টাইপ থাকবে, স্ক্রিনে নতুন আইটেম টাইপ আসার সময় তত কম ইনফ্লেশন করতে হবে। সম্ভব হলে, যেখানে যুক্তিযুক্ত সেখানে ভিউয়ের ধরন মার্জ করুন। যদি বিভিন্ন ধরনের মধ্যে শুধুমাত্র একটি আইকন, রঙ বা টেক্সটের অংশ পরিবর্তন হয়, তাহলে আপনি বাইন্ড করার সময় সেই পরিবর্তন করতে পারেন এবং ইনফ্লেশন এড়াতে পারেন, যা একই সাথে আপনার অ্যাপের মেমরি ব্যবহার কমিয়ে দেয়।

আপনার ভিউ টাইপ ঠিক থাকলে, মুদ্রাস্ফীতির খরচ কমানোর কথা ভাবুন। অপ্রয়োজনীয় কন্টেনার ও স্ট্রাকচারাল ভিউ কমালে সাহায্য হতে পারে। ConstraintLayout-এর সাথে itemViews তৈরি করার কথা বিবেচনা করুন, যা স্ট্রাকচারাল ভিউ কমাতে সাহায্য করতে পারে।

আপনি যদি পারফর্ম্যান্স আরও অপ্টিমাইজ করতে চান এবং আপনার আইটেম হায়ারার্কি সহজ হয় এবং আপনার জটিল থিমিং এবং স্টাইল ফিচারের প্রয়োজন না হয়, তাহলে কনস্ট্রাক্টরদের নিজেকে কল করার কথা বিবেচনা করুন। তবে, XML-এর সরলতা ও ফিচার হারানোর বিনিময়ে এটি প্রায়শই লাভজনক হয় না।

RecyclerView: Bind করতে অনেক সময় লাগছে

বাইন্ড—অর্থাৎ, onBindViewHolder(VH, int)— অবশ্যই সহজ হতে হবে এবং সবচেয়ে জটিল আইটেম ছাড়া বাকি সবকিছুর জন্য এক মিলিসেকেন্ডের চেয়ে অনেক কম সময় নিতে হবে। এটি অবশ্যই আপনার অ্যাডাপ্টারের ইন্টার্নাল আইটেম ডেটা থেকে প্লেন ওল্ড জাভা অবজেক্ট (POJO) আইটেম নেবে এবং ViewHolder-এ ভিউতে সেটার কল করবে। RV OnBindView-এর ক্ষেত্রে অনেক সময় লাগলে, আপনার বাইন্ড কোডে ন্যূনতম কাজ করছেন কিনা তা যাচাই করুন।

আপনার অ্যাডাপ্টারে ডেটা রাখার জন্য আপনি যদি বেসিক POJO অবজেক্ট ব্যবহার করেন, তাহলে আপনি ডেটা বাইন্ডিং লাইব্রেরি ব্যবহার করে onBindViewHolder-এ বাইন্ডিং কোড লেখা সম্পূর্ণরূপে এড়াতে পারবেন।

RecyclerView বা ListView: লেআউট বা ড্র করতে অনেক সময় লাগছে

ড্র ও লেআউট সংক্রান্ত সমস্যার জন্য, লেআউট পারফর্ম্যান্স এবং রেন্ডারিং পারফর্ম্যান্স বিভাগ দেখুন।

ListView: Inflation

আপনি সতর্ক না থাকলে, ভুলবশত ListView-এ রিসাইক্লিং বন্ধ করে দিতে পারেন। স্ক্রিনে কোনও আইটেম আসার সময় প্রতিবার ইনফ্লেশন দেখতে পেলে, আপনার Adapter.getView() ইমপ্লিমেন্টেশন convertView প্যারামিটারকে মিউজ, রি-বাইন্ড ও রিটার্ন করছে কিনা তা চেক করুন। আপনার getView() ইমপ্লিমেন্টেশন সবসময় ইনফ্লেট করলে, আপনার অ্যাপ ListView-এ রিসাইকেল করার সুবিধা পায় না। আপনার getView()-এর স্ট্রাকচার প্রায় সবসময় নিম্নলিখিত ইমপ্লিমেন্টেশনের মতো হতে হবে:

Kotlin

fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
    return (convertView ?: layoutInflater.inflate(R.layout.my_layout, parent, false)).apply {
        // Bind content from position to convertView.
    }
}

জাভা

View getView(int position, View convertView, ViewGroup parent) {

    if (convertView == null) {
        // Only inflate if no convertView passed.
        convertView = layoutInflater.inflate(R.layout.my_layout, parent, false)
    }
    // Bind content from position to convertView.
    return convertView;
}

লেআউট পারফর্ম্যান্স

Systrace যদি দেখায় যে Choreographer#doFrame-এর লেআউট সেগমেন্ট খুব বেশি বা খুব ঘন ঘন কাজ করছে, তাহলে এর অর্থ হল আপনি লেআউট পারফর্ম্যান্স সংক্রান্ত সমস্যার সম্মুখীন হচ্ছেন। আপনার অ্যাপের লেআউট পারফর্ম্যান্স নির্ভর করে ভিউ হায়ারার্কির কোন অংশের লেআউট প্যারামিটার বা ইনপুট পরিবর্তন হচ্ছে তার উপর।

লেআউট পারফর্ম্যান্স: খরচ

সেগমেন্টগুলি কয়েক মিলিসেকেন্ডের চেয়ে দীর্ঘ হলে, এটি সম্ভব যে আপনি RelativeLayouts, বা weighted-LinearLayouts-এর জন্য সবচেয়ে খারাপ-কেস নেস্টিং পারফর্ম্যান্স হিট করছেন। এই লেআউটগুলির প্রতিটি তার চাইল্ডের একাধিক পরিমাপ এবং লেআউট পাস ট্রিগার করতে পারে, তাই সেগুলিকে নেস্ট করলে নেস্টিংয়ের গভীরতার উপর O(n^2) আচরণ হতে পারে।

RelativeLayout বা LinearLayout-এর ওয়েট ফিচার এড়িয়ে চলুন, তবে হায়ারার্কির সবচেয়ে নিচের লিফ নোডের ক্ষেত্রে এটি প্রযোজ্য নয়। আপনি নিম্নলিখিত উপায়ে এটি করতে পারবেন:

  • আপনার স্ট্রাকচারাল ভিউ আবার সাজান।
  • কাস্টম লেআউট লজিক নির্ধারণ করুন। নির্দিষ্ট উদাহরণের জন্য লেআউট হায়ারার্কি অপ্টিমাইজ করুন দেখুন। আপনি ConstraintLayout-এ কনভার্ট করে দেখতে পারেন, যা পারফর্ম্যান্সের কোনও ত্রুটি ছাড়াই একই ধরনের ফিচার প্রদান করে।

লেআউট পারফর্ম্যান্স: ফ্রিকোয়েন্সি

স্ক্রিনে নতুন কন্টেন্ট এলে লেআউট শিফ্ট হওয়ার সম্ভাবনা থাকে, যেমন RecyclerView-এ নতুন আইটেম স্ক্রল করে ভিউতে এলে। প্রতিটি ফ্রেমে উল্লেখযোগ্য লেআউট হলে, আপনি লেআউট অ্যানিমেট করছেন, যার ফলে ফ্রেম ড্রপ হওয়ার সম্ভাবনা থাকে।

সাধারণত, অ্যানিমেশন অবশ্যই View-এর ড্রয়িং প্রপার্টিতে রান করতে হবে, যেমন নিম্নলিখিত:

আপনি লেআউট প্রপার্টির তুলনায় অনেক কম খরচে এগুলি পরিবর্তন করতে পারবেন, যেমন প্যাডিং বা মার্জিন। সাধারণত, কোনও ভিউয়ের ড্রয়িং প্রপার্টি পরিবর্তন করার জন্য সেটার কল করা অনেক সস্তা, যা invalidate() ট্রিগার করে, যার পরে পরবর্তী ফ্রেমে draw(Canvas) ট্রিগার হয়। এটি ভিউয়ের জন্য ড্রয়িং অপারেশন আবার রেকর্ড করে যা বাতিল করা হয়েছে এবং সাধারণত লেআউটের চেয়ে অনেক সস্তা।

রেন্ডারিং পারফর্ম্যান্স

Android UI দুটি পর্যায়ে কাজ করে:

  • UI থ্রেডে Record View#draw, যা প্রতিটি ইনভ্যালিডেট করা ভিউতে draw(Canvas) রান করে এবং কাস্টম ভিউতে বা আপনার কোডে কল করতে পারে।
  • RenderThread-এ DrawFrame, যা নেটিভ RenderThread-এ চলে কিন্তু Record View#draw ফেজের মাধ্যমে তৈরি করা কাজের ভিত্তিতে অপারেট করে।

রেন্ডারিং পারফর্ম্যান্স: UI থ্রেড

Record View#draw-এর জন্য অনেক সময় লাগলে, UI থ্রেডে বিটম্যাপ আঁকা হচ্ছে বলে ধরে নেওয়া হয়। বিটম্যাপে পেইন্টিং CPU রেন্ডারিং ব্যবহার করে, তাই সাধারণত সম্ভব হলে এটি এড়িয়ে চলুন। এটিই সমস্যা কিনা তা দেখতে আপনি Android CPU Profiler-এর সাথে মেথড ট্রেসিং ব্যবহার করতে পারেন।

বিটম্যাপে পেইন্টিং প্রায়ই করা হয় যখন কোনও অ্যাপ বিটম্যাপ দেখানোর আগে সেটি সাজাতে চায়—কখনও কখনও সাজানোর জন্য গোলাকার কোণ যোগ করা হয়:

Kotlin

val paint = Paint().apply {
    isAntiAlias = true
}
Canvas(roundedOutputBitmap).apply {
    // Draw a round rect to define the shape:
    drawRoundRect(
            0f,
            0f,
            roundedOutputBitmap.width.toFloat(),
            roundedOutputBitmap.height.toFloat(),
            20f,
            20f,
            paint
    )
    paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)
    // Multiply content on top to make it rounded.
    drawBitmap(sourceBitmap, 0f, 0f, paint)
    setBitmap(null)
    // Now roundedOutputBitmap has sourceBitmap inside, but as a circle.
}

জাভা

Canvas bitmapCanvas = new Canvas(roundedOutputBitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
// Draw a round rect to define the shape:
bitmapCanvas.drawRoundRect(0, 0,
        roundedOutputBitmap.getWidth(), roundedOutputBitmap.getHeight(), 20, 20, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
// Multiply content on top to make it rounded.
bitmapCanvas.drawBitmap(sourceBitmap, 0, 0, paint);
bitmapCanvas.setBitmap(null);
// Now roundedOutputBitmap has sourceBitmap inside, but as a circle.

আপনি যদি UI থ্রেডে এই ধরনের কাজ করেন, তাহলে এর পরিবর্তে আপনি ব্যাকগ্রাউন্ডে ডিকোডিং থ্রেডে এটি করতে পারেন। কিছু ক্ষেত্রে, যেমন পূর্ববর্তী উদাহরণে, আপনি ড্র করার সময়ও কাজ করতে পারেন। তাই, আপনার Drawable বা View কোড যদি এরকম দেখতে হয়:

Kotlin

fun setBitmap(bitmap: Bitmap) {
    mBitmap = bitmap
    invalidate()
}

override fun onDraw(canvas: Canvas) {
    canvas.drawBitmap(mBitmap, null, paint)
}

জাভা

void setBitmap(Bitmap bitmap) {
    mBitmap = bitmap;
    invalidate();
}

void onDraw(Canvas canvas) {
    canvas.drawBitmap(mBitmap, null, paint);
}

আপনি এটি দিয়ে এটি পরিবর্তন করতে পারবেন:

Kotlin

fun setBitmap(bitmap: Bitmap) {
    shaderPaint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
    invalidate()
}

override fun onDraw(canvas: Canvas) {
    canvas.drawRoundRect(0f, 0f, width, height, 20f, 20f, shaderPaint)
}

জাভা

void setBitmap(Bitmap bitmap) {
    shaderPaint.setShader(
            new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP));
    invalidate();
}

void onDraw(Canvas canvas) {
    canvas.drawRoundRect(0, 0, width, height, 20, 20, shaderPaint);
}

এছাড়াও, আপনি ব্যাকগ্রাউন্ড সুরক্ষার জন্য এটি করতে পারেন, যেমন বিটম্যাপের উপরে গ্রেডিয়েন্ট আঁকার সময় এবং ColorMatrixColorFilter—বিটম্যাপ পরিবর্তন করার জন্য করা অন্য দুটি সাধারণ অপারেশন দিয়ে ইমেজ ফিল্টার করা।

আপনি যদি অন্য কোনও কারণে বিটম্যাপে আঁকেন—সম্ভবত এটিকে ক্যাশে হিসেবে ব্যবহার করছেন—তাহলে আপনার View বা Drawable-এ সরাসরি পাস করা হার্ডওয়্যার-অ্যাক্সিলারেটেড Canvas-এ আঁকার চেষ্টা করুন। প্রয়োজন হলে, জটিল রেন্ডারিং আউটপুট ক্যাশে করতে এবং GPU রেন্ডারিংয়ের সুবিধা নিতে LAYER_TYPE_HARDWARE সহ setLayerType() কল করার কথাও বিবেচনা করুন।

রেন্ডারিং পারফর্ম্যান্স: RenderThread

কিছু Canvas অপারেশন রেকর্ড করা সস্তা কিন্তু RenderThread-এ ব্যয়বহুল কম্পিউটেশন ট্রিগার করে। Systrace সাধারণত সতর্কতার মাধ্যমে এগুলি দেখায়।

বড় পাথ অ্যানিমেট করা হচ্ছে

হার্ডওয়্যার-অ্যাক্সিলারেটেড Canvas-এ Canvas.drawPath() কল করা হলে, View-এ পাস করা হয়, Android প্রথমে CPU-তে এইসব পাথ ড্র করে এবং GPU-তে আপলোড করে। আপনার কাছে বড় পাথ থাকলে, ফ্রেম থেকে ফ্রেমে সেগুলি এডিট করা এড়িয়ে চলুন, যাতে সেগুলি দক্ষতার সাথে ক্যাশে এবং আঁকা যায়। drawPoints(), drawLines() এবং drawRect/Circle/Oval/RoundRect() আরও দক্ষ এবং আপনি আরও বেশি ড্র কল ব্যবহার করলেও ব্যবহার করা ভাল।

Canvas.clipPath

clipPath(Path) দামি ক্লিপিং আচরণ ট্রিগার করে এবং সাধারণত এড়িয়ে যেতে হবে। সম্ভব হলে, আয়তাকার নয় এমন শেপে ক্লিপ করার পরিবর্তে শেপ আঁকার বিকল্প বেছে নিন। এটি আরও ভাল পারফর্ম করে এবং অ্যান্টি-অ্যালাইজিং সাপোর্ট করে। যেমন, নিম্নলিখিত clipPath কলটি অন্যভাবে প্রকাশ করা যেতে পারে:

Kotlin

canvas.apply {
    save()
    clipPath(circlePath)
    drawBitmap(bitmap, 0f, 0f, paint)
    restore()
}

জাভা

canvas.save();
canvas.clipPath(circlePath);
canvas.drawBitmap(bitmap, 0f, 0f, paint);
canvas.restore();

পরিবর্তে, পূর্ববর্তী উদাহরণটি এইভাবে প্রকাশ করুন:

Kotlin

paint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
// At draw time:
canvas.drawPath(circlePath, mPaint)

জাভা

// One time init:
paint.setShader(new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP));
// At draw time:
canvas.drawPath(circlePath, mPaint);
বিটম্যাপ আপলোড

Android, বিটম্যাপকে OpenGL টেক্সচার হিসেবে দেখায় এবং প্রথমবার কোনও ফ্রেমে বিটম্যাপ দেখানো হলে, সেটি GPU-তে আপলোড করা হয়। আপনি Systrace-এ এটি টেক্সচার আপলোড(id) প্রস্থ x উচ্চতা হিসেবে দেখতে পাবেন। এতে কয়েক মিলিসেকেন্ড সময় লাগতে পারে, যেমন ছবি ২-এ দেখানো হয়েছে, তবে GPU-এর মাধ্যমে ছবি দেখানোর জন্য এটি প্রয়োজনীয়।

এগুলি করতে অনেক সময় লাগলে, প্রথমে ট্রেসে প্রস্থ ও উচ্চতার সংখ্যা চেক করুন। নিশ্চিত করুন যে বিটম্যাপটি স্ক্রিনে যে জায়গায় দেখানো হচ্ছে তার থেকে উল্লেখযোগ্যভাবে বড় নয়। যদি তা হয়, তাহলে আপলোড করার সময় ও মেমরি নষ্ট হয়। সাধারণত, বিটম্যাপ লোডিং লাইব্রেরি উপযুক্ত সাইজের বিটম্যাপের অনুরোধ করার সুবিধা প্রদান করে।

Android 7.0-এ, বিটম্যাপ লোডিং কোড—সাধারণত লাইব্রেরির মাধ্যমে করা হয়—প্রয়োজন হওয়ার আগেই আপলোড ট্রিগার করতে prepareToDraw() কল করতে পারে। এইভাবে, RenderThread আইডল থাকার সময় আপলোড আগেভাগেই হয়ে যায়। আপনি ডিকোড করার পরে বা ভিউতে বিটম্যাপ বাইন্ড করার সময় এটি করতে পারেন, তবে আপনাকে বিটম্যাপ সম্পর্কে জানতে হবে। সাধারণত, আপনার বিটম্যাপ লোডিং লাইব্রেরি আপনার জন্য এটি করে, তবে আপনি যদি নিজেরটি ম্যানেজ করেন বা নতুন ডিভাইসে আপলোড না করার বিষয়টি নিশ্চিত করতে চান, তাহলে আপনার নিজের কোডে prepareToDraw() কল করতে পারেন।

কোনও অ্যাপ একটি ফ্রেমে অনেক সময় ব্যয় করে
  বড় বিটম্যাপ আপলোড করছে
ছবি ২. কোনও অ্যাপ একটি ফ্রেমে উল্লেখযোগ্য সময় ব্যয় করে একটি বড় বিটম্যাপ আপলোড করে। prepareToDraw()-এর মাধ্যমে ডিকোড করার সময় হয় এর সাইজ কমান অথবা আগে ট্রিগার করুন।

থ্রেড শিডিউল করার ক্ষেত্রে দেরি

থ্রেড শিডিউলার হল Android অপারেটিং সিস্টেমের অংশ যা সিস্টেমে কোন থ্রেডগুলি চলতে হবে, কখন চলবে এবং কতক্ষণ চলবে তা নির্ধারণ করার দায়িত্বে থাকে।

কখনও কখনও, আপনার অ্যাপের UI থ্রেড ব্লক হয়ে গেলে বা না চললে জ্যাঙ্ক হয়। Systrace বিভিন্ন রঙ ব্যবহার করে, যেমনটি চিত্র ৩-এ দেখানো হয়েছে, একটি থ্রেড কখন স্লিপিং (ধূসর), রান করার উপযুক্ত (নীল: এটি রান করতে পারে, তবে এখনও রান করার জন্য শিডিউলার দ্বারা বেছে নেওয়া হয়নি), অ্যাক্টিভভাবে রান করছে (সবুজ), বা নিরবচ্ছিন্ন স্লিপ (লাল বা কমলা) অবস্থায় আছে তা নির্দেশ করতে। থ্রেড শিডিউল করার সময় দেরি হওয়ার কারণে যে জ্যাঙ্ক সমস্যা হয়, সেটি ডিবাগ করার জন্য এটি অত্যন্ত উপযোগী।

UI থ্রেড স্লিপ মোডে থাকার সময়
  একটি সময়সীমা হাইলাইট করে
ছবি ৩। UI থ্রেড স্লিপিং মোডে থাকার সময়কালের হাইলাইট।

প্রায়ই, বাইন্ডার কল—Android-এ ইন্টার-প্রসেস কমিউনিকেশন (IPC) মেকানিজম—আপনার অ্যাপের এক্সিকিউশনে দীর্ঘ বিরতির কারণ হয়। Android-এর পরবর্তী ভার্সনে, UI থ্রেড বন্ধ হয়ে যাওয়ার এটি অন্যতম সাধারণ কারণ। সাধারণত, বাইন্ডার কল করে এমন ফাংশন কল করা এড়িয়ে গিয়ে এই সমস্যার সমাধান করা হয়। এটি এড়ানো না গেলে, ভ্যালু ক্যাশে করুন বা ব্যাকগ্রাউন্ড থ্রেডে কাজ সরান। কোডবেস আরও বড় হয়ে গেলে, আপনি সতর্ক না থাকলে, কিছু লো-লেভেল মেথড ইনভোক করার মাধ্যমে ভুলবশত বাইন্ডার কল যোগ করে ফেলতে পারেন। তবে, আপনি ট্রেসিংয়ের মাধ্যমে সেগুলি খুঁজে বের করে সমাধান করতে পারবেন।

আপনার বাইন্ডার ট্রানজ্যাকশন থাকলে, আপনি নিম্নলিখিত adb কমান্ডের মাধ্যমে তাদের কল স্ট্যাক ক্যাপচার করতে পারবেন:

$ adb shell am trace-ipc start
… use the app - scroll/animate ...
$ adb shell am trace-ipc stop --dump-file /data/local/tmp/ipc-trace.txt
$ adb pull /data/local/tmp/ipc-trace.txt

কখনও কখনও এমন কল যা নিরীহ বলে মনে হয়, যেমন getRefreshRate(), বাইন্ডার ট্রানজ্যাকশন ট্রিগার করতে পারে এবং ঘন ঘন কল করা হলে বড় সমস্যা সৃষ্টি করতে পারে। নিয়মিত ট্রেস করলে, এইসব সমস্যা দেখা দেওয়ার সাথে সাথেই আপনি সেগুলি খুঁজে বের করে সমাধান করতে পারবেন।

RV ফ্লিংয়ে বাইন্ডার ট্রানজ্যাকশনের কারণে
  UI থ্রেড স্লিপিং দেখায়। আপনার বাইন্ড লজিক ফোকাসড রাখুন এবং বাইন্ডার কল ট্র্যাক ও সরাতে
  trace-ipc ব্যবহার করুন।
ছবি ৪. RV ফ্লিংয়ে বাইন্ডার ট্রানজ্যাকশনের কারণে UI থ্রেড স্লিপিং মোডে আছে। আপনার বাইন্ড লজিক সহজ রাখুন এবং trace-ipc বাইন্ডার কল ট্র্যাক ডাউন ও সরিয়ে দিতে ব্যবহার করুন।

আপনি যদি বাইন্ডার অ্যাক্টিভিটি না দেখেন কিন্তু এখনও আপনার UI থ্রেড রান দেখতে না পান, নিশ্চিত করুন যে আপনি অন্য থ্রেড থেকে লক বা অন্য অপারেশনের জন্য অপেক্ষা করছেন না। সাধারণত, UI থ্রেডকে অন্য থ্রেড থেকে ফলাফলের জন্য অপেক্ষা করতে হয় না। অন্যান্য থ্রেড অবশ্যই এতে তথ্য পোস্ট করবে।

অবজেক্ট অ্যালোকেশন ও গার্বেজ কালেকশন

Android 5.0-এ ডিফল্ট রানটাইম হিসেবে ART চালু করার পর থেকে অবজেক্ট অ্যালোকেশন ও গার্বেজ কালেকশন (GC) সংক্রান্ত সমস্যা অনেক কমে গেছে, তবে এখনও এই অতিরিক্ত কাজের জন্য আপনার থ্রেডের উপর চাপ পড়তে পারে। বিরল ইভেন্টের ক্ষেত্রে অ্যালোকেট করা যেতে পারে যা প্রতি সেকেন্ডে অনেকবার ঘটে না—যেমন ব্যবহারকারী কোনও বোতামে ট্যাপ করছেন—তবে মনে রাখবেন যে প্রতিটি অ্যালোকেশনের জন্য খরচ হয়। এটি যদি ঘন ঘন কল করা টাইট লুপে থাকে, তাহলে GC-এর উপর লোড কমাতে অ্যালোকেশন এড়ানোর কথা বিবেচনা করুন।

GC ঘন ঘন চলছে কিনা তা Systrace আপনাকে দেখায় এবং Android মেমরি প্রোফাইলার আপনাকে দেখাতে পারে যে কোথা থেকে অ্যালোকেশন আসছে। সম্ভব হলে, বিশেষ করে টাইট লুপে, আপনি অ্যালোকেশন এড়িয়ে গেলে, আপনার সমস্যা হওয়ার সম্ভাবনা কম।

HeapTaskDaemon-এ ৯৪ মিলি সেকেন্ডের GC দেখায়
ছবি ৫. HeapTaskDaemon থ্রেডে ৯৪ মিলি সেকেন্ডের GC।

Android-এর সাম্প্রতিক ভার্সনে, GC সাধারণত HeapTaskDaemon নামের ব্যাকগ্রাউন্ড থ্রেডে চলে। উল্লেখযোগ্য পরিমাণে বরাদ্দ করার অর্থ হল GC-তে আরও বেশি CPU রিসোর্স খরচ করা, যেমনটি চিত্র ৫-এ দেখানো হয়েছে।