پشتیبانی از کنترلرها در نسخه‌های مختلف اندروید

اگر در بازی خود از دسته‌های بازی پشتیبانی می‌کنید، مسئولیت شماست که مطمئن شوید بازی شما به طور مداوم در دستگاه‌هایی که روی نسخه‌های مختلف اندروید اجرا می‌شوند، به دسته‌ها پاسخ می‌دهد. این به بازی شما اجازه می‌دهد تا به مخاطبان گسترده‌تری برسد و بازیکنان شما می‌توانند حتی هنگام تعویض یا ارتقاء دستگاه‌های اندروید خود، از یک تجربه گیم‌پلی روان با دسته‌های خود لذت ببرند.

این درس نحوه استفاده از API های موجود در اندروید ۴.۱ و بالاتر را به صورت سازگار با نسخه‌های قبلی نشان می‌دهد و به بازی شما این امکان را می‌دهد که از ویژگی‌های زیر در دستگاه‌های دارای اندروید ۳.۱ و بالاتر پشتیبانی کند:

  • بازی می‌تواند تشخیص دهد که آیا یک دسته بازی جدید اضافه، تغییر یا حذف شده است یا خیر.
  • این بازی می‌تواند قابلیت‌های یک کنترلر بازی را بررسی کند.
  • این بازی می‌تواند رویدادهای حرکتی ورودی از یک کنترلر بازی را تشخیص دهد.

آماده‌سازی برای انتزاع APIها برای پشتیبانی از کنترلر بازی

فرض کنید می‌خواهید بتوانید تشخیص دهید که آیا وضعیت اتصال یک دسته بازی در دستگاه‌هایی که اندروید ۳.۱ (سطح API ۱۲) دارند تغییر کرده است یا خیر. با این حال، APIها فقط در اندروید ۴.۱ (سطح API ۱۶) و بالاتر در دسترس هستند، بنابراین باید پیاده‌سازی‌ای ارائه دهید که از اندروید ۴.۱ و بالاتر پشتیبانی کند و در عین حال یک مکانیزم پشتیبان ارائه دهد که از اندروید ۳.۱ تا اندروید ۴.۰ پشتیبانی کند.

برای کمک به شما در تعیین اینکه کدام ویژگی‌ها برای نسخه‌های پایین‌تر به مکانیزم پشتیبان نیاز دارند، جدول 1 تفاوت‌های پشتیبانی از دسته‌های بازی بین اندروید 3.1 (سطح API 12) و 4.1 (سطح API 16) را فهرست می‌کند.

جدول 1. رابط‌های برنامه‌نویسی کاربردی (API) برای پشتیبانی از دسته بازی در نسخه‌های مختلف اندروید.

اطلاعات کنترل کننده رابط برنامه‌نویسی کنترل‌کننده سطح API 12 سطح API 16
شناسایی دستگاه getInputDeviceIds()  
getInputDevice()  
getVibrator()  
SOURCE_JOYSTICK
SOURCE_GAMEPAD
وضعیت اتصال onInputDeviceAdded()
onInputDeviceChanged()
onInputDeviceRemoved()
شناسایی رویداد ورودی فشار دادن D-pad ( 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. یک رابط واسط جاوا تعریف کنید که پیاده‌سازی ویژگی‌های کنترل‌کننده بازی مورد نیاز بازی شما را خلاصه کند.
  2. یک پیاده‌سازی پروکسی از رابط کاربری خود ایجاد کنید که از APIهای اندروید ۴.۱ و بالاتر استفاده کند.
  3. یک پیاده‌سازی سفارشی از رابط کاربری خود ایجاد کنید که از APIهای موجود بین اندروید ۳.۱ تا اندروید ۴.۰ استفاده کند.
  4. منطق لازم برای جابجایی بین این پیاده‌سازی‌ها را در زمان اجرا ایجاد کنید و استفاده از رابط کاربری را در بازی خود شروع کنید.

برای مرور کلی در مورد چگونگی استفاده از انتزاع برای تأیید اینکه برنامه‌ها می‌توانند به روشی سازگار با نسخه‌های مختلف اندروید کار کنند، به ایجاد رابط‌های کاربری سازگار با نسخه‌های قبلی مراجعه کنید.

یک رابط برای سازگاری با نسخه‌های قبلی اضافه کنید

برای فراهم کردن سازگاری با نسخه‌های قبلی، می‌توانید یک رابط کاربری سفارشی ایجاد کنید و سپس پیاده‌سازی‌های مخصوص هر نسخه را به آن اضافه کنید. یکی از مزایای این رویکرد این است که به شما امکان می‌دهد رابط‌های کاربری عمومی در اندروید ۴.۱ (سطح API ۱۶) که از کنترل‌کننده‌های بازی پشتیبانی می‌کنند را کپی کنید.

کاتلین

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

جاوا

// 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() به عنوان Mirrors تعریف می‌کند. شیء InputDevice که نشان‌دهنده‌ی قابلیت‌های یک کنترلر بازی است را دریافت می‌کند.
getInputDeviceIds()
تابع getInputDeviceIds() را به عنوان Mirrors در نظر می‌گیرد. این تابع آرایه‌ای از اعداد صحیح را برمی‌گرداند که هر کدام شناسه یک دستگاه ورودی متفاوت هستند. این تابع زمانی مفید است که در حال ساخت بازی‌ای هستید که از چندین بازیکن پشتیبانی می‌کند و می‌خواهید تعداد کنترلرهای متصل را تشخیص دهید.
registerInputDeviceListener()
آینه‌های registerInputDeviceListener() . به شما امکان می‌دهد هنگام اضافه شدن، تغییر یا حذف یک دستگاه جدید، ثبت نام کنید تا مطلع شوید.
unregisterInputDeviceListener()
تابع unregisterInputDeviceListener() را منعکس می‌کند. یک شنونده‌ی دستگاه ورودی را از حالت ثبت خارج می‌کند.
onGenericMotionEvent()
Mirrors onGenericMotionEvent() . به بازی شما اجازه می‌دهد اشیاء MotionEvent و مقادیر محور را که نشان‌دهنده رویدادهایی مانند حرکات جوی‌استیک و فشردن ماشه آنالوگ هستند، رهگیری و مدیریت کند.
onPause()
وقتی فعالیت اصلی متوقف می‌شود یا وقتی بازی دیگر فوکوس ندارد، نظرسنجی برای رویدادهای کنترل‌کننده بازی را متوقف می‌کند.
onResume()
وقتی فعالیت اصلی از سر گرفته می‌شود، یا وقتی بازی شروع می‌شود و در پیش‌زمینه اجرا می‌شود، شروع به جمع‌آوری اطلاعات برای رویدادهای کنترل‌کننده بازی می‌کند.
InputDeviceListener
رابط InputManager.InputDeviceListener را منعکس می‌کند. به بازی شما اطلاع می‌دهد که چه زمانی یک کنترلر بازی اضافه، تغییر یا حذف شده است.

در مرحله بعد، پیاده‌سازی‌هایی برای InputManagerCompat ایجاد کنید که در نسخه‌های مختلف پلتفرم کار کنند. اگر بازی شما روی اندروید ۴.۱ یا بالاتر اجرا می‌شود و یک متد InputManagerCompat را فراخوانی می‌کند، پیاده‌سازی پروکسی، متد معادل آن را در InputManager فراخوانی می‌کند. با این حال، اگر بازی شما روی اندروید ۳.۱ تا اندروید ۴.۰ اجرا می‌شود، پیاده‌سازی سفارشی، فراخوانی‌های متدهای InputManagerCompat را تنها با استفاده از APIهایی که حداکثر تا اندروید ۳.۱ معرفی شده‌اند، پردازش می‌کند. صرف نظر از اینکه کدام پیاده‌سازی مختص نسخه در زمان اجرا استفاده می‌شود، پیاده‌سازی نتایج فراخوانی را به صورت شفاف به بازی بازمی‌گرداند.

نمودار کلاس از پیاده‌سازی‌های رابط و نسخه خاص.
شکل ۱ .: نمودار کلاس پیاده‌سازی‌های مختص رابط و نسخه.

رابط کاربری را در اندروید ۴.۱ و بالاتر پیاده‌سازی کنید

InputManagerCompatV16 یک پیاده‌سازی از رابط InputManagerCompat است که فراخوانی‌های متد را به یک InputManager واقعی و InputManager.InputDeviceListener پروکسی می‌کند. InputManager از Context سیستم دریافت می‌شود.

کاتلین

// 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
    ...
}

جاوا

// 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
    }

}

پیاده‌سازی رابط کاربری روی اندروید ۳.۱ تا اندروید ۴.۰

برای ایجاد یک پیاده‌سازی از InputManagerCompat که از اندروید ۳.۱ تا اندروید ۴.۰ پشتیبانی می‌کند، می‌توانید از اشیاء زیر استفاده کنید:

  • یک SparseArray از شناسه‌های دستگاه برای ردیابی کنترلرهای بازی که به دستگاه متصل هستند.
  • یک Handler برای پردازش رویدادهای دستگاه. وقتی یک برنامه شروع یا از سر گرفته می‌شود، Handler پیامی برای شروع نظرسنجی برای قطع اتصال دسته بازی دریافت می‌کند. Handler یک حلقه را برای بررسی هر دسته بازی متصل شناخته شده شروع می‌کند و بررسی می‌کند که آیا شناسه دستگاه برگردانده می‌شود یا خیر. مقدار بازگشتی null نشان می‌دهد که دسته بازی قطع شده است. Handler وقتی برنامه متوقف می‌شود، نظرسنجی را متوقف می‌کند.
  • Map از اشیاء InputManagerCompat.InputDeviceListener . شما از شنونده‌ها برای به‌روزرسانی وضعیت اتصال کنترلرهای بازی ردیابی‌شده استفاده خواهید کرد.

کاتلین

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

جاوا

// 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() را بازنویسی کند. این متد بررسی می‌کند که آیا یک کنترلر بازی متصل قطع شده است یا خیر و به شنوندگان ثبت‌نام‌شده اطلاع می‌دهد.

کاتلین

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

جاوا

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

برای شروع و توقف نظرسنجی برای قطع اتصال دسته بازی، این متدها را بازنویسی کنید:

کاتلین

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

جاوا

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() را override کنید. هنگامی که سیستم یک رویداد حرکتی را گزارش می‌دهد، بررسی کنید که آیا این رویداد از شناسه دستگاهی که قبلاً ردیابی شده است یا از یک شناسه دستگاه جدید آمده است. اگر شناسه دستگاه جدید است، به شنوندگان ثبت شده اطلاع دهید.

کاتلین

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

جاوا

@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 اجرا می‌شود، متد callback مناسب شنونده فراخوانی می‌شود تا نشان دهد که آیا کنترل‌کننده بازی اضافه، تغییر یا حذف شده است یا خیر.

کاتلین

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

}

جاوا

@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 دارید: یکی که روی دستگاه‌های دارای اندروید ۴.۱ و بالاتر کار می‌کند، و دیگری که روی دستگاه‌های دارای اندروید ۳.۱ تا اندروید ۴.۰ کار می‌کند.

از پیاده‌سازی مختص هر نسخه استفاده کنید

منطق سوئیچینگ مختص به نسخه در کلاسی پیاده‌سازی شده است که به عنوان یک factory عمل می‌کند.

کاتلین

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

جاوا

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 اصلی خود ثبت کنید. به دلیل منطق تغییر نسخه‌ای که تنظیم کرده‌اید، بازی شما به طور خودکار از پیاده‌سازی مناسب برای نسخه اندروید دستگاه استفاده می‌کند.

کاتلین

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

جاوا

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() را در نمای اصلی خود، همانطور که در بخش «مدیریت رویداد حرکتی از یک کنترلر بازی و بالاتر» توضیح داده شده است، بازنویسی کنید. اکنون بازی شما باید بتواند رویدادهای کنترلر بازی را به طور مداوم در دستگاه‌هایی که اندروید ۳.۱ (سطح API) دارند، پردازش کند.

کاتلین

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

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

جاوا

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

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

می‌توانید پیاده‌سازی کاملی از این کد سازگاری را در کلاس GameView که در فایل نمونه ControllerSample.zip موجود است، پیدا کنید و آن را دانلود کنید.