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

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

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

আপনি যদি এমন গেম তৈরি করেন যা View সিস্টেম ব্যবহার করে না, তাহলে আপনি Choreographer বাইপাস করতে পারেন। এক্ষেত্রে ফ্রেম পেসিং লাইব্রেরি অ্যান্ড্রয়েডে OpenGL এবং Vulkan গেমগুলোকে মসৃণ রেন্ডারিং এবং সঠিক ফ্রেম পেসিং অর্জনে সহায়তা করে।

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

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

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

চাক্ষুষ পরিদর্শন

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

চাক্ষুষ পরিদর্শন করার জন্য এখানে কিছু পরামর্শ দেওয়া হলো:

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

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

সিস্ট্রেস

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

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

সিস্ট্রেস উদাহরণ
চিত্র ১. সিস্ট্রেস (Systrace) এর উদাহরণ।

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

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

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

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

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

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

যদি আপনি স্থানীয় ডিভাইসে জ্যাঙ্কটি পুনরায় তৈরি করতে না পারেন, তবে আপনি আপনার অ্যাপে কাস্টম পারফরম্যান্স মনিটরিং যুক্ত করতে পারেন, যা ফিল্ডে থাকা ডিভাইসগুলিতে জ্যাঙ্কের উৎস শনাক্ত করতে সাহায্য করবে।

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

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

স্থির ফ্রেম

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

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

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

Perfetto- এর FrameTimeline ধীরগতির বা থেমে যাওয়া ফ্রেম ট্র্যাক করতে সাহায্য করতে পারে।

স্লো ফ্রেম, ফ্রোজেন ফ্রেম এবং এএনআর-এর মধ্যে সম্পর্ক

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

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

ধীরগতির ফ্রেম এবং থেমে যাওয়া ফ্রেমগুলো আলাদাভাবে ট্র্যাক করুন।

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

জ্যাঙ্ককে অগ্রাধিকার দেওয়া এবং সমাধান করার সর্বোত্তম অনুশীলন

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

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

জ্যাঙ্ক ঠিক করা

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

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

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

জ্যাঙ্কের সাধারণ উৎস

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

স্ক্রোলযোগ্য তালিকা

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() কল করা সম্ভব, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে:

কোটলিন

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

জাভা

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

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

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

কোটলিন

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 implementation হিসেবে সংজ্ঞায়িত করুন।

RecyclerView: নেস্টেড RecyclerViews

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

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

কোটলিন

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);

    }
    ...

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

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

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

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

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

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

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

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

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

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

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

তালিকা দৃশ্য: মুদ্রাস্ফীতি

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

কোটলিন

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 এর weight ফিচারটি ব্যবহার করা এড়িয়ে চলুন। নিচে এর কয়েকটি উপায় দেওয়া হলো:

  • আপনার কাঠামোগত দৃষ্টিভঙ্গি পুনর্গঠন করুন।
  • কাস্টম লেআউট লজিক সংজ্ঞায়িত করুন। একটি নির্দিষ্ট উদাহরণের জন্য 'লেআউট হায়ারার্কি অপ্টিমাইজ করুন' দেখুন। আপনি ConstraintLayout এ রূপান্তর করার চেষ্টা করতে পারেন, যা পারফরম্যান্সের অসুবিধা ছাড়াই অনুরূপ বৈশিষ্ট্য প্রদান করে।

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

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

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

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

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

অ্যান্ড্রয়েড ইউআই দুটি ধাপে কাজ করে:

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

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

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

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

কোটলিন

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 কোডটি দেখতে এইরকম হয়:

কোটলিন

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);
}

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

কোটলিন

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 ড্র করার চেষ্টা করুন। প্রয়োজনে, জটিল রেন্ডারিং আউটপুট ক্যাশ করতে এবং জিপিইউ রেন্ডারিংয়ের সুবিধা নিতে LAYER_TYPE_HARDWARE সহ setLayerType() কল করার কথাও বিবেচনা করতে পারেন।

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

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

বড় পথগুলিকে অ্যানিমেট করা

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

ক্যানভাস.ক্লিপপাথ

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

কোটলিন

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

জাভা

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

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

কোটলিন

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);
বিটম্যাপ আপলোড

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

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

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

একটি অ্যাপ একটি ফ্রেমে একটি বড় বিটম্যাপ আপলোড করতে উল্লেখযোগ্য সময় ব্যয় করে।
চিত্র ২। একটি অ্যাপ একটি বড় বিটম্যাপ আপলোড করতে একটি ফ্রেমে উল্লেখযোগ্য সময় ব্যয় করে। হয় এর আকার হ্রাস করুন অথবা prepareToDraw() দিয়ে ডিকোড করার সময় এটিকে আগেভাগেই ট্রিগার করুন।

থ্রেড সময়সূচী বিলম্ব

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

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

এমন একটি সময়কালকে হাইলাইট করে যখন UI থ্রেড স্লিপিং অবস্থায় থাকে
চিত্র ৩। UI থ্রেডের নিষ্ক্রিয় থাকার সময়কালের বিশেষ অংশ।

প্রায়শই, বাইন্ডার কল—অ্যান্ড্রয়েডের ইন্টার-প্রসেস কমিউনিকেশন (IPC) মেকানিজম—আপনার অ্যাপের কার্য সম্পাদনে দীর্ঘ বিরতির কারণ হয়। অ্যান্ড্রয়েডের পরবর্তী সংস্করণগুলিতে, 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 থ্রেডকে অন্য থ্রেডের ফলাফলের জন্য অপেক্ষা করতে হয় না। অন্য থ্রেডগুলোকেই এর কাছে তথ্য পোস্ট করতে হয়।

অবজেক্ট বরাদ্দ এবং গার্বেজ সংগ্রহ

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

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

HeapTaskDaemon-এ ৯৪ মিলিসেকেন্ডের একটি GC দেখাচ্ছে।
চিত্র ৫. HeapTaskDaemon থ্রেডে একটি ৯৪ মিলিসেকেন্ডের গার্বেজ কালেকশন (GC)।

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

{% হুবহু %} {% endverbatim %} {% হুবহু %} {% endverbatim %}