Unity के साथ, अलग प्रोसेस में ऐसी सर्विस चलाना जिसे उपयोगकर्ता देख सकें

इस गाइड में, Unity ऐप्लिकेशन से किसी निजी प्रोसेस में Android की परसीव की जा सकने वाली सेवा (फ़ोरग्राउंड सेवा या एफ़जीएस) को चलाने के लिए ज़रूरी कॉन्फ़िगरेशन और लागू करने के बारे में बताया गया है.

1. सेवा की जानकारी को समझने में मदद करने वाली सुविधाओं को कॉन्फ़िगर करना

इस सेक्शन में, ज़रूरी अनुमतियां सेट अप करने और अपने प्रोजेक्ट के मेनिफ़ेस्ट में सेवा के बारे में बताने का तरीका बताया गया है.

1.1 अनुमतियां और सेवा से जुड़ा एलान

कस्टम 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 कॉल से मेल खाना चाहिए. तीनों एक जैसे होने चाहिए. ऐसा न होने पर, सेवा शुरू नहीं हो पाएगी.

1.2 Unity प्रोजेक्ट में service-process Java जोड़ना

एफ़जीएस सेवा की प्रोसेस, Java पर चलती है. सेवा को लागू करने वाले Java कोड को जार में पैकेज करें और इसे Assets/Plugins/Android/ में रखें. Unity, उस डायरेक्ट्री में मौजूद जार को Gradle के libs/input में अपने-आप शामिल कर लेता है और उन्हें APK में पैकेज कर देता है. सेवा प्रोसेस, इन क्लास को रनटाइम में लोड करती है. Java या Kotlin में सेवा को लागू करने के बारे में ज़्यादा जानने के लिए, Java में लागू करना लेख पढ़ें.

2. कोई अलग प्रोसेस कॉन्फ़िगर करना

प्रोसेस बाउंड्री को एक मेनिफ़ेस्ट एट्रिब्यूट से चालू किया जाता है:

android:process=":downloader"

शुरुआत में मौजूद कोलन, ऐप्लिकेशन के लिए निजी प्रोसेस बनाता है. इसका नाम your.package.name:downloader होता है. इसका पीआईडी, मुख्य प्रोसेस से अलग होता है. साथ ही, मुख्य प्रोसेस के बंद होने के बाद भी यह चालू रह सकता है.

2.1 सेवा देने की प्रोसेस में एकरूपता नहीं है

Unity के libunity.so, IL2CPP रनटाइम, और इसी तरह की अन्य चीज़ों को सेवा प्रोसेस में शामिल करने से बचने के लिए, सेवा-साइड Java को किसी भी Unity क्लास (UnityPlayer.currentActivity भी शामिल है) का इस्तेमाल नहीं करना चाहिए. साथ ही, इसे सिर्फ़ Android Context, Intent एक्स्ट्रा, और प्लैटफ़ॉर्म एपीआई पर निर्भर रहना चाहिए. Unity इंजन रनटाइम को सिर्फ़ मुख्य प्रोसेस के UnityPlayerActivity के ज़रिए लोड किया जाता है. निजी :downloader प्रोसेस इन लाइब्रेरी को लोड नहीं करती है. इसलिए, सर्विस प्रोसेस में Unity क्लास को रेफ़रंस करने की सुविधा काम नहीं करती है.

2.2 प्रोसेस स्टार्टअप एंट्री पॉइंट

मुख्य प्रोसेस startForegroundService को कॉल करने के बाद, सिस्टम :downloader सेवा प्रोसेस को फ़ोर्क करता है:

  • Application को इंस्टैंशिएट करता है और Application.onCreate को कॉल करता है — यह हर प्रोसेस में चलता है. इसलिए, सेवा प्रोसेस के लिए ज़रूरी इनिशियलाइज़ेशन को यहां फिर से करना होगा. ज़्यादा जानकारी के लिए, सेक्शन 2.3 देखें.
  • यह DownloadService बनाता है और onCreate को कॉल करता है. यह सेवा-प्रोसेस का एंट्री पॉइंट है.
  • यह कुकी, स्टार्टअप के समय बनाए गए इंटेंट के साथ onStartCommand को वापस कॉल करती है. यहां सेवा, खुद को फ़ोरग्राउंड में प्रमोट करती है और वर्कर को शुरू करती है. ज़्यादा जानकारी के लिए, सेक्शन 3.1 देखें.

2.3 हर प्रोसेस के लिए, राज्य की जानकारी सिर्फ़ एक बार मौजूद होती है

Dex कोड को सिर्फ़ पढ़ने के लिए शेयर किया जाता है, लेकिन रनटाइम की स्थिति को शेयर नहीं किया जाता:

  • Application.attachBaseContext और Application.onCreate, ऐप्लिकेशन के कॉम्पोनेंट को होस्ट करने वाली हर प्रोसेस में चलते हैं.
  • स्टैटिक इनिशियलाइज़र और स्टैटिक फ़ील्ड, हर प्रोसेस में अलग-अलग मौजूद होते हैं. मुख्य प्रोसेस में स्टैटिक फ़ील्ड असाइन करने से, सेवा से कम्यूनिकेट नहीं किया जाता.
  • Unity, C#, और Activities, मुख्य प्रोसेस में बने रहते हैं.

3. Java लागू करना

इस सेक्शन में, FGS मॉड्यूल के Java साइड के बारे में बताया गया है: 3.1 और 3.2, सर्विस प्रोसेस में DownloadService हैं; 3.3, मुख्य प्रोसेस में FgsBridge है (JNI का इस्तेमाल करके C# कॉल करने का एंट्री पॉइंट).

3.1 सबसे पहले फ़ोरग्राउंड में प्रमोट करें

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

    ...

3.2 सूचना जोड़ना

उपयोगकर्ता को दिखने वाली सेवा को, सूचना चैनल पर लगातार सूचना भेजनी होती है, ताकि वह प्रोग्रेस की जानकारी दे सके. सूचना में, 'बंद करें' कार्रवाई होती है. इससे सेवा को ACTION_STOP भेजा जाता है. ऐसा PendingIntent.getService का इस्तेमाल करके किया जाता है, ताकि सेवा को बंद किया जा सके.

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

3.3 Java होस्ट एंट्री पॉइंट

FgsBridge, Java का एंट्री पॉइंट है. इसका इस्तेमाल मुख्य प्रोसेस, 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);
        }
    }
}

4. Unity (C#) से FGS को शुरू और कंट्रोल करना

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 क्लास को हल करता है और उससे जुड़े Java स्टैटिक मेथड को कॉल करता है. RequestNotificationPermission को ऐप्लिकेशन के स्टार्टअप पर कॉल किया जाना चाहिए, ताकि सेवा की प्रोसेस शुरू होने पर सूचना तुरंत दिखे.