এই নির্দেশিকায় একটি ইউনিটি অ্যাপ্লিকেশন থেকে একটি প্রাইভেট প্রসেসে অ্যান্ড্রয়েড পারসেপ্টিবল সার্ভিস (ফোরগ্রাউন্ড সার্ভিস বা এফজিএস) চালানোর জন্য প্রয়োজনীয় কনফিগারেশন এবং বাস্তবায়ন সম্পর্কে আলোচনা করা হয়েছে।
১. উপলব্ধিযোগ্য-পরিষেবা সমর্থন কনফিগার করুন
এই অংশে প্রয়োজনীয় অনুমতিগুলো কীভাবে সেট আপ করতে হয় এবং আপনার প্রোজেক্টের ম্যানিফেস্টে সার্ভিসটি কীভাবে ঘোষণা করতে হয়, তা ব্যাখ্যা করা হয়েছে।
১.১ অনুমতি ও পরিষেবা ঘোষণা
কাস্টম Assets/Plugins/Android/AndroidManifest.xml ফাইলে অবশ্যই লঞ্চার অ্যাক্টিভিটি, পারসেপ্টিবল-সার্ভিস পারমিশন, নোটিফিকেশন পারমিশন, নেটওয়ার্ক পারমিশন এবং সার্ভিসটি ডিক্লেয়ার করতে হবে:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.INTERNET" />
<application>
<activity
android:name="com.unity3d.player.UnityPlayerActivity"
android:theme="@style/UnityThemeSelector"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>
<service
android:name="com.sample.fgs.DownloadService"
android:process=":downloader"
android:exported="false"
android:stopWithTask="false"
android:foregroundServiceType="dataSync" />
</application>
</manifest>
foregroundServiceType অবশ্যই আসল কাজের সাথে মিলতে হবে: ডেটা ট্রান্সফারের জন্য dataSync , প্লেব্যাকের জন্য mediaPlayback , এবং লোকেশন ট্র্যাকিংয়ের জন্য location । এটিকে অবশ্যই সংশ্লিষ্ট FOREGROUND_SERVICE_<TYPE> পারমিশন এবং startForeground কলের সাথে সামঞ্জস্যপূর্ণ হতে হবে — এই তিনটিই অবশ্যই সঙ্গতিপূর্ণ হতে হবে, অন্যথায় সার্ভিসটি চালু হতে ব্যর্থ হবে।
১.২ ইউনিটি প্রজেক্টে সার্ভিস-প্রসেস জাভা যোগ করুন
FGS সার্ভিস প্রসেসটি জাভা চালায়। সার্ভিস ইমপ্লিমেন্টেশন জাভা কোডটিকে একটি jar ফাইলে প্যাকেজ করে Assets/Plugins/Android/ ফোল্ডারের অধীনে রাখুন। ইউনিটি স্বয়ংক্রিয়ভাবে ওই ডিরেক্টরির jar ফাইলগুলোকে Gradle-এর libs/ ইনপুটে অন্তর্ভুক্ত করে এবং সেগুলোকে APK-তে প্যাকেজ করে; সার্ভিস প্রসেসটি রানটাইমে এই ক্লাসগুলো লোড করে। জাভা বা কোটলিনে কীভাবে সার্ভিসটি ইমপ্লিমেন্ট করতে হয় সে সম্পর্কে আরও তথ্যের জন্য, Java implementation দেখুন।
২. একটি পৃথক প্রক্রিয়া কনফিগার করুন
প্রসেস বাউন্ডারিটি একটি ম্যানিফেস্ট অ্যাট্রিবিউটের মাধ্যমে সক্রিয় করা হয়:
android:process=":downloader"
শুরুতে কোলন দিলে your.package.name:downloader নামের একটি অ্যাপ্লিকেশন-প্রাইভেট প্রসেস তৈরি হয়। এটির পিআইডি (PID) মূল প্রসেস থেকে আলাদা এবং মূল প্রসেস বন্ধ হয়ে যাওয়ার পরেও এটি চালু থাকতে পারে।
২.১ পরিষেবা প্রক্রিয়ার কোনো ইউনিটি নেই
সার্ভিস প্রসেসে ইউনিটির libunity.so , IL2CPP রানটাইম এবং এই জাতীয় জিনিস আনা এড়ানোর জন্য, সার্ভিস-সাইড জাভার কোনো ইউনিটি ক্লাস ( UnityPlayer.currentActivity সহ) ব্যবহার করা উচিত নয় এবং শুধুমাত্র অ্যান্ড্রয়েড কনটেক্সট, ইনটেন্ট এক্সট্রা ও প্ল্যাটফর্ম এপিআই-এর উপর নির্ভর করা উচিত। ইউনিটি ইঞ্জিন রানটাইম শুধুমাত্র মেইন প্রসেসের UnityPlayerActivity এর মাধ্যমে লোড হয়; প্রাইভেট :downloader প্রসেস এই লাইব্রেরিগুলো লোড করে না, তাই সার্ভিস প্রসেসে ইউনিটি ক্লাস রেফারেন্স করা কাজ করে না।
২.২ প্রক্রিয়া শুরুর প্রবেশ বিন্দু
মূল প্রসেস startForegroundService কল করার পর, সিস্টেম :downloader সার্ভিস প্রসেসটি ফর্ক করে:
-
Applicationইনস্ট্যানশিয়েট করে এবংApplication.onCreateকল করে — এটি প্রতিটি প্রসেসে চলে, তাই সার্ভিস প্রসেসের জন্য প্রয়োজনীয় ইনিশিয়ালাইজেশন এখানে আবার করতে হবে। আরও তথ্যের জন্য, অনুচ্ছেদ ২.৩ দেখুন। -
DownloadServiceতৈরি করে এবংonCreateকল করে — এটি সার্ভিস-প্রসেসের এন্ট্রি পয়েন্ট। - স্টার্টআপের সময় তৈরি করা ইন্টেন্টটি সহ
onStartCommandকলব্যাক করে। এখানে সার্ভিসটি নিজেকে ফোরগ্রাউন্ডে নিয়ে আসে এবং ওয়ার্কারটি চালু করে। আরও তথ্যের জন্য, ৩.১ অনুচ্ছেদ দেখুন।
২.৩ প্রতিটি প্রসেসের জন্য স্টেট একবার বিদ্যমান থাকে।
ডেক্স কোড শুধুমাত্র পঠনযোগ্য হিসেবে শেয়ার করা হয়, কিন্তু রানটাইম স্টেট তা নয়:
-
Application.attachBaseContextএবংApplication.onCreateসেইসব প্রতিটি প্রসেসে রান করে যেগুলো অ্যাপ্লিকেশন কম্পোনেন্টগুলো হোস্ট করে। - স্ট্যাটিক ইনিশিয়ালাইজার এবং স্ট্যাটিক ফিল্ড প্রতিটি প্রসেসে স্বাধীনভাবে বিদ্যমান থাকে। মূল প্রসেসে একটি স্ট্যাটিক ফিল্ড অ্যাসাইন করলে তা সার্ভিসের সাথে যোগাযোগ করে না।
- ইউনিটি, সি#, এবং অ্যাক্টিভিটিজ মূল প্রক্রিয়ার মধ্যে রয়েছে।
৩. জাভা বাস্তবায়ন
এই অংশে FGS মডিউলের জাভা অংশটি আলোচনা করা হয়েছে: ৩.১ এবং ৩.২ হলো সার্ভিস প্রসেসের DownloadService ; ৩.৩ হলো মেইন প্রসেসের FgsBridge (এন্ট্রি পয়েন্ট, যা C# JNI ব্যবহার করে কল করে)।
৩.১ প্রথমে অগ্রভাগে নিয়ে আসুন
public class DownloadService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
try {
startForegroundCompat();
// Start the download thread; must come after promoting to foreground
// ...
} catch (Exception e) {
Log.e(TAG, "onStartCommand() failed [errorType="
+ e.getClass().getSimpleName() + "]: " + e.getMessage(), e);
stopSelf();
}
return START_NOT_STICKY;
}
private void startForegroundCompat() {
Notification notification = buildNotification(0L);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
} else {
startForeground(NOTIFICATION_ID, notification);
}
}
...
৩.২ বিজ্ঞপ্তিটি যোগ করুন
একটি বোধগম্য পরিষেবার অগ্রগতি জানানোর জন্য একটি নোটিফিকেশন চ্যানেলে চলমান নোটিফিকেশনের প্রয়োজন হয়। এই নোটিফিকেশনে একটি স্টপ অ্যাকশন থাকে, যা PendingIntent.getService ব্যবহার করে পরিষেবাটিকে বন্ধ করার জন্য সরাসরি ACTION_STOP পাঠায়।
private Notification buildNotification(long progressBytes) {
int progressMib = (int) (progressBytes / MIB);
Intent launchIntent = getPackageManager().getLaunchIntentForPackage(getPackageName());
PendingIntent contentIntent = launchIntent != null
? PendingIntent.getActivity(this, 0, launchIntent,
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT)
: null;
Intent stopIntent = new Intent(this, DownloadService.class)
.setAction(ACTION_STOP);
PendingIntent stopPendingIntent = PendingIntent.getService(this, 0,
stopIntent, PendingIntent.FLAG_IMMUTABLE
| PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Builder builder = new Notification.Builder(this,
CHANNEL_ID)
.setContentTitle("Download service")
.setContentText("Downloaded " + progressMib + " MB / " + TOTAL_MIB + " MB")
.setSmallIcon(android.R.drawable.stat_sys_download)
.setProgress(TOTAL_MIB, progressMib, false)
.setOngoing(true)
.setOnlyAlertOnce(true)
.addAction(new Notification.Action.Builder(null,
"Stop", stopPendingIntent).build());
if (contentIntent != null) {
builder.setContentIntent(contentIntent);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
builder.setForegroundServiceBehavior(
Notification.FOREGROUND_SERVICE_IMMEDIATE);
}
return builder.build();
}
৩.৩ জাভা হোস্ট এন্ট্রি পয়েন্ট
FgsBridge হলো জাভা এন্ট্রি পয়েন্ট যা মূল প্রসেসটি FGS নিয়ন্ত্রণ করতে ব্যবহার করে (এটি মূল প্রসেসে চলে, সার্ভিস প্রসেসে নয়)। C# সার্ভিসটি চালু ও বন্ধ করতে এবং নোটিফিকেশনের অনুমতি পরিচালনা করতে JNI ব্যবহার করে এই স্ট্যাটিক মেথডগুলোকে কল করে:
public class FgsBridge {
// Start the :downloader perceptible service and begin downloading
public static void startDownloadService(Context context) {
try {
context.startForegroundService(new Intent(context, DownloadService.class));
} catch (Exception e) {
Log.e(TAG, "startDownloadService() failed [errorType="
+ e.getClass().getSimpleName() + "]: " + e.getMessage(), e);
}
}
// Stop the service: sends a stop intent; the service removes its
// notification before exiting
public static void stopDownloadService(Context context) {
try {
context.startService(new Intent(context, DownloadService.class)
.setAction(ACTION_STOP));
} catch (Exception e) {
Log.e(TAG, "stopDownloadService() failed [errorType="
+ e.getClass().getSimpleName() + "]: " + e.getMessage(), e);
}
}
// Request notification permission (only needed on API 33+; if already
// granted, no dialog is shown and it returns immediately)
public static void requestNotificationPermission(Activity activity) {
if (Build.VERSION.SDK_INT < 33) {
return;
}
try {
activity.requestPermissions(
new String[] { POST_NOTIFICATIONS }, NOTIFICATION_PERMISSION_REQUEST);
} catch (Exception e) {
Log.e(TAG, "requestNotificationPermission() failed [errorType="
+ e.getClass().getSimpleName() + "]: " + e.getMessage(), e);
}
}
}
৪. ইউনিটি থেকে FGS চালু এবং নিয়ন্ত্রণ করুন (C#)
AndroidBridge হলো একটি C# র্যাপার যা JNI ব্যবহার করে FgsBridge কল করে। এর তিনটি মেথড ইমপ্লিমেন্টেশন হলো:
public static void StartDownloadService()
{
#if UNITY_ANDROID && !UNITY_EDITOR
CallStatic("startDownloadService");
#else
Debug.Log("StartDownloadService() no-op outside Android");
#endif
}
public static void StopDownloadService()
{
#if UNITY_ANDROID && !UNITY_EDITOR
CallStatic("stopDownloadService");
#else
Debug.Log("StopDownloadService() no-op outside Android");
#endif
}
public static void RequestNotificationPermission()
{
#if UNITY_ANDROID && !UNITY_EDITOR
CallStatic("requestNotificationPermission");
#else
Debug.Log("RequestNotificationPermission() no-op outside Android");
#endif
}
CallStatic হলো একটি অভ্যন্তরীণ JNI হেল্পার যা FgsBridge ক্লাসটি রিজলভ করে এবং সংশ্লিষ্ট জাভা স্ট্যাটিক মেথডটি কল করে। অ্যাপ্লিকেশন চালুর সময় RequestNotificationPermission কল করা উচিত, যাতে সার্ভিস প্রসেস শুরু হওয়ার সাথে সাথেই নোটিফিকেশনটি প্রদর্শিত হয়।