إتاحة استخدام وحدات التحكُّم في جميع إصدارات Android

إذا كنت تدعم وحدات التحكم في الألعاب في لعبتك، تقع على عاتقك مسؤولية التأكد من أن لعبتك تستجيب لوحدات التحكم بشكل مستمر في جميع الأجهزة التي تعمل بإصدارات مختلفة من Android. يتيح ذلك إمكانية وصول لعبتك إلى شريحة أكبر من الجمهور، ويمكن للّاعبين الاستمتاع بتجربة لعب سلسة باستخدام وحدات التحكّم حتى عند تبديل أجهزة Android أو ترقيتها.

يشرح هذا الدرس كيفية استخدام واجهات برمجة التطبيقات المتوفّرة في الإصدار Android 4.1 والإصدارات الأحدث بطريقة متوافقة مع الأنظمة القديمة، ما يتيح لعبتك إمكانية إتاحة الميزات التالية على الأجهزة التي تعمل بالإصدار 3.1 من نظام التشغيل Android والإصدارات الأحدث:

  • يمكن أن ترصد اللعبة ما إذا تمت إضافة وحدة تحكّم جديدة في الألعاب أو تغييرها أو إزالتها.
  • يمكن أن تستعلم اللعبة عن إمكانات وحدة التحكم في الألعاب.
  • يمكن للّعبة التعرّف على أحداث الحركة الواردة من وحدة تحكُّم الألعاب.

تستند الأمثلة في هذا الدرس إلى طريقة تنفيذ المرجع التي يوفّرها نموذج ControllerSample.zip المتاح للتنزيل أعلاه. يعرض هذا النموذج طريقة تنفيذ واجهة InputManagerCompat لتوافق مع إصدارات مختلفة من Android. لتجميع النموذج، عليك استخدام Android 4.1 (المستوى 16 لواجهة برمجة التطبيقات) أو إصدار أحدث. بعد تجميع نموذج التطبيق، يتم تشغيله على أي جهاز يعمل بالإصدار Android 3.1 (المستوى 12 لواجهة برمجة التطبيقات) أو إصدار أحدث كهدف الإصدار.

الاستعداد لتلخيص واجهات برمجة التطبيقات للحصول على دعم وحدات تحكّم الألعاب

لنفترض أنّك تريد التمكّن من تحديد ما إذا كانت حالة اتصال وحدة التحكّم في الألعاب قد تغيّرت على الأجهزة التي تعمل بالإصدار 3.1 من Android (المستوى 12 من واجهة برمجة التطبيقات). ومع ذلك، لا تتوفر واجهات برمجة التطبيقات إلا في الإصدار Android 4.1 (المستوى 16 لواجهة برمجة التطبيقات) والإصدارات الأحدث، لذا عليك توفير عملية تنفيذ تتوافق مع الإصدار 4.1 من نظام التشغيل Android والإصدارات الأحدث مع توفير آلية احتياطية تتوافق مع الإصدار 3.1 من نظام التشغيل Android وصولاً إلى الإصدار 4.0 من نظام التشغيل Android.

لمساعدتك في تحديد الميزات التي تتطلّب استخدام آلية احتياطية في الإصدارات الأقدم، يسرد الجدول 1 الاختلافات في إمكانية استخدام وحدات تحكّم الألعاب بين الإصدار 3.1 من نظام التشغيل Android (المستوى 12 من واجهة برمجة التطبيقات) والإصدار 4.1 (المستوى 16 من واجهة برمجة التطبيقات).

الجدول 1. وواجهات برمجة التطبيقات لدعم وحدات تحكم الألعاب عبر إصدارات Android المختلفة.

معلومات مسؤول التحكّم بالبيانات واجهة برمجة تطبيقات وحدة التحكم المستوى 12 من واجهة برمجة التطبيقات المستوى 16 من واجهة برمجة التطبيقات
تحديد الجهاز getInputDeviceIds()  
getInputDevice()  
getVibrator()  
SOURCE_JOYSTICK
SOURCE_GAMEPAD
حالة الاتصال onInputDeviceAdded()  
onInputDeviceChanged()  
onInputDeviceRemoved()  
تحديد حدث الإدخال الضغط على لوحة التحكّم ( KEYCODE_DPAD_UP، KEYCODE_DPAD_DOWN، KEYCODE_DPAD_LEFT، KEYCODE_DPAD_RIGHT، KEYCODE_DPAD_CENTER)
الضغط على زر لوحة الألعاب ( BUTTON_A، BUTTON_B، BUTTON_THUMBL، BUTTON_THUMBR، BUTTON_SELECT، BUTTON_START، BUTTON_R1، BUTTON_L1، BUTTON_R2، BUTTON_L2)
حركة التبديل بين ذراع التحكّم والقبعة ( AXIS_X، AXIS_Y، AXIS_Z، AXIS_RZ، AXIS_HAT_X، AXIS_HAT_Y)
الضغط على المشغِّل التناظري ( AXIS_LTRIGGER، AXIS_RTRIGGER)

يمكنك استخدام التجريد في الحصول على دعم لوحدات تحكم الألعاب على مختلف الأنظمة الأساسية. يتضمن هذا النهج الخطوات التالية:

  1. حدد واجهة وسيطة Java تفسر تنفيذ ميزات وحدة التحكم في الألعاب التي تتطلبها اللعبة.
  2. يمكنك إنشاء تنفيذ خادم وكيل للواجهة التي تستخدم واجهات برمجة التطبيقات في الإصدار Android 4.1 والإصدارات الأحدث.
  3. يمكنك إنشاء تنفيذ مخصّص للواجهة يستخدم واجهات برمجة التطبيقات المتاحة بين الإصدار Android 3.1 وAndroid 4.0.
  4. أنشئ المنطق للتبديل بين عمليات التنفيذ هذه في وقت التشغيل، وابدأ باستخدام الواجهة في لعبتك.

للحصول على نظرة عامة حول طريقة استخدام التجريد لضمان عمل التطبيقات بطريقة متوافقة مع الأنظمة القديمة على مختلف إصدارات Android، يمكنك الاطّلاع على إنشاء واجهات مستخدم متوافقة مع الأنظمة القديمة.

إضافة واجهة للتوافق مع الأنظمة القديمة

لتوفير توافق مع الأنظمة القديمة، يمكنك إنشاء واجهة مخصّصة ثمّ إضافة عمليات تنفيذ خاصة بإصدار معيّن. تتمثل إحدى ميزات هذا الأسلوب في أنّه يتيح لك عكس الواجهات العامة على نظام التشغيل Android 4.1 (المستوى 16 من واجهة برمجة التطبيقات) التي تتوافق مع وحدات التحكّم في الألعاب.

Kotlin

// The InputManagerCompat interface is a reference example.
// The full code is provided in the ControllerSample.zip sample.
interface InputManagerCompat {
    val inputDeviceIds: IntArray
    fun getInputDevice(id: Int): InputDevice

    fun registerInputDeviceListener(
            listener: InputManager.InputDeviceListener,
            handler: Handler?
    )

    fun unregisterInputDeviceListener(listener:InputManager.InputDeviceListener)

    fun onGenericMotionEvent(event: MotionEvent)

    fun onPause()
    fun onResume()

    interface InputDeviceListener {
        fun onInputDeviceAdded(deviceId: Int)
        fun onInputDeviceChanged(deviceId: Int)
        fun onInputDeviceRemoved(deviceId: Int)
    }
}

Java

// The InputManagerCompat interface is a reference example.
// The full code is provided in the ControllerSample.zip sample.
public interface InputManagerCompat {
    ...
    public InputDevice getInputDevice(int id);
    public int[] getInputDeviceIds();

    public void registerInputDeviceListener(
            InputManagerCompat.InputDeviceListener listener,
            Handler handler);
    public void unregisterInputDeviceListener(
            InputManagerCompat.InputDeviceListener listener);

    public void onGenericMotionEvent(MotionEvent event);

    public void onPause();
    public void onResume();

    public interface InputDeviceListener {
        void onInputDeviceAdded(int deviceId);
        void onInputDeviceChanged(int deviceId);
        void onInputDeviceRemoved(int deviceId);
    }
    ...
}

توفّر واجهة InputManagerCompat الطرق التالية:

getInputDevice()
مرايا getInputDevice() الحصول على الكائن InputDevice الذي يمثّل إمكانات وحدة التحكّم في الألعاب
getInputDeviceIds()
مرايا getInputDeviceIds() تعرض صفيفًا من الأعداد الصحيحة، ويمثل كل منها معرّفًا لجهاز إدخال مختلف. يكون هذا الإجراء مفيدًا إذا كنت تصمّم لعبة تتيح استخدام عدة لاعبين وتريد اكتشاف عدد وحدات التحكّم المتصلة معًا.
registerInputDeviceListener()
مرايا registerInputDeviceListener() تتيح لك التسجيل ليتم إبلاغك عند إضافة جهاز جديد أو تغييره أو إزالته.
unregisterInputDeviceListener()
مرايا unregisterInputDeviceListener() إلغاء تسجيل مستمِع جهاز الإدخال
onGenericMotionEvent()
مرايا onGenericMotionEvent() للسماح للّعبة باعتراض ومعالجة كائنات MotionEvent وقيم المحاور التي تمثّل أحداثًا مثل حركات ذراع التحكّم والضغط على زر التشغيل التناظري.
onPause()
يوقف استطلاع الرأي حول أحداث وحدات التحكم في الألعاب عندما يكون النشاط الرئيسي متوقفًا مؤقتًا، أو عندما لا تكون اللعبة بؤرة التركيز.
onResume()
تبدأ هذه الميزة في إجراء الاستطلاعات حول أحداث وحدات التحكّم في الألعاب عند استئناف النشاط الرئيسي أو عند بدء اللعبة وتشغيلها في المقدّمة.
InputDeviceListener
يعكس واجهة InputManager.InputDeviceListener. يتيح للّعبة معرفة ما إذا تمت إضافة ذراع تحكّم في الألعاب أو تغييرها أو إزالتها.

الخطوة التالية هي إنشاء عمليات تنفيذ لـ InputManagerCompat تعمل على مختلف الإصدارات من النظام الأساسي إذا كانت لعبتك تعمل على نظام التشغيل Android 4.1 أو إصدار أحدث واستدعت طريقة InputManagerCompat، يستدعي تنفيذ الخادم الوكيل الطريقة المكافئة في InputManager. ومع ذلك، إذا كانت لعبتك تعمل على نظام التشغيل Android 3.1 أو Android 4.0، يعالج التنفيذ المخصّص عمليات الاستدعاء لطرق InputManagerCompat باستخدام واجهات برمجة التطبيقات التي تم تقديمها في إصدار أحدث من Android 3.1 فقط. وبغض النظر عن طريقة التنفيذ الخاصة بالإصدار المستخدم في وقت التشغيل، تعمل عملية التنفيذ على تمرير نتائج الطلبات بشفافية إلى اللعبة.

الشكل 1. رسم تخطيطي لفئة التطبيقات للواجهة والإصدارات الخاصة بكل إصدار

تنفيذ الواجهة على نظام التشغيل Android 4.1 والإصدارات الأحدث

InputManagerCompatV16 هو تنفيذ لواجهة InputManagerCompat تعمل على استدعاء طريقة الخوادم الوكيلة لـ InputManager وInputManager.InputDeviceListener فعليين. تم الحصول على InputManager من النظام Context.

Kotlin

// The InputManagerCompatV16 class is a reference implementation.
// The full code is provided in the ControllerSample.zip sample.
public class InputManagerV16(
        context: Context,
        private val inputManager: InputManager =
            context.getSystemService(Context.INPUT_SERVICE) as InputManager,
        private val listeners:
            MutableMap<InputManager.InputDeviceListener, V16InputDeviceListener> = mutableMapOf()
) : InputManagerCompat {
    override val inputDeviceIds: IntArray = inputManager.inputDeviceIds

    override fun getInputDevice(id: Int): InputDevice = inputManager.getInputDevice(id)

    override fun registerInputDeviceListener(
            listener: InputManager.InputDeviceListener,
            handler: Handler?
    ) {
        V16InputDeviceListener(listener).also { v16listener ->
            inputManager.registerInputDeviceListener(v16listener, handler)
            listeners += listener to v16listener
        }
    }

    // Do the same for unregistering an input device listener
    ...

    override fun onGenericMotionEvent(event: MotionEvent) {
        // unused in V16
    }

    override fun onPause() {
        // unused in V16
    }

    override fun onResume() {
        // unused in V16
    }

}

class V16InputDeviceListener(
        private val idl: InputManager.InputDeviceListener
) : InputManager.InputDeviceListener {

    override fun onInputDeviceAdded(deviceId: Int) {
        idl.onInputDeviceAdded(deviceId)
    }
    // Do the same for device change and removal
    ...
}

Java

// The InputManagerCompatV16 class is a reference implementation.
// The full code is provided in the ControllerSample.zip sample.
public class InputManagerV16 implements InputManagerCompat {

    private final InputManager inputManager;
    private final Map<InputManagerCompat.InputDeviceListener,
            V16InputDeviceListener> listeners;

    public InputManagerV16(Context context) {
        inputManager = (InputManager)
                context.getSystemService(Context.INPUT_SERVICE);
        listeners = new HashMap<InputManagerCompat.InputDeviceListener,
                V16InputDeviceListener>();
    }

    @Override
    public InputDevice getInputDevice(int id) {
        return inputManager.getInputDevice(id);
    }

    @Override
    public int[] getInputDeviceIds() {
        return inputManager.getInputDeviceIds();
    }

    static class V16InputDeviceListener implements
            InputManager.InputDeviceListener {
        final InputManagerCompat.InputDeviceListener mIDL;

        public V16InputDeviceListener(InputDeviceListener idl) {
            mIDL = idl;
        }

        @Override
        public void onInputDeviceAdded(int deviceId) {
            mIDL.onInputDeviceAdded(deviceId);
        }

        // Do the same for device change and removal
        ...
    }

    @Override
    public void registerInputDeviceListener(InputDeviceListener listener,
            Handler handler) {
        V16InputDeviceListener v16Listener = new
                V16InputDeviceListener(listener);
        inputManager.registerInputDeviceListener(v16Listener, handler);
        listeners.put(listener, v16Listener);
    }

    // Do the same for unregistering an input device listener
    ...

    @Override
    public void onGenericMotionEvent(MotionEvent event) {
        // unused in V16
    }

    @Override
    public void onPause() {
        // unused in V16
    }

    @Override
    public void onResume() {
        // unused in V16
    }

}

تنفيذ الواجهة على نظام التشغيل Android 3.1 وصولاً إلى Android 4.0

لإنشاء تنفيذ InputManagerCompat يتوافق مع Android 3.1 حتى Android 4.0، يمكنك استخدام العناصر التالية:

  • SparseArray من معرّفات الأجهزة لتتبع وحدات تحكم الألعاب المتصلة بالجهاز.
  • Handler لمعالجة أحداث الجهاز. عند بدء تشغيل تطبيق أو استئنافه، يتلقّى Handler رسالة لبدء الاستطلاع لإلغاء ربط وحدة التحكّم في الألعاب. سيبدأ Handler حلقة للتحقق من كل وحدة تحكم معروفة في الألعاب متصلة ومعرفة ما إذا تم إرجاع رقم تعريف الجهاز. تشير القيمة المعروضة null إلى أنّه تم فصل وحدة التحكّم في الألعاب. يتوقف عنصر Handler عن إجراء الاستطلاعات عند إيقاف التطبيق مؤقتًا.
  • تمثّل هذه السمة Map من InputManagerCompat.InputDeviceListener كائنات. ستستخدم أدوات التحكم لتحديث حالة اتصال وحدات التحكم في الألعاب التي يتم تتبعها.

Kotlin

// The InputManagerCompatV9 class is a reference implementation.
// The full code is provided in the ControllerSample.zip sample.
class InputManagerV9(
        val devices: SparseArray<Array<Long>> = SparseArray(),
        private val listeners:
        MutableMap<InputManager.InputDeviceListener, Handler> = mutableMapOf()
) : InputManagerCompat {
    private val defaultHandler: Handler = PollingMessageHandler(this)
    …
}

Java

// The InputManagerCompatV9 class is a reference implementation.
// The full code is provided in the ControllerSample.zip sample.
public class InputManagerV9 implements InputManagerCompat {
    private final SparseArray<long[]> devices;
    private final Map<InputDeviceListener, Handler> listeners;
    private final Handler defaultHandler;
    …

    public InputManagerV9() {
        devices = new SparseArray<long[]>();
        listeners = new HashMap<InputDeviceListener, Handler>();
        defaultHandler = new PollingMessageHandler(this);
    }
}

نفِّذ كائن PollingMessageHandler يزيد من Handler، وتجاوز طريقة handleMessage(). تتحقّق هذه الطريقة مما إذا كان قد تم فصل وحدة تحكّم الألعاب المرفقة وترسل إشعارًا إلى المستمعين المسجَّلين.

Kotlin

private class PollingMessageHandler(
        inputManager: InputManagerV9,
        private val mInputManager: WeakReference<InputManagerV9> = WeakReference(inputManager)
) : Handler() {

    override fun handleMessage(msg: Message) {
        super.handleMessage(msg)
        when (msg.what) {
            MESSAGE_TEST_FOR_DISCONNECT -> {
                mInputManager.get()?.also { imv ->
                    val time = SystemClock.elapsedRealtime()
                    val size = imv.devices.size()
                    for (i in 0 until size) {
                        imv.devices.valueAt(i)?.also { lastContact ->
                            if (time - lastContact[0] > CHECK_ELAPSED_TIME) {
                                // check to see if the device has been
                                // disconnected
                                val id = imv.devices.keyAt(i)
                                if (null == InputDevice.getDevice(id)) {
                                    // Notify the registered listeners
                                    // that the game controller is disconnected
                                    imv.devices.remove(id)
                                } else {
                                    lastContact[0] = time
                                }
                            }
                        }
                    }
                    sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT, CHECK_ELAPSED_TIME)
                }
            }
        }
    }
}

Java

private static class PollingMessageHandler extends Handler {
    private final WeakReference<InputManagerV9> inputManager;

    PollingMessageHandler(InputManagerV9 im) {
        inputManager = new WeakReference<InputManagerV9>(im);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case MESSAGE_TEST_FOR_DISCONNECT:
                InputManagerV9 imv = inputManager.get();
                if (null != imv) {
                    long time = SystemClock.elapsedRealtime();
                    int size = imv.devices.size();
                    for (int i = 0; i < size; i++) {
                        long[] lastContact = imv.devices.valueAt(i);
                        if (null != lastContact) {
                            if (time - lastContact[0] > CHECK_ELAPSED_TIME) {
                                // check to see if the device has been
                                // disconnected
                                int id = imv.devices.keyAt(i);
                                if (null == InputDevice.getDevice(id)) {
                                    // Notify the registered listeners
                                    // that the game controller is disconnected
                                    imv.devices.remove(id);
                                } else {
                                    lastContact[0] = time;
                                }
                            }
                        }
                    }
                    sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
                            CHECK_ELAPSED_TIME);
                }
                break;
        }
    }
}

لبدء استطلاع بشأن إلغاء ربط وحدة التحكّم في الألعاب وإيقافها، يمكنك تجاهل الطرق التالية:

Kotlin

private const val MESSAGE_TEST_FOR_DISCONNECT = 101
private const val CHECK_ELAPSED_TIME = 3000L

class InputManagerV9(
        val devices: SparseArray<Array<Long>> = SparseArray(),
        private val listeners:
        MutableMap<InputManager.InputDeviceListener, Handler> = mutableMapOf()
) : InputManagerCompat {
    ...
    override fun onPause() {
        defaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT)
    }

    override fun onResume() {
        defaultHandler.sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT, CHECK_ELAPSED_TIME)
    }
    ...
}

Java

private static final int MESSAGE_TEST_FOR_DISCONNECT = 101;
private static final long CHECK_ELAPSED_TIME = 3000L;

@Override
public void onPause() {
    defaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT);
}

@Override
public void onResume() {
    defaultHandler.sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
            CHECK_ELAPSED_TIME);
}

لاكتشاف أنه تمت إضافة جهاز إدخال، يمكنك إلغاء طريقة onGenericMotionEvent(). عندما يبلغ النظام عن حدث حركة، تحقّق مما إذا كان مصدر هذا الحدث هو رقم تعريف جهاز تم تتبُّعه مسبقًا أو من رقم تعريف جهاز جديد. إذا كان رقم تعريف الجهاز جديدًا، يُرجى إرسال إشعارات إلى المستمعين المسجّلين.

Kotlin

override fun onGenericMotionEvent(event: MotionEvent) {
    // detect new devices
    val id = event.deviceId
    val timeArray: Array<Long> = mDevices.get(id) ?: run {
        // Notify the registered listeners that a game controller is added
        ...
        arrayOf<Long>().also {
            mDevices.put(id, it)
        }
    }
    timeArray[0] = SystemClock.elapsedRealtime()
}

Java

@Override
public void onGenericMotionEvent(MotionEvent event) {
    // detect new devices
    int id = event.getDeviceId();
    long[] timeArray = mDevices.get(id);
    if (null == timeArray) {
        // Notify the registered listeners that a game controller is added
        ...
        timeArray = new long[1];
        mDevices.put(id, timeArray);
    }
    long time = SystemClock.elapsedRealtime();
    timeArray[0] = time;
}

يتم تنفيذ إشعارات المستمعين باستخدام الكائن Handler لإرسال عنصر DeviceEvent Runnable إلى قائمة انتظار الرسائل. تحتوي السمة DeviceEvent على مرجع إلى InputManagerCompat.InputDeviceListener. عند تشغيل DeviceEvent، يتم استدعاء طريقة معاودة الاتصال المناسبة للمستمع للإشارة إلى ما إذا تمت إضافة وحدة تحكّم في الألعاب أو تغييرها أو إزالتها.

Kotlin

class InputManagerV9(
        val devices: SparseArray<Array<Long>> = SparseArray(),
        private val listeners:
        MutableMap<InputManager.InputDeviceListener, Handler> = mutableMapOf()
) : InputManagerCompat {
    ...
    override fun registerInputDeviceListener(
            listener: InputManager.InputDeviceListener,
            handler: Handler?
    ) {
        listeners[listener] = handler ?: defaultHandler
    }

    override fun unregisterInputDeviceListener(listener: InputManager.InputDeviceListener) {
        listeners.remove(listener)
    }

    private fun notifyListeners(why: Int, deviceId: Int) {
        // the state of some device has changed
        listeners.forEach { listener, handler ->
            DeviceEvent.getDeviceEvent(why, deviceId, listener).also {
                handler?.post(it)
            }
        }
    }
    ...
}

private val sObjectQueue: Queue<DeviceEvent> = ArrayDeque<DeviceEvent>()

private class DeviceEvent(
        private var mMessageType: Int,
        private var mId: Int,
        private var mListener: InputManager.InputDeviceListener
) : Runnable {

    companion object {
        fun getDeviceEvent(messageType: Int, id: Int, listener: InputManager.InputDeviceListener) =
                sObjectQueue.poll()?.apply {
                    mMessageType = messageType
                    mId = id
                    mListener = listener
                } ?: DeviceEvent(messageType, id, listener)

    }

    override fun run() {
        when(mMessageType) {
            ON_DEVICE_ADDED -> mListener.onInputDeviceAdded(mId)
            ON_DEVICE_CHANGED -> mListener.onInputDeviceChanged(mId)
            ON_DEVICE_REMOVED -> mListener.onInputDeviceChanged(mId)
            else -> {
                // Handle unknown message type
            }
        }
    }

}

Java

@Override
public void registerInputDeviceListener(InputDeviceListener listener,
        Handler handler) {
    listeners.remove(listener);
    if (handler == null) {
        handler = defaultHandler;
    }
    listeners.put(listener, handler);
}

@Override
public void unregisterInputDeviceListener(InputDeviceListener listener) {
    listeners.remove(listener);
}

private void notifyListeners(int why, int deviceId) {
    // the state of some device has changed
    if (!listeners.isEmpty()) {
        for (InputDeviceListener listener : listeners.keySet()) {
            Handler handler = listeners.get(listener);
            DeviceEvent odc = DeviceEvent.getDeviceEvent(why, deviceId,
                    listener);
            handler.post(odc);
        }
    }
}

private static class DeviceEvent implements Runnable {
    private int mMessageType;
    private int mId;
    private InputDeviceListener mListener;
    private static Queue<DeviceEvent> sObjectQueue =
            new ArrayDeque<DeviceEvent>();
    ...

    static DeviceEvent getDeviceEvent(int messageType, int id,
            InputDeviceListener listener) {
        DeviceEvent curChanged = sObjectQueue.poll();
        if (null == curChanged) {
            curChanged = new DeviceEvent();
        }
        curChanged.mMessageType = messageType;
        curChanged.mId = id;
        curChanged.mListener = listener;
        return curChanged;
    }

    @Override
    public void run() {
        switch (mMessageType) {
            case ON_DEVICE_ADDED:
                mListener.onInputDeviceAdded(mId);
                break;
            case ON_DEVICE_CHANGED:
                mListener.onInputDeviceChanged(mId);
                break;
            case ON_DEVICE_REMOVED:
                mListener.onInputDeviceRemoved(mId);
                break;
            default:
                // Handle unknown message type
                ...
                break;
        }
        // Put this runnable back in the queue
        sObjectQueue.offer(this);
    }
}

لديك الآن تطبيقان لتطبيق InputManagerCompat: تطبيق متوافق مع الأجهزة التي تعمل بالإصدار 4.1 من نظام التشغيل Android والإصدارات الأحدث، والأخرى تعمل على الأجهزة التي تعمل بالإصدارات 3.1 من نظام التشغيل Android وصولاً إلى الإصدار 4.0 من نظام التشغيل Android.

استخدام التنفيذ الخاص بالإصدار

يتم تنفيذ منطق التبديل الخاص بالإصدار في فئة تعمل كمصنع.

Kotlin

object Factory {
    fun getInputManager(context: Context): InputManagerCompat =
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                InputManagerV16(context)
            } else {
                InputManagerV9()
            }
}

Java

public static class Factory {
    public static InputManagerCompat getInputManager(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            return new InputManagerV16(context);
        } else {
            return new InputManagerV9();
        }
    }
}

يمكنك الآن إنشاء مثيل لكائن InputManagerCompat وتسجيل InputManagerCompat.InputDeviceListener في View الرئيسي. نظرًا لمنطق تبديل الإصدار الذي أعددته، تستخدم لعبتك تلقائيًا طريقة التنفيذ المناسبة لإصدار Android الذي يعمل به الجهاز.

Kotlin

class GameView(context: Context) : View(context), InputManager.InputDeviceListener {
    private val inputManager: InputManagerCompat = Factory.getInputManager(context).apply {
        registerInputDeviceListener(this@GameView, null)
        ...
    }
    ...
}

Java

public class GameView extends View implements InputDeviceListener {
    private InputManagerCompat inputManager;
    ...

    public GameView(Context context, AttributeSet attrs) {
        inputManager =
                InputManagerCompat.Factory.getInputManager(this.getContext());
        inputManager.registerInputDeviceListener(this, null);
        ...
    }
}

بعد ذلك، ألغِ طريقة onGenericMotionEvent() في طريقة العرض الرئيسية، كما هو موضّح في التعامل مع حدث MotionEvent من وحدة تحكّم الألعاب. من المفترض أن تتمكن لعبتك الآن من معالجة أحداث وحدات التحكم في الألعاب بانتظام على الأجهزة التي تعمل بنظام التشغيل Android 3.1 (مستوى واجهة برمجة التطبيقات 12) والإصدارات الأحدث.

Kotlin

override fun onGenericMotionEvent(event: MotionEvent): Boolean {
    inputManager.onGenericMotionEvent(event)

    // Handle analog input from the controller as normal
    ...
    return super.onGenericMotionEvent(event)
}

Java

@Override
public boolean onGenericMotionEvent(MotionEvent event) {
    inputManager.onGenericMotionEvent(event);

    // Handle analog input from the controller as normal
    ...
    return super.onGenericMotionEvent(event);
}

يمكنك العثور على التنفيذ الكامل لرمز التوافق هذا في الفئة GameView المقدَّمة في النموذج ControllerSample.zip المتاح للتنزيل أعلاه.