অ্যান্ড্রয়েড অ্যাসিঙ্ক এবং ননব্লকিং API নির্দেশিকা

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

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

অ্যাসিঙ্ক্রোনাস এপিআই লেখার দুটি প্রধান উদ্দেশ্য রয়েছে:

  • একই সাথে একাধিক অপারেশন সম্পাদন করা, যেখানে N-1তম অপারেশনটি সম্পন্ন হওয়ার আগেই Nতম অপারেশনটি শুরু করতে হবে।
  • কোনো অপারেশন সম্পূর্ণ না হওয়া পর্যন্ত কলিং থ্রেডকে ব্লক করা পরিহার করা।

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

ফাংশন স্থগিত করুন:

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

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

অ্যাসিঙ্ক এপিআই সম্পর্কে ডেভেলপারদের প্রত্যাশা

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

যেসব এপিআই কলব্যাক গ্রহণ করে, সেগুলো সাধারণত অ্যাসিঙ্ক্রোনাস হয়।

যদি কোনো API এমন একটি কলব্যাক গ্রহণ করে যা শুধুমাত্র ইন-প্লেস কল করার জন্য নথিভুক্ত নয় (অর্থাৎ, API কলটি নিজে রিটার্ন করার আগে শুধুমাত্র কলিং থ্রেড দ্বারা কল করা হয় না), তাহলে সেই API-টিকে অ্যাসিঙ্ক্রোনাস বলে ধরে নেওয়া হয় এবং সেই API-টিকে পরবর্তী বিভাগগুলিতে নথিভুক্ত অন্যান্য সমস্ত প্রত্যাশা পূরণ করতে হবে।

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

অ্যাসিঙ্ক্রোনাস এপিআইগুলোর যত দ্রুত সম্ভব ফলাফল দেওয়া উচিত।

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

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

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

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

অ্যাসিঙ্ক্রোনাস এপিআই-এর ভয়েড রিটার্ন করা উচিত এবং শুধুমাত্র অবৈধ আর্গুমেন্টের জন্য থ্রো করা উচিত।

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

অ্যাসিঙ্ক এপিআইগুলো আর্গুমেন্টগুলো নাল (null) কিনা তা পরীক্ষা করতে পারে এবং NullPointerException থ্রো করতে পারে, অথবা প্রদত্ত আর্গুমেন্টগুলো একটি বৈধ সীমার মধ্যে আছে কিনা তা পরীক্ষা করে IllegalArgumentException থ্রো করতে পারে। উদাহরণস্বরূপ, একটি ফাংশন যা 0 থেকে 1f সীমার মধ্যে একটি float গ্রহণ করে, ফাংশনটি প্যারামিটারটি এই সীমার মধ্যে আছে কিনা তা পরীক্ষা করতে পারে এবং সীমার বাইরে গেলে IllegalArgumentException থ্রো করতে পারে, অথবা একটি ছোট String কোনো বৈধ ফরম্যাটের (যেমন শুধুমাত্র অ্যালফানিউমেরিক) সাথে সামঞ্জস্যপূর্ণ কিনা তা পরীক্ষা করা হতে পারে। (মনে রাখবেন যে সিস্টেম সার্ভারের কখনোই অ্যাপ প্রসেসকে বিশ্বাস করা উচিত নয়! যেকোনো সিস্টেম সার্ভিসের উচিত এই পরীক্ষাগুলো নিজের মধ্যেই করা।)

অন্যান্য সকল ত্রুটি প্রদত্ত কলব্যাকে রিপোর্ট করা উচিত। এর মধ্যে অন্তর্ভুক্ত, তবে এতেই সীমাবদ্ধ নয়:

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

অ্যাসিঙ্ক্রোনাস এপিআই-গুলিতে একটি বাতিল করার ব্যবস্থা থাকা উচিত।

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

কলার কর্তৃক প্রদত্ত কলব্যাকগুলির হার্ড রেফারেন্সগুলি রিলিজ করা উচিত।

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

কলারের জন্য কাজ সম্পাদনকারী এক্সিকিউশন ইঞ্জিন সেই কাজটি বন্ধ করে দিতে পারে।

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

ক্যাশ করা বা জমে যাওয়া অ্যাপের জন্য বিশেষ বিবেচ্য বিষয়

যখন এমন অ্যাসিঙ্ক্রোনাস এপিআই ডিজাইন করা হয়, যেখানে কলব্যাকগুলো একটি সিস্টেম প্রসেস থেকে উৎপন্ন হয়ে অ্যাপে পাঠানো হয়, তখন নিম্নলিখিত বিষয়গুলো বিবেচনা করুন:

  1. প্রসেস এবং অ্যাপ লাইফসাইকেল : প্রাপক অ্যাপ প্রসেসটি ক্যাশড অবস্থায় থাকতে পারে।
  2. ক্যাশড অ্যাপস ফ্রিজার : প্রাপকের অ্যাপ প্রসেসটি আটকে যেতে পারে।

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

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

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

পর্যালোচনাধীন:

  • অ্যাপের প্রসেসটি ক্যাশড থাকা অবস্থায় অ্যাপ কলব্যাক ডিসপ্যাচ করা স্থগিত রাখার বিষয়টি আপনার বিবেচনা করা উচিত।
  • অ্যাপের প্রসেসটি ফ্রিজ থাকা অবস্থায় আপনাকে অবশ্যই অ্যাপ কলব্যাক ডিসপ্যাচ করা বন্ধ রাখতে হবে।

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

অ্যাপগুলি কখন ক্যাশড অবস্থায় প্রবেশ করে বা তা থেকে বেরিয়ে আসে তা ট্র্যাক করতে:

mActivityManager.addOnUidImportanceListener(
    new UidImportanceListener() { ... },
    IMPORTANCE_CACHED);

অ্যাপ কখন ফ্রিজ বা আনফ্রিজ হয় তা ট্র্যাক করতে:

IBinder binder = <...>;
binder.addFrozenStateChangeCallback(executor, callback);

অ্যাপ কলব্যাক প্রেরণ পুনরায় শুরু করার কৌশল

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

উদাহরণস্বরূপ:

IBinder binder = <...>;
bool shouldSendCallbacks = true;
binder.addFrozenStateChangeCallback(executor, (who, state) -> {
    if (state == IBinder.FrozenStateChangeCallback.STATE_FROZEN) {
        shouldSendCallbacks = false;
    } else if (state == IBinder.FrozenStateChangeCallback.STATE_UNFROZEN) {
        shouldSendCallbacks = true;
    }
});

এর বিকল্প হিসেবে, আপনি RemoteCallbackList ব্যবহার করতে পারেন, যা টার্গেট প্রসেসটি ফ্রিজ হয়ে গেলে সেটিতে কলব্যাক ডেলিভার না করার বিষয়টি নিশ্চিত করে।

উদাহরণস্বরূপ:

RemoteCallbackList<IInterface> rc =
        new RemoteCallbackList.Builder<IInterface>(
                        RemoteCallbackList.FROZEN_CALLEE_POLICY_DROP)
                .setExecutor(executor)
                .build();
rc.register(callback);
rc.broadcast((callback) -> callback.foo(bar));

callback.foo() শুধুমাত্র তখনই কল করা হয় যখন প্রসেসটি ফ্রিজ করা থাকে না।

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

interface BatteryListener {
    void onBatteryPercentageChanged(int newPercentage);
}

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

RemoteCallbackList<IInterface> rc =
        new RemoteCallbackList.Builder<IInterface>(
                        RemoteCallbackList.FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT)
                .setExecutor(executor)
                .build();
rc.register(callback);
rc.broadcast((callback) -> callback.onBatteryPercentageChanged(value));

কিছু ক্ষেত্রে, আপনি অ্যাপে পাঠানো সর্বশেষ মানটি ট্র্যাক করতে পারেন, যাতে একবার আনফ্রিজ হয়ে গেলে অ্যাপটিকে একই মান সম্পর্কে আর অবহিত করার প্রয়োজন না হয়।

অবস্থাকে আরও জটিল ডেটা হিসাবে প্রকাশ করা যেতে পারে। অ্যাপগুলিকে নেটওয়ার্ক ইন্টারফেস সম্পর্কে অবহিত করার জন্য একটি কাল্পনিক API বিবেচনা করুন:

interface NetworkListener {
    void onAvailable(Network network);
    void onLost(Network network);
    void onChanged(Network network);
}

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

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

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

ডেভেলপার ডকুমেন্টেশনের জন্য বিবেচ্য বিষয়সমূহ

অ্যাসিঙ্ক ইভেন্টের ডেলিভারি বিলম্বিত হতে পারে, কারণ হয় প্রেরক পূর্ববর্তী বিভাগে দেখানো অনুযায়ী কিছু সময়ের জন্য ডেলিভারি স্থগিত রেখেছেন অথবা প্রাপক অ্যাপটি সময়মতো ইভেন্টটি প্রসেস করার জন্য পর্যাপ্ত ডিভাইস রিসোর্স পায়নি।

তাদের অ্যাপে কোনো ইভেন্টের নোটিফিকেশন আসার সময় এবং ইভেন্টটি প্রকৃতপক্ষে ঘটার সময়ের মধ্যবর্তী সময় নিয়ে ডেভেলপারদের অনুমান করা থেকে নিরুৎসাহিত করুন।

এপিআই স্থগিত করার বিষয়ে ডেভেলপারদের প্রত্যাশা

যেসব ডেভেলপার কোটলিনের স্ট্রাকচার্ড কনকারেন্সির সাথে পরিচিত, তারা যেকোনো সাসপেন্ডিং এপিআই থেকে নিম্নলিখিত আচরণগুলো প্রত্যাশা করেন:

সাসপেন্ড ফাংশনগুলো রিটার্ন বা থ্রো করার আগে সংশ্লিষ্ট সমস্ত কাজ সম্পন্ন করবে।

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

সাসপেন্ড ফাংশনগুলোর শুধুমাত্র ইন-প্লেস কলব্যাক প্যারামিটার কল করা উচিত।

সাসপেন্ড ফাংশন রিটার্ন করার আগে সর্বদা এর সাথে সম্পর্কিত সমস্ত কাজ সম্পন্ন করবে, তাই সাসপেন্ড ফাংশন রিটার্ন করার পর এটি কখনই প্রদত্ত কোনো কলব্যাক বা অন্য কোনো ফাংশন প্যারামিটারকে কল করবে না বা সেটির রেফারেন্স ধরে রাখবে না।

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

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

সাসপেন্ড ফাংশনগুলোতে kotlinx.coroutines জব ক্যান্সেলেশন সমর্থন থাকা উচিত।

যেকোনো সাসপেন্ড ফাংশনকে অবশ্যই kotlinx.coroutines দ্বারা সংজ্ঞায়িত জব ক্যান্সেলেশনের সাথে সামঞ্জস্যপূর্ণ হতে হবে। যদি চলমান কোনো অপারেশনের কলিং জব বাতিল করা হয়, তবে ফাংশনটির উচিত যত তাড়াতাড়ি সম্ভব একটি CancellationException সহ পুনরায় চালু হওয়া, যাতে কলার যত তাড়াতাড়ি সম্ভব তার কাজ গুছিয়ে নিয়ে চালিয়ে যেতে পারে। এটি suspendCancellableCoroutine এবং kotlinx.coroutines দ্বারা প্রদত্ত অন্যান্য সাসপেন্ডিং এপিআই দ্বারা স্বয়ংক্রিয়ভাবে পরিচালিত হয়। লাইব্রেরি ইমপ্লিমেন্টেশনগুলোর সাধারণত সরাসরি suspendCoroutine ব্যবহার করা উচিত নয়, কারণ এটি ডিফল্টভাবে এই ক্যান্সেলেশন আচরণটি সমর্থন করে না।

যেসব ফাংশন ব্যাকগ্রাউন্ডে (নন-মেইন বা UI থ্রেড) ব্লকিং কাজ সম্পাদন করে, সেগুলোকে সাসপেন্ড করার জন্য ব্যবহৃত ডিসপ্যাচার কনফিগার করার একটি উপায় অবশ্যই প্রদান করতে হবে।

থ্রেড পরিবর্তন করার জন্য কোনো ব্লকিং ফাংশনকে সম্পূর্ণরূপে স্থগিত করার সুপারিশ করা হয় না

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

যেসব ফাংশন ব্লকিং কাজ সম্পাদনের জন্য একটি ঐচ্ছিক CoroutineContext বা Dispatcher প্যারামিটার গ্রহণ করে এবং পরে সেই ডিসপ্যাচারে স্থানান্তরিত হয়, সেগুলোকে সাসপেন্ড করার পরিবর্তে অন্তর্নিহিত ব্লকিং ফাংশনটিকে উন্মুক্ত করে দেওয়া উচিত এবং কলকারী ডেভেলপারদেরকে তাদের নিজস্ব withContext কল ব্যবহার করে কাজটি একটি নির্বাচিত ডিসপ্যাচারে পাঠানোর পরামর্শ দেওয়া উচিত।

ক্লাসগুলি কোরাউটিন চালু করছে

যে ক্লাসগুলো কো-রুটিন চালু করে, সেই লঞ্চ অপারেশনগুলো সম্পাদনের জন্য তাদের অবশ্যই একটি CoroutineScope থাকতে হবে। স্ট্রাকচার্ড কনকারেন্সি নীতিগুলো মেনে চললে, সেই স্কোপটি অর্জন ও পরিচালনার জন্য নিম্নলিখিত কাঠামোগত প্যাটার্নগুলো অনুসরণ করতে হয়।

অন্য স্কোপে সমান্তরাল টাস্ক চালু করে এমন কোনো ক্লাস লেখার আগে, বিকল্প প্যাটার্নগুলো বিবেচনা করুন:

class MyClass {
    private val requests = Channel<MyRequest>(Channel.UNLIMITED)

    suspend fun handleRequests() {
        coroutineScope {
            for (request in requests) {
                // Allow requests to be processed concurrently;
                // alternatively, omit the [launch] and outer [coroutineScope]
                // to process requests serially
                launch {
                    processRequest(request)
                }
            }
        }
    }

    fun submitRequest(request: MyRequest) {
        requests.trySend(request).getOrThrow()
    }
}

সমান্তরাল কাজ সম্পাদনের জন্য একটি suspend fun ব্যবহার করলে কলার তার নিজস্ব কনটেক্সটে অপারেশনটি আহ্বান করতে পারে, ফলে MyClass কে CoroutineScope পরিচালনা করার প্রয়োজন হয় না। অনুরোধ প্রক্রিয়াকরণকে সিরিয়ালাইজ করা সহজ হয়ে যায় এবং স্টেট প্রায়শই ক্লাস প্রপার্টি হিসেবে না থেকে handleRequests এর লোকাল ভেরিয়েবল হিসেবে থাকতে পারে, যেগুলোর জন্য অন্যথায় অতিরিক্ত সিনক্রোনাইজেশনের প্রয়োজন হতো।

যে ক্লাসগুলো কো-রুটিন পরিচালনা করে, তাদের close এবং cancel মেথড প্রকাশ করা উচিত।

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

private val myJob = Job(parent = `CoroutineContext`[Job])
private val myScope = CoroutineScope(`CoroutineContext` + myJob)

fun cancel() {
    myJob.cancel()
}

অবজেক্টটি দ্বারা সম্পাদিত কোনো অসমাপ্ত সমান্তরাল কাজের সমাপ্তির জন্য ব্যবহারকারী কোডকে অপেক্ষা করার সুযোগ দিতে একটি join() মেথডও প্রদান করা যেতে পারে। (এর মধ্যে কোনো অপারেশন বাতিল করে সম্পাদিত পরিষ্করণমূলক কাজও অন্তর্ভুক্ত থাকতে পারে।)

suspend fun join() {
    myJob.join()
}

টার্মিনাল অপারেশন নামকরণ

কোনো অবজেক্টের মালিকানাধীন চলমান কনকারেন্ট টাস্কগুলোকে সুষ্ঠুভাবে বন্ধ করার জন্য ব্যবহৃত মেথডগুলোর নামটি, শাটডাউন প্রক্রিয়াটির আচরণগত চুক্তিকে প্রতিফলিত করবে:

যখন চলমান অপারেশনগুলো সম্পন্ন হতে পারে কিন্তু close() () কলটি রিটার্ন করার পর কোনো নতুন অপারেশন শুরু করা যায় না, তখন close() ব্যবহার করুন।

চলমান কোনো অপারেশন সম্পূর্ণ হওয়ার আগেই বাতিল করার প্রয়োজন হলে cancel() ব্যবহার করুন। cancel() কলটি রিটার্ন করার পর কোনো নতুন অপারেশন শুরু করা যাবে না।

ক্লাস কনস্ট্রাক্টরগুলো CoroutineContext গ্রহণ করে, CoroutineScope নয়।

যখন অবজেক্টগুলোকে সরাসরি কোনো প্রদত্ত প্যারেন্ট স্কোপে চালু হতে নিষেধ করা হয়, তখন কনস্ট্রাক্টর প্যারামিটার হিসেবে CoroutineScope এর উপযোগিতা নষ্ট হয়ে যায়:

// Don't do this
class MyClass(scope: CoroutineScope) {
    private val myJob = Job(parent = scope.`CoroutineContext`[Job])
    private val myScope = CoroutineScope(scope.`CoroutineContext` + myJob)

    // ... the [scope] constructor parameter is never used again
}

CoroutineScope একটি অপ্রয়োজনীয় এবং বিভ্রান্তিকর র‍্যাপার হয়ে ওঠে, যা কিছু ক্ষেত্রে শুধুমাত্র কনস্ট্রাক্টর প্যারামিটার হিসেবে পাস করার জন্য তৈরি করা হতে পারে এবং পরে বাতিল করে দেওয়া হয়:

// Don't do this; just pass the context
val myObject = MyClass(CoroutineScope(parentScope.`CoroutineContext` + Dispatchers.IO))

CoroutineContext প্যারামিটারগুলো ডিফল্টভাবে EmptyCoroutineContext হয়।

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

class MyOuterClass(
    `CoroutineContext`: `CoroutineContext` = Empty`CoroutineContext`
) {
    private val innerObject = MyInnerClass(`CoroutineContext`)

    // ...
}

class MyInnerClass(
    `CoroutineContext`: `CoroutineContext` = Empty`CoroutineContext`
) {
    private val job = Job(parent = `CoroutineContext`[Job])
    private val scope = CoroutineScope(`CoroutineContext` + job)

    // ...
}