অ্যান্ড্রয়েড ইন্টারফেস ডেফিনিশন ল্যাঙ্গুয়েজ (এআইডিএল) অন্যান্য IDL-এর মতোই: এটি আপনাকে প্রোগ্রামিং ইন্টারফেসকে সংজ্ঞায়িত করতে দেয় যা ক্লায়েন্ট এবং পরিষেবা উভয়ই ইন্টারপ্রসেস কমিউনিকেশন (IPC) ব্যবহার করে একে অপরের সাথে যোগাযোগ করার জন্য সম্মত হয়।
অ্যান্ড্রয়েডে, একটি প্রক্রিয়া সাধারণত অন্য প্রক্রিয়ার মেমরি অ্যাক্সেস করতে পারে না। কথা বলার জন্য, তাদের তাদের বস্তুগুলিকে আদিমগুলিতে পচন করতে হবে যা অপারেটিং সিস্টেম বুঝতে পারে এবং আপনার জন্য সেই সীমানা জুড়ে বস্তুগুলিকে মার্শাল করতে পারে। মার্শালিং করার জন্য কোডটি লিখতে ক্লান্তিকর, তাই Android এটি AIDL এর সাথে আপনার জন্য পরিচালনা করে।
দ্রষ্টব্য: যদি আপনি বিভিন্ন অ্যাপ্লিকেশনের ক্লায়েন্টদের IPC-এর জন্য আপনার পরিষেবা অ্যাক্সেস করতে দেন এবং আপনি আপনার পরিষেবাতে মাল্টিথ্রেডিং পরিচালনা করতে চান তবেই এআইডিএল প্রয়োজনীয়। যদি আপনাকে বিভিন্ন অ্যাপ্লিকেশন জুড়ে সমসাময়িক IPC সঞ্চালনের প্রয়োজন না হয়, তাহলে একটি Binder
প্রয়োগ করে আপনার ইন্টারফেস তৈরি করুন। আপনি যদি IPC সঞ্চালন করতে চান কিন্তু মাল্টিথ্রেডিং পরিচালনা করার প্রয়োজন না হয় , তাহলে একটি Messenger
ব্যবহার করে আপনার ইন্টারফেস বাস্তবায়ন করুন। যাই হোক না কেন, নিশ্চিত হোন যে আপনি একটি AIDL প্রয়োগ করার আগে আবদ্ধ পরিষেবাগুলি বুঝতে পেরেছেন৷
আপনি আপনার AIDL ইন্টারফেস ডিজাইন করা শুরু করার আগে, সচেতন থাকুন যে AIDL ইন্টারফেসে কলগুলি সরাসরি ফাংশন কল। যে থ্রেডে কলটি ঘটে সে সম্পর্কে অনুমান করবেন না। কলটি স্থানীয় প্রক্রিয়া বা দূরবর্তী প্রক্রিয়ার একটি থ্রেড থেকে এসেছে কিনা তার উপর নির্ভর করে যা ঘটে তা ভিন্ন:
- স্থানীয় প্রক্রিয়া থেকে করা কলগুলি একই থ্রেডে কার্যকর হয় যা কল করছে। যদি এটি আপনার প্রধান UI থ্রেড হয়, তাহলে সেই থ্রেডটি AIDL ইন্টারফেসে কার্যকর হতে থাকে। যদি এটি অন্য থ্রেড হয়, তাহলে সেটিই পরিষেবাতে আপনার কোডটি কার্যকর করে। এইভাবে, যদি শুধুমাত্র স্থানীয় থ্রেডগুলি পরিষেবাটি অ্যাক্সেস করে তবে আপনি এটিতে কোন থ্রেডগুলি কার্যকর করছে তা সম্পূর্ণরূপে নিয়ন্ত্রণ করতে পারেন৷ কিন্তু যদি তা হয়, তবে এআইডিএল ব্যবহার করবেন না; পরিবর্তে, একটি
Binder
প্রয়োগ করে ইন্টারফেস তৈরি করুন। - একটি দূরবর্তী প্রক্রিয়া থেকে কল একটি থ্রেড পুল থেকে প্রেরণ করা হয় প্ল্যাটফর্মটি আপনার নিজস্ব প্রক্রিয়ার মধ্যে বজায় রাখে। অজানা থ্রেড থেকে ইনকামিং কলের জন্য প্রস্তুত থাকুন, একই সময়ে একাধিক কল হচ্ছে। অন্য কথায়, একটি এআইডিএল ইন্টারফেসের বাস্তবায়ন সম্পূর্ণরূপে থ্রেড-নিরাপদ হতে হবে। একই দূরবর্তী বস্তুর একটি থ্রেড থেকে করা কল রিসিভার প্রান্তে ক্রমানুসারে আসে।
-
oneway
কীওয়ার্ড রিমোট কলের আচরণ পরিবর্তন করে। যখন এটি ব্যবহার করা হয়, একটি দূরবর্তী কল ব্লক করে না। এটি লেনদেনের ডেটা পাঠায় এবং অবিলম্বে ফেরত দেয়। ইন্টারফেসের বাস্তবায়ন শেষ পর্যন্তBinder
থ্রেড পুল থেকে একটি সাধারণ দূরবর্তী কল হিসাবে এটি একটি নিয়মিত কল হিসাবে গ্রহণ করে। যদি স্থানীয় কলের সাথেoneway
ব্যবহার করা হয়, তাহলে কোন প্রভাব নেই এবং কলটি এখনও সিঙ্ক্রোনাস থাকে।
একটি এআইডিএল ইন্টারফেস সংজ্ঞায়িত করা
জাভা প্রোগ্রামিং ল্যাঙ্গুয়েজ সিনট্যাক্স ব্যবহার করে একটি .aidl
ফাইলে আপনার AIDL ইন্টারফেসটি সংজ্ঞায়িত করুন, তারপর এটিকে সোর্স কোডে সংরক্ষণ করুন, src/
ডিরেক্টরিতে, পরিষেবাটি হোস্ট করা অ্যাপ্লিকেশন এবং পরিষেবার সাথে সংযুক্ত অন্য যেকোন অ্যাপ্লিকেশনের।
আপনি যখন .aidl
ফাইল ধারণকারী প্রতিটি অ্যাপ্লিকেশন তৈরি করেন, তখন Android SDK টুলগুলি .aidl
ফাইলের উপর ভিত্তি করে একটি IBinder
ইন্টারফেস তৈরি করে এবং প্রকল্পের gen/
ডিরেক্টরিতে সংরক্ষণ করে। পরিষেবাটিকে অবশ্যই উপযুক্ত হিসাবে IBinder
ইন্টারফেস প্রয়োগ করতে হবে। ক্লায়েন্ট অ্যাপ্লিকেশনগুলি তারপর আইপিসি সম্পাদনের জন্য IBinder
থেকে পরিষেবা এবং কল পদ্ধতির সাথে আবদ্ধ হতে পারে।
AIDL ব্যবহার করে একটি আবদ্ধ পরিষেবা তৈরি করতে, এই পদক্ষেপগুলি অনুসরণ করুন, যা অনুসরণ করা বিভাগগুলিতে বর্ণিত হয়েছে:
-
.aidl
ফাইল তৈরি করুনএই ফাইলটি পদ্ধতি স্বাক্ষর সহ প্রোগ্রামিং ইন্টারফেস সংজ্ঞায়িত করে।
- ইন্টারফেস বাস্তবায়ন
Android SDK টুলগুলি আপনার
.aidl
ফাইলের উপর ভিত্তি করে জাভা প্রোগ্রামিং ভাষায় একটি ইন্টারফেস তৈরি করে। এই ইন্টারফেসটিতেStub
নামে একটি অভ্যন্তরীণ বিমূর্ত শ্রেণী রয়েছে যাBinder
প্রসারিত করে এবং আপনার AIDL ইন্টারফেস থেকে পদ্ধতি প্রয়োগ করে। আপনাকে অবশ্যইStub
ক্লাস প্রসারিত করতে হবে এবং পদ্ধতিগুলি বাস্তবায়ন করতে হবে। - ক্লায়েন্টদের কাছে ইন্টারফেসটি প্রকাশ করুন
একটি
Service
প্রয়োগ করুন এবং আপনারStub
ক্লাসের বাস্তবায়ন ফিরিয়ে দিতেonBind()
ওভাররাইড করুন।
সতর্কতা: আপনার প্রথম প্রকাশের পরে আপনি আপনার AIDL ইন্টারফেসে যে কোনও পরিবর্তন করবেন তা অবশ্যই আপনার পরিষেবা ব্যবহার করে এমন অন্যান্য অ্যাপ্লিকেশনগুলিকে ভাঙা এড়াতে পিছিয়ে সামঞ্জস্যপূর্ণ থাকতে হবে। অর্থাৎ, যেহেতু আপনার .aidl
ফাইলটি অবশ্যই অন্যান্য অ্যাপ্লিকেশনগুলিতে অনুলিপি করা উচিত যাতে তারা আপনার পরিষেবার ইন্টারফেস অ্যাক্সেস করতে পারে, আপনাকে অবশ্যই মূল ইন্টারফেসের জন্য সমর্থন বজায় রাখতে হবে।
.aidl ফাইল তৈরি করুন
এআইডিএল একটি সাধারণ সিনট্যাক্স ব্যবহার করে যা আপনাকে এক বা একাধিক পদ্ধতির সাথে একটি ইন্টারফেস ঘোষণা করতে দেয় যা পরামিতি এবং রিটার্ন মান নিতে পারে। পরামিতি এবং রিটার্ন মান যে কোনো ধরনের হতে পারে, এমনকি অন্যান্য এআইডিএল-জেনারেটেড ইন্টারফেসও।
আপনাকে অবশ্যই জাভা প্রোগ্রামিং ভাষা ব্যবহার করে .aidl
ফাইলটি তৈরি করতে হবে। প্রতিটি .aidl
ফাইল একটি একক ইন্টারফেস সংজ্ঞায়িত করতে হবে এবং শুধুমাত্র ইন্টারফেস ঘোষণা এবং পদ্ধতি স্বাক্ষর প্রয়োজন।
ডিফল্টরূপে, AIDL নিম্নলিখিত ডেটা প্রকারগুলিকে সমর্থন করে:
- জাভা প্রোগ্রামিং ভাষার সমস্ত আদিম প্রকার (যেমন
int
,long
,char
,boolean
, ইত্যাদি) - যেকোনো ধরনের অ্যারে, যেমন
int[]
বাMyParcelable[]
-
String
-
CharSequence
-
List
List
সমস্ত উপাদান অবশ্যই এই তালিকার সমর্থিত ডেটা টাইপের একটি হতে হবে বা আপনার ঘোষণা করা অন্য এআইডিএল-জেনারেটেড ইন্টারফেস বা পার্সেবলগুলির একটি হতে হবে। একটিList
ঐচ্ছিকভাবে একটি প্যারামিটারাইজড টাইপ ক্লাস হিসাবে ব্যবহার করা যেতে পারে, যেমনList<String>
। অন্য পক্ষ যে প্রকৃত কংক্রিট ক্লাসটি গ্রহণ করে তা সর্বদা একটিArrayList
হয়, যদিও পদ্ধতিটিList
ইন্টারফেস ব্যবহার করার জন্য তৈরি করা হয়। -
Map
Map
সমস্ত উপাদান অবশ্যই এই তালিকার সমর্থিত ডেটা প্রকারগুলির একটি হতে হবে বা আপনার ঘোষণা করা অন্যান্য AIDL-উত্পন্ন ইন্টারফেস বা পার্সেবলগুলির একটি হতে হবে৷ প্যারামিটারাইজড টাইপ ম্যাপ, যেমন ফর্মMap<String,Integer>
, সমর্থিত নয়। অন্য পক্ষ যে প্রকৃত কংক্রিট ক্লাসটি গ্রহণ করে তা সর্বদা একটিHashMap
, যদিও পদ্ধতিটিMap
ইন্টারফেস ব্যবহার করার জন্য তৈরি করা হয়।Map
বিকল্প হিসাবে একটিBundle
ব্যবহার করার কথা বিবেচনা করুন৷
পূর্বে তালিকাভুক্ত নয় এমন প্রতিটি অতিরিক্ত প্রকারের জন্য আপনাকে অবশ্যই একটি import
বিবৃতি অন্তর্ভুক্ত করতে হবে, এমনকি যদি সেগুলি আপনার ইন্টারফেসের মতো একই প্যাকেজে সংজ্ঞায়িত করা হয়।
আপনার পরিষেবা ইন্টারফেস সংজ্ঞায়িত করার সময়, সচেতন থাকুন যে:
- পদ্ধতিগুলি শূন্য বা তার বেশি প্যারামিটার নিতে পারে এবং একটি মান বা অকার্যকর ফেরত দিতে পারে।
- সমস্ত অ-আদি পরামিতিগুলির জন্য একটি দিকনির্দেশক ট্যাগের প্রয়োজন হয় যা নির্দেশ করে যে ডেটা কোন পথে যায়:
in
,out
, বাinout
(নীচের উদাহরণটি দেখুন)।আদিম,
String
,IBinder
, এবং এআইডিএল-জেনারেটেড ইন্টারফেসগুলিin
থাকে এবং অন্যথায় হতে পারে না।সতর্কতা: প্রকৃতপক্ষে যা প্রয়োজন তার দিকনির্দেশ সীমিত করুন, কারণ মার্শালিং প্যারামিটার ব্যয়বহুল।
-
.aidl
ফাইলে অন্তর্ভুক্ত সমস্ত কোড মন্তব্যগুলি আমদানি এবং প্যাকেজ বিবৃতিগুলির আগে মন্তব্যগুলি ছাড়া জেনারেট করাIBinder
ইন্টারফেসে অন্তর্ভুক্ত করা হয়েছে। - স্ট্রিং এবং int ধ্রুবকগুলি AIDL ইন্টারফেসে সংজ্ঞায়িত করা যেতে পারে, যেমন
const int VERSION = 1;
. - পদ্ধতি কল একটি
transact()
কোড দ্বারা প্রেরণ করা হয়, যা সাধারণত ইন্টারফেসের একটি পদ্ধতি সূচকের উপর ভিত্তি করে। যেহেতু এটি সংস্করণকে কঠিন করে তোলে, আপনি ম্যানুয়ালি একটি পদ্ধতিতে লেনদেন কোড নির্ধারণ করতে পারেন:void method() = 10;
. - বাতিলযোগ্য আর্গুমেন্ট এবং রিটার্নের ধরন অবশ্যই
@nullable
ব্যবহার করে টীকা করতে হবে।
এখানে একটি উদাহরণ .aidl
ফাইল আছে:
// IRemoteService.aidl package com.example.android; // Declare any non-default types here with import statements. /** Example service interface */ interface IRemoteService { /** Request the process ID of this service. */ int getPid(); /** Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); }
আপনার প্রকল্পের src/
ডিরেক্টরিতে আপনার .aidl
ফাইলটি সংরক্ষণ করুন। আপনি যখন আপনার অ্যাপ্লিকেশন তৈরি করেন, SDK টুলগুলি আপনার প্রকল্পের gen/
ডিরেক্টরিতে IBinder
ইন্টারফেস ফাইল তৈরি করে। জেনারেট করা ফাইলের নাম .aidl
ফাইলের নামের সাথে মিলে যায়, কিন্তু একটি .java
এক্সটেনশনের সাথে। উদাহরণস্বরূপ, IRemoteService.aidl
ফলাফল IRemoteService.java
।
আপনি যদি অ্যান্ড্রয়েড স্টুডিও ব্যবহার করেন, ক্রমবর্ধমান বিল্ড প্রায় সঙ্গে সঙ্গে বাইন্ডার ক্লাস তৈরি করে। আপনি যদি অ্যান্ড্রয়েড স্টুডিও ব্যবহার না করেন, তাহলে পরের বার আপনি আপনার অ্যাপ্লিকেশন তৈরি করার সময় গ্রেডল টুল বাইন্ডার ক্লাস তৈরি করে। আপনি .aidl
ফাইলটি লেখা শেষ করার সাথে সাথে gradle assembleDebug
বা gradle assembleRelease
দিয়ে আপনার প্রকল্প তৈরি করুন, যাতে আপনার কোড জেনারেট করা ক্লাসের সাথে লিঙ্ক করতে পারে।
ইন্টারফেস বাস্তবায়ন
আপনি যখন আপনার অ্যাপ্লিকেশন তৈরি করেন, তখন Android SDK টুলগুলি আপনার .aidl
ফাইলের নামে একটি .java
ইন্টারফেস ফাইল তৈরি করে। জেনারেট করা ইন্টারফেসে Stub
নামে একটি সাবক্লাস রয়েছে যা এটির মূল ইন্টারফেসের একটি বিমূর্ত বাস্তবায়ন, যেমন YourInterface.Stub
, এবং .aidl
ফাইল থেকে সমস্ত পদ্ধতি ঘোষণা করে।
দ্রষ্টব্য: Stub
কয়েকটি সহায়ক পদ্ধতিকেও সংজ্ঞায়িত করে, বিশেষ করে asInterface()
, যা একটি IBinder
নেয়, সাধারণত একটি ক্লায়েন্টের onServiceConnected()
কলব্যাক পদ্ধতিতে পাস করা হয় এবং স্টাব ইন্টারফেসের একটি উদাহরণ প্রদান করে। এই কাস্টটি কীভাবে তৈরি করবেন সে সম্পর্কে আরও বিশদ বিবরণের জন্য, একটি আইপিসি পদ্ধতিতে কল করা বিভাগটি দেখুন।
.aidl
থেকে জেনারেট করা ইন্টারফেস বাস্তবায়ন করতে, YourInterface.Stub
এর মতো জেনারেট করা Binder
ইন্টারফেস প্রসারিত করুন এবং .aidl
ফাইল থেকে উত্তরাধিকারসূত্রে প্রাপ্ত পদ্ধতিগুলি বাস্তবায়ন করুন।
এখানে IRemoteService
নামক একটি ইন্টারফেসের বাস্তবায়নের একটি উদাহরণ রয়েছে, যা পূর্ববর্তী IRemoteService.aidl
উদাহরণ দ্বারা সংজ্ঞায়িত করা হয়েছে, একটি বেনামী উদাহরণ ব্যবহার করে:
কোটলিন
private val binder = object : IRemoteService.Stub() { override fun getPid(): Int = Process.myPid() override fun basicTypes( anInt: Int, aLong: Long, aBoolean: Boolean, aFloat: Float, aDouble: Double, aString: String ) { // Does nothing. } }
জাভা
private final IRemoteService.Stub binder = new IRemoteService.Stub() { public int getPid(){ return Process.myPid(); } public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) { // Does nothing. } };
এখন binder
হল Stub
ক্লাসের একটি উদাহরণ (একটি Binder
), যা পরিষেবার জন্য IPC ইন্টারফেসকে সংজ্ঞায়িত করে। পরবর্তী ধাপে, এই উদাহরণটি ক্লায়েন্টদের কাছে প্রকাশ করা হয় যাতে তারা পরিষেবাটির সাথে যোগাযোগ করতে পারে।
আপনার এআইডিএল ইন্টারফেস প্রয়োগ করার সময় কয়েকটি নিয়ম সম্পর্কে সচেতন থাকুন:
- ইনকামিং কলগুলি মূল থ্রেডে কার্যকর করার গ্যারান্টি দেওয়া হয় না, তাই আপনাকে শুরু থেকেই মাল্টিথ্রেডিং সম্পর্কে চিন্তা করতে হবে এবং থ্রেড-নিরাপদ হতে আপনার পরিষেবাটি সঠিকভাবে তৈরি করতে হবে।
- ডিফল্টরূপে, IPC কলগুলি সিঙ্ক্রোনাস। আপনি যদি জানেন যে পরিষেবাটি একটি অনুরোধ সম্পূর্ণ করতে কয়েক মিলিসেকেন্ডের বেশি সময় নেয়, তাহলে কার্যকলাপের মূল থ্রেড থেকে এটিকে কল করবেন না। এটি অ্যাপ্লিকেশনটিকে হ্যাং করতে পারে, যার ফলে অ্যান্ড্রয়েড একটি "অ্যাপ্লিকেশন ইজ নট রেসপন্ডিং" ডায়ালগ প্রদর্শন করে৷ ক্লায়েন্টে একটি পৃথক থ্রেড থেকে এটি কল করুন।
-
Parcel.writeException()
এর জন্য রেফারেন্স ডকুমেন্টেশনের অধীনে তালিকাভুক্ত ব্যতিক্রম প্রকারগুলিই কলারের কাছে ফেরত পাঠানো হয়।
ক্লায়েন্টদের কাছে ইন্টারফেসটি প্রকাশ করুন
একবার আপনি আপনার পরিষেবার জন্য ইন্টারফেসটি প্রয়োগ করার পরে, আপনাকে এটিকে ক্লায়েন্টদের কাছে প্রকাশ করতে হবে যাতে তারা এটির সাথে আবদ্ধ হতে পারে। আপনার পরিষেবার জন্য ইন্টারফেস প্রকাশ করতে, Service
প্রসারিত করুন এবং আপনার ক্লাসের একটি দৃষ্টান্ত ফিরিয়ে আনতে onBind()
প্রয়োগ করুন যা তৈরি করা Stub
প্রয়োগ করে, যেমনটি পূর্ববর্তী বিভাগে আলোচনা করা হয়েছে। এখানে একটি উদাহরণ পরিষেবা যা IRemoteService
উদাহরণ ইন্টারফেসকে ক্লায়েন্টদের কাছে প্রকাশ করে।
কোটলিন
class RemoteService : Service() { override fun onCreate() { super.onCreate() } override fun onBind(intent: Intent): IBinder { // Return the interface. return binder } private val binder = object : IRemoteService.Stub() { override fun getPid(): Int { return Process.myPid() } override fun basicTypes( anInt: Int, aLong: Long, aBoolean: Boolean, aFloat: Float, aDouble: Double, aString: String ) { // Does nothing. } } }
জাভা
public class RemoteService extends Service { @Override public void onCreate() { super.onCreate(); } @Override public IBinder onBind(Intent intent) { // Return the interface. return binder; } private final IRemoteService.Stub binder = new IRemoteService.Stub() { public int getPid(){ return Process.myPid(); } public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) { // Does nothing. } }; }
এখন, যখন একটি ক্লায়েন্ট, যেমন একটি কার্যকলাপ, এই পরিষেবার সাথে সংযোগ করতে bindService()
কল করে, তখন ক্লায়েন্টের onServiceConnected()
কলব্যাক পরিষেবার onBind()
পদ্ধতি দ্বারা ফিরে আসা binder
ইন্সট্যান্স গ্রহণ করে।
ক্লায়েন্টের অবশ্যই ইন্টারফেস ক্লাসে অ্যাক্সেস থাকতে হবে। তাই যদি ক্লায়েন্ট এবং পরিষেবা পৃথক অ্যাপ্লিকেশনে থাকে, তাহলে ক্লায়েন্টের অ্যাপ্লিকেশনের অবশ্যই .aidl
ফাইলের একটি অনুলিপি তার src/
ডিরেক্টরিতে থাকতে হবে, যা android.os.Binder
ইন্টারফেস তৈরি করে, ক্লায়েন্টকে AIDL পদ্ধতিতে অ্যাক্সেস প্রদান করে।
ক্লায়েন্ট যখন onServiceConnected()
কলব্যাকে IBinder
গ্রহণ করে, তখন তাকে অবশ্যই YourServiceInterface .Stub.asInterface(service)
কল করতে হবে যা YourServiceInterface
টাইপে প্রত্যাবর্তিত প্যারামিটারটি কাস্ট করতে হবে:
কোটলিন
var iRemoteService: IRemoteService? = null val mConnection = object : ServiceConnection { // Called when the connection with the service is established. override fun onServiceConnected(className: ComponentName, service: IBinder) { // Following the preceding example for an AIDL interface, // this gets an instance of the IRemoteInterface, which we can use to call on the service. iRemoteService = IRemoteService.Stub.asInterface(service) } // Called when the connection with the service disconnects unexpectedly. override fun onServiceDisconnected(className: ComponentName) { Log.e(TAG, "Service has unexpectedly disconnected") iRemoteService = null } }
জাভা
IRemoteService iRemoteService; private ServiceConnection mConnection = new ServiceConnection() { // Called when the connection with the service is established. public void onServiceConnected(ComponentName className, IBinder service) { // Following the preceding example for an AIDL interface, // this gets an instance of the IRemoteInterface, which we can use to call on the service. iRemoteService = IRemoteService.Stub.asInterface(service); } // Called when the connection with the service disconnects unexpectedly. public void onServiceDisconnected(ComponentName className) { Log.e(TAG, "Service has unexpectedly disconnected"); iRemoteService = null; } };
আরও নমুনা কোডের জন্য, ApiDemos- এ RemoteService.java
ক্লাস দেখুন।
আইপিসি এর উপর বস্তু পাস করা
অ্যান্ড্রয়েড 10 (এপিআই স্তর 29 বা উচ্চতর), আপনি এআইডিএল-এ সরাসরি Parcelable
অবজেক্ট সংজ্ঞায়িত করতে পারেন। এআইডিএল ইন্টারফেস আর্গুমেন্ট এবং অন্যান্য পার্সেলেবল হিসাবে সমর্থিত প্রকারগুলিও এখানে সমর্থিত। এটি ম্যানুয়ালি মার্শালিং কোড এবং একটি কাস্টম ক্লাস লিখতে অতিরিক্ত কাজ এড়িয়ে যায়। যাইহোক, এটি একটি খালি কাঠামো তৈরি করে। যদি কাস্টম অ্যাক্সেসর বা অন্যান্য কার্যকারিতা আকাঙ্ক্ষিত হয়, তবে পরিবর্তে Parcelable
প্রয়োগ করুন।
package android.graphics; // Declare Rect so AIDL can find it and knows that it implements // the parcelable protocol. parcelable Rect { int left; int top; int right; int bottom; }
পূর্ববর্তী কোড নমুনা স্বয়ংক্রিয়ভাবে left
, top
, right
এবং bottom
পূর্ণসংখ্যা ক্ষেত্র সহ একটি জাভা ক্লাস তৈরি করে। সমস্ত প্রাসঙ্গিক মার্শালিং কোড স্বয়ংক্রিয়ভাবে প্রয়োগ করা হয়, এবং বস্তুটি কোনো বাস্তবায়ন যোগ না করে সরাসরি ব্যবহার করা যেতে পারে।
আপনি একটি আইপিসি ইন্টারফেসের মাধ্যমে একটি প্রক্রিয়া থেকে অন্য প্রক্রিয়াতে একটি কাস্টম ক্লাস পাঠাতে পারেন। যাইহোক, নিশ্চিত করুন যে আপনার ক্লাসের কোডটি IPC চ্যানেলের অন্য দিকে উপলব্ধ রয়েছে এবং আপনার ক্লাসটি অবশ্যই Parcelable
ইন্টারফেস সমর্থন করবে। Parcelable
সমর্থন করা গুরুত্বপূর্ণ কারণ এটি অ্যান্ড্রয়েড সিস্টেমকে আদিম বস্তুতে পচন দিতে দেয় যা প্রসেস জুড়ে মার্শাল করা যেতে পারে।
Parcelable
সমর্থন করে এমন একটি কাস্টম ক্লাস তৈরি করতে, নিম্নলিখিতগুলি করুন:
- আপনার ক্লাসকে
Parcelable
ইন্টারফেস বাস্তবায়ন করুন। -
writeToParcel
প্রয়োগ করুন, যা বস্তুর বর্তমান অবস্থা নেয় এবং এটি একটিParcel
লিখে। - আপনার ক্লাসে
CREATOR
নামক একটি স্ট্যাটিক ফিল্ড যোগ করুন যাParcelable.Creator
ইন্টারফেস বাস্তবায়নকারী একটি বস্তু। - অবশেষে, একটি
.aidl
ফাইল তৈরি করুন যা আপনার পার্সেলযোগ্য শ্রেণী ঘোষণা করে, যেমনটি নিম্নলিখিতRect.aidl
ফাইলের জন্য দেখানো হয়েছে।আপনি যদি একটি কাস্টম বিল্ড প্রক্রিয়া ব্যবহার করেন, তাহলে আপনার বিল্ডে
.aidl
ফাইল যোগ করবেন না । C ভাষায় হেডার ফাইলের মতো, এই.aidl
ফাইলটি কম্পাইল করা হয় না।
এআইডিএল আপনার বস্তুকে মার্শাল এবং আনমার্শাল করার জন্য তৈরি করা কোডে এই পদ্ধতি এবং ক্ষেত্রগুলি ব্যবহার করে।
উদাহরণস্বরূপ, এখানে একটি Rect.aidl
ফাইল একটি Rect
ক্লাস তৈরি করার জন্য যা পার্সেলযোগ্য:
package android.graphics; // Declare Rect so AIDL can find it and knows that it implements // the parcelable protocol. parcelable Rect;
এবং এখানে Rect
ক্লাস কীভাবে Parcelable
প্রোটোকল প্রয়োগ করে তার একটি উদাহরণ।
কোটলিন
import android.os.Parcel import android.os.Parcelable class Rect() : Parcelable { var left: Int = 0 var top: Int = 0 var right: Int = 0 var bottom: Int = 0 companion object CREATOR : Parcelable.Creator<Rect> { override fun createFromParcel(parcel: Parcel): Rect { return Rect(parcel) } override fun newArray(size: Int): Array<Rect?> { return Array(size) { null } } } private constructor(inParcel: Parcel) : this() { readFromParcel(inParcel) } override fun writeToParcel(outParcel: Parcel, flags: Int) { outParcel.writeInt(left) outParcel.writeInt(top) outParcel.writeInt(right) outParcel.writeInt(bottom) } private fun readFromParcel(inParcel: Parcel) { left = inParcel.readInt() top = inParcel.readInt() right = inParcel.readInt() bottom = inParcel.readInt() } override fun describeContents(): Int { return 0 } }
জাভা
import android.os.Parcel; import android.os.Parcelable; public final class Rect implements Parcelable { public int left; public int top; public int right; public int bottom; public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() { public Rect createFromParcel(Parcel in) { return new Rect(in); } public Rect[] newArray(int size) { return new Rect[size]; } }; public Rect() { } private Rect(Parcel in) { readFromParcel(in); } public void writeToParcel(Parcel out, int flags) { out.writeInt(left); out.writeInt(top); out.writeInt(right); out.writeInt(bottom); } public void readFromParcel(Parcel in) { left = in.readInt(); top = in.readInt(); right = in.readInt(); bottom = in.readInt(); } public int describeContents() { return 0; } }
Rect
ক্লাসে মার্শালিং সোজা। আপনি Parcel
লিখতে পারেন এমন অন্যান্য ধরণের মান দেখতে Parcel
অন্যান্য পদ্ধতিগুলি দেখুন।
সতর্কতা: অন্যান্য প্রক্রিয়া থেকে ডেটা প্রাপ্তির নিরাপত্তা প্রভাব মনে রাখবেন। এই ক্ষেত্রে, Rect
Parcel
থেকে চারটি নম্বর পড়ে, কিন্তু কলকারী যা কিছু করার চেষ্টা করছেন তার জন্য এগুলি গ্রহণযোগ্য সীমার মধ্যে রয়েছে তা নিশ্চিত করা আপনার উপর নির্ভর করে। কীভাবে আপনার অ্যাপ্লিকেশনটিকে ম্যালওয়্যার থেকে সুরক্ষিত রাখবেন সে সম্পর্কে আরও তথ্যের জন্য, নিরাপত্তা টিপস দেখুন।
পার্সেবল সমন্বিত বান্ডেল আর্গুমেন্ট সহ পদ্ধতি
যদি একটি পদ্ধতি একটিBundle
অবজেক্ট গ্রহণ করে যাতে পার্সেলেবল থাকতে পারে বলে আশা করা হচ্ছে, তবে নিশ্চিত করুন যে আপনি Bundle.setClassLoader(ClassLoader)
কল করে Bundle
থেকে পড়ার চেষ্টা করার আগে Bundle
ক্লাসলোডার সেট করেছেন। অন্যথায়, আপনি ClassNotFoundException
এ চলে যাবেন যদিও পার্সেবল আপনার অ্যাপ্লিকেশনে সঠিকভাবে সংজ্ঞায়িত করা হয়েছে। উদাহরণস্বরূপ, নিম্নলিখিত নমুনা .aidl
ফাইল বিবেচনা করুন:
// IRectInsideBundle.aidl package com.example.android; /** Example service interface */ interface IRectInsideBundle { /** Rect parcelable is stored in the bundle with key "rect". */ void saveRect(in Bundle bundle); }
ClassLoader
Rect
পড়ার আগে Bundle
স্পষ্টভাবে সেট করা আছে: কোটলিন
private val binder = object : IRectInsideBundle.Stub() { override fun saveRect(bundle: Bundle) { bundle.classLoader = classLoader val rect = bundle.getParcelable<Rect>("rect") process(rect) // Do more with the parcelable. } }
জাভা
private final IRectInsideBundle.Stub binder = new IRectInsideBundle.Stub() { public void saveRect(Bundle bundle){ bundle.setClassLoader(getClass().getClassLoader()); Rect rect = bundle.getParcelable("rect"); process(rect); // Do more with the parcelable. } };
একটি IPC পদ্ধতি কল করা
AIDL এর সাথে সংজ্ঞায়িত একটি দূরবর্তী ইন্টারফেস কল করতে, আপনার কলিং ক্লাসে নিম্নলিখিত পদক্ষেপগুলি নিন:
- প্রকল্প
src/
ডিরেক্টরিতে.aidl
ফাইলটি অন্তর্ভুক্ত করুন। -
IBinder
ইন্টারফেসের একটি উদাহরণ ঘোষণা করুন, যা AIDL-এর উপর ভিত্তি করে তৈরি করা হয়েছে। -
ServiceConnection
বাস্তবায়ন করুন। -
Context.bindService()
এ কল করুন, আপনারServiceConnection
বাস্তবায়নে পাস করুন। - আপনার
onServiceConnected()
এর বাস্তবায়নে, আপনি একটিIBinder
উদাহরণ পাবেন, যাকে বলা হয়service
।YourInterfaceName .Stub.asInterface((IBinder) service )
YourInterface
টাইপে ফেরত দেওয়া প্যারামিটারটি কাস্ট করতে কল করুন। - আপনি আপনার ইন্টারফেসে সংজ্ঞায়িত পদ্ধতিগুলিকে কল করুন। সর্বদা
DeadObjectException
ব্যতিক্রম ফাঁদ, যা সংযোগ বিচ্ছিন্ন হলে নিক্ষেপ করা হয়। এছাড়াও, ট্র্যাপSecurityException
এক্সেপশন, যেগুলি আইপিসি মেথড কলের সাথে জড়িত দুটি প্রক্রিয়ার এআইডিএল সংজ্ঞাগুলি পরস্পরবিরোধী হলে ফেলে দেওয়া হয়। - সংযোগ বিচ্ছিন্ন করতে, আপনার ইন্টারফেসের উদাহরণ সহ
Context.unbindService()
এ কল করুন।
একটি IPC পরিষেবাতে কল করার সময় এই পয়েন্টগুলি মনে রাখবেন:
- বস্তুগুলি প্রসেস জুড়ে রেফারেন্স গণনা করা হয়।
- আপনি পদ্ধতি আর্গুমেন্ট হিসাবে বেনামী বস্তু পাঠাতে পারেন.
একটি পরিষেবার সাথে আবদ্ধ হওয়ার বিষয়ে আরও তথ্যের জন্য, আবদ্ধ পরিষেবার ওভারভিউ পড়ুন।
এখানে কিছু নমুনা কোড রয়েছে যা ApiDemos প্রোজেক্টের রিমোট সার্ভিস নমুনা থেকে নেওয়া একটি AIDL-তৈরি পরিষেবাকে কল করার প্রদর্শন করে।
কোটলিন
private const val BUMP_MSG = 1 class Binding : Activity() { /** The primary interface you call on the service. */ private var mService: IRemoteService? = null /** Another interface you use on the service. */ internal var secondaryService: ISecondary? = null private lateinit var killButton: Button private lateinit var callbackText: TextView private lateinit var handler: InternalHandler private var isBound: Boolean = false /** * Class for interacting with the main interface of the service. */ private val mConnection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { // This is called when the connection with the service is // established, giving us the service object we can use to // interact with the service. We are communicating with our // service through an IDL interface, so get a client-side // representation of that from the raw service object. mService = IRemoteService.Stub.asInterface(service) killButton.isEnabled = true callbackText.text = "Attached." // We want to monitor the service for as long as we are // connected to it. try { mService?.registerCallback(mCallback) } catch (e: RemoteException) { // In this case, the service crashes before we can // do anything with it. We can count on soon being // disconnected (and then reconnected if it can be restarted) // so there is no need to do anything here. } // As part of the sample, tell the user what happened. Toast.makeText( this@Binding, R.string.remote_service_connected, Toast.LENGTH_SHORT ).show() } override fun onServiceDisconnected(className: ComponentName) { // This is called when the connection with the service is // unexpectedly disconnected—that is, its process crashed. mService = null killButton.isEnabled = false callbackText.text = "Disconnected." // As part of the sample, tell the user what happened. Toast.makeText( this@Binding, R.string.remote_service_disconnected, Toast.LENGTH_SHORT ).show() } } /** * Class for interacting with the secondary interface of the service. */ private val secondaryConnection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { // Connecting to a secondary interface is the same as any // other interface. secondaryService = ISecondary.Stub.asInterface(service) killButton.isEnabled = true } override fun onServiceDisconnected(className: ComponentName) { secondaryService = null killButton.isEnabled = false } } private val mBindListener = View.OnClickListener { // Establish a couple connections with the service, binding // by interface names. This lets other applications be // installed that replace the remote service by implementing // the same interface. val intent = Intent(this@Binding, RemoteService::class.java) intent.action = IRemoteService::class.java.name bindService(intent, mConnection, Context.BIND_AUTO_CREATE) intent.action = ISecondary::class.java.name bindService(intent, secondaryConnection, Context.BIND_AUTO_CREATE) isBound = true callbackText.text = "Binding." } private val unbindListener = View.OnClickListener { if (isBound) { // If we have received the service, and hence registered with // it, then now is the time to unregister. try { mService?.unregisterCallback(mCallback) } catch (e: RemoteException) { // There is nothing special we need to do if the service // crashes. } // Detach our existing connection. unbindService(mConnection) unbindService(secondaryConnection) killButton.isEnabled = false isBound = false callbackText.text = "Unbinding." } } private val killListener = View.OnClickListener { // To kill the process hosting the service, we need to know its // PID. Conveniently, the service has a call that returns // that information. try { secondaryService?.pid?.also { pid -> // Note that, though this API lets us request to // kill any process based on its PID, the kernel // still imposes standard restrictions on which PIDs you // can actually kill. Typically this means only // the process running your application and any additional // processes created by that app, as shown here. Packages // sharing a common UID are also able to kill each // other's processes. Process.killProcess(pid) callbackText.text = "Killed service process." } } catch (ex: RemoteException) { // Recover gracefully from the process hosting the // server dying. // For purposes of this sample, put up a notification. Toast.makeText(this@Binding, R.string.remote_call_failed, Toast.LENGTH_SHORT).show() } } // ---------------------------------------------------------------------- // Code showing how to deal with callbacks. // ---------------------------------------------------------------------- /** * This implementation is used to receive callbacks from the remote * service. */ private val mCallback = object : IRemoteServiceCallback.Stub() { /** * This is called by the remote service regularly to tell us about * new values. Note that IPC calls are dispatched through a thread * pool running in each process, so the code executing here is * NOT running in our main thread like most other things. So, * to update the UI, we need to use a Handler to hop over there. */ override fun valueChanged(value: Int) { handler.sendMessage(handler.obtainMessage(BUMP_MSG, value, 0)) } } /** * Standard initialization of this activity. Set up the UI, then wait * for the user to interact with it before doing anything. */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.remote_service_binding) // Watch for button taps. var button: Button = findViewById(R.id.bind) button.setOnClickListener(mBindListener) button = findViewById(R.id.unbind) button.setOnClickListener(unbindListener) killButton = findViewById(R.id.kill) killButton.setOnClickListener(killListener) killButton.isEnabled = false callbackText = findViewById(R.id.callback) callbackText.text = "Not attached." handler = InternalHandler(callbackText) } private class InternalHandler( textView: TextView, private val weakTextView: WeakReference<TextView> = WeakReference(textView) ) : Handler() { override fun handleMessage(msg: Message) { when (msg.what) { BUMP_MSG -> weakTextView.get()?.text = "Received from service: ${msg.arg1}" else -> super.handleMessage(msg) } } } }
জাভা
public static class Binding extends Activity { /** The primary interface we are calling on the service. */ IRemoteService mService = null; /** Another interface we use on the service. */ ISecondary secondaryService = null; Button killButton; TextView callbackText; private InternalHandler handler; private boolean isBound; /** * Standard initialization of this activity. Set up the UI, then wait * for the user to interact with it before doing anything. */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.remote_service_binding); // Watch for button taps. Button button = (Button)findViewById(R.id.bind); button.setOnClickListener(mBindListener); button = (Button)findViewById(R.id.unbind); button.setOnClickListener(unbindListener); killButton = (Button)findViewById(R.id.kill); killButton.setOnClickListener(killListener); killButton.setEnabled(false); callbackText = (TextView)findViewById(R.id.callback); callbackText.setText("Not attached."); handler = new InternalHandler(callbackText); } /** * Class for interacting with the main interface of the service. */ private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // This is called when the connection with the service is // established, giving us the service object we can use to // interact with the service. We are communicating with our // service through an IDL interface, so get a client-side // representation of that from the raw service object. mService = IRemoteService.Stub.asInterface(service); killButton.setEnabled(true); callbackText.setText("Attached."); // We want to monitor the service for as long as we are // connected to it. try { mService.registerCallback(mCallback); } catch (RemoteException e) { // In this case the service crashes before we can even // do anything with it. We can count on soon being // disconnected (and then reconnected if it can be restarted) // so there is no need to do anything here. } // As part of the sample, tell the user what happened. Toast.makeText(Binding.this, R.string.remote_service_connected, Toast.LENGTH_SHORT).show(); } public void onServiceDisconnected(ComponentName className) { // This is called when the connection with the service is // unexpectedly disconnected—that is, its process crashed. mService = null; killButton.setEnabled(false); callbackText.setText("Disconnected."); // As part of the sample, tell the user what happened. Toast.makeText(Binding.this, R.string.remote_service_disconnected, Toast.LENGTH_SHORT).show(); } }; /** * Class for interacting with the secondary interface of the service. */ private ServiceConnection secondaryConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // Connecting to a secondary interface is the same as any // other interface. secondaryService = ISecondary.Stub.asInterface(service); killButton.setEnabled(true); } public void onServiceDisconnected(ComponentName className) { secondaryService = null; killButton.setEnabled(false); } }; private OnClickListener mBindListener = new OnClickListener() { public void onClick(View v) { // Establish a couple connections with the service, binding // by interface names. This lets other applications be // installed that replace the remote service by implementing // the same interface. Intent intent = new Intent(Binding.this, RemoteService.class); intent.setAction(IRemoteService.class.getName()); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); intent.setAction(ISecondary.class.getName()); bindService(intent, secondaryConnection, Context.BIND_AUTO_CREATE); isBound = true; callbackText.setText("Binding."); } }; private OnClickListener unbindListener = new OnClickListener() { public void onClick(View v) { if (isBound) { // If we have received the service, and hence registered with // it, then now is the time to unregister. if (mService != null) { try { mService.unregisterCallback(mCallback); } catch (RemoteException e) { // There is nothing special we need to do if the service // crashes. } } // Detach our existing connection. unbindService(mConnection); unbindService(secondaryConnection); killButton.setEnabled(false); isBound = false; callbackText.setText("Unbinding."); } } }; private OnClickListener killListener = new OnClickListener() { public void onClick(View v) { // To kill the process hosting our service, we need to know its // PID. Conveniently, our service has a call that returns // that information. if (secondaryService != null) { try { int pid = secondaryService.getPid(); // Note that, though this API lets us request to // kill any process based on its PID, the kernel // still imposes standard restrictions on which PIDs you // can actually kill. Typically this means only // the process running your application and any additional // processes created by that app as shown here. Packages // sharing a common UID are also able to kill each // other's processes. Process.killProcess(pid); callbackText.setText("Killed service process."); } catch (RemoteException ex) { // Recover gracefully from the process hosting the // server dying. // For purposes of this sample, put up a notification. Toast.makeText(Binding.this, R.string.remote_call_failed, Toast.LENGTH_SHORT).show(); } } } }; // ---------------------------------------------------------------------- // Code showing how to deal with callbacks. // ---------------------------------------------------------------------- /** * This implementation is used to receive callbacks from the remote * service. */ private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() { /** * This is called by the remote service regularly to tell us about * new values. Note that IPC calls are dispatched through a thread * pool running in each process, so the code executing here is * NOT running in our main thread like most other things. So, * to update the UI, we need to use a Handler to hop over there. */ public void valueChanged(int value) { handler.sendMessage(handler.obtainMessage(BUMP_MSG, value, 0)); } }; private static final int BUMP_MSG = 1; private static class InternalHandler extends Handler { private final WeakReference<TextView> weakTextView; InternalHandler(TextView textView) { weakTextView = new WeakReference<>(textView); } @Override public void handleMessage(Message msg) { switch (msg.what) { case BUMP_MSG: TextView textView = weakTextView.get(); if (textView != null) { textView.setText("Received from service: " + msg.arg1); } break; default: super.handleMessage(msg); } } } }