是否可以创建一个监听硬件按键的Android服务?

38

我想运行一个Android后台服务,它将在主屏幕或手机睡眠时作为按键监听器。这可行吗?

根据在线的类似示例,我编写了以下服务,但出现错误“onKeyDown未定义Service类型”。这是否意味着不能在不重写Launcher的情况下完成,还是有什么明显的问题我忽略了?

public class ServiceName extends Service {

    @Override
    public void onCreate() {
        //Stuff
    }

    public IBinder onBind(Intent intent) {
        //Stuff
        return null;
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if(event.getAction() == KeyEvent.ACTION_DOWN) {
        switch(keyCode) {
        case KeyEvent.KEYCODE_A:
            //Stuff
            return true;
        case KeyEvent.KEYCODE_B:
            //Stuff
            return true;

            //etc.
        }
        }

        return super.onKeyDown(keyCode, event);
    }
}

我知道当你在主屏幕上键入时,Android默认会跳转到搜索栏,但这只适用于非常特定的用途。我并不认为除了我之外还有其他人会需要这个功能。例如,我认为使用相机按钮唤醒手机是一个不错的选择。


2
hiii 如果你已经找到了解决这个问题的方法,请告诉我。谢谢。 - Its not blank
4个回答

19
据我所知,只有 Activities 才能处理 KeyEvents,因为它们是用户按键的接口。Services 在后台运行,不打算对用户输入做出反应。这也是编译器警告 “onKeyDown 在 Service 类型中未定义” 的原因。Service 或其任何超类都没有实现 KeyEvent.Callback 接口。作为一种解决方法,您可以在 AndroidManifest.xml 中注册一个 Activity 来对某些系统通知进行反应,例如 android.intent.action.SCREEN_ON。当按下电源按钮以打开屏幕时,您的 Activity 可以启动并初始化某种类型的服务,然后返回后台。如果这正是您想要做的事情,可以查看 Intent 文档 获取可能的操作。希望这有所帮助...

1
抱歉如果我误解了。Romain Guy在Android团队工作,他确认过了这一点。 - user942821

12
KeyEvents需要触发活动(Activity)。因此,硬件按键无法通过服务(Service)检测,因为服务没有托管活动。您可以请求SystemOverlay并创建一个透明的活动。但是,在API 26+设备上,这种方法似乎不起作用。
解决方法是通过AccessibilityServices设置观察者(observer)。这允许您全局检测硬件按键。
注意:将应用程序启用为辅助功能应用程序可能会导致重大安全问题,并且用户可能会警惕启用此功能。因此,在处理应用程序将处理的数据方面,建议在应用程序“透明”于用户的情况下使用此方法。此方法适用于所有API 21+设备,我尚未测试低于此版本的设备,因此可能有效也可能无效。
步骤: - 创建一个XML文件,包括以下选项
  <accessibility-service
    android:accessibilityFlags="flagRequestFilterKeyEvents"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackAllMask"
    android:notificationTimeout="100"
    android:canRetrieveWindowContent="true"
    android:settingsActivity=""
    android:packageNames="yourpackagename"
    android:canRequestFilterKeyEvents="true"
  />
  • 在清单文件中定义您的AccessibilityService

  • <service android:name=".Services.AccessibilityKeyDetector"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>
            <meta-data android:name="android.accessibilityservice"
                android:resource="@xml/accessibility_layout" />
    </service>
    
  • 创建一个AccessibilityService类

  • public class AccessibilityKeyDetector extends AccessibilityService {
    
        private final String TAG = "AccessKeyDetector";
    
        @Override
        public boolean onKeyEvent(KeyEvent event) {
            Log.d(TAG,"Key pressed via accessibility is: "+event.getKeyCode());
            //This allows the key pressed to function normally after it has been used by your app.
            return super.onKeyEvent(event);
        }
    
    
        @Override
        protected void onServiceConnected() {
            Log.i(TAG,"Service connected");
    
        }
    
        @Override
        public void onAccessibilityEvent(AccessibilityEvent event) {
    
        }
    
    
        @Override
        public void onInterrupt() {
    
        }
    }
    
    
    • 创建一个MainActivity,用于处理此权限请求。
    public class MainActivity extends AppCompatActivity {
    
        private final String TAG = "Test";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            checkAccessibilityPermission();
        }
    
        public boolean checkAccessibilityPermission() {
            int accessEnabled=0;
            try {
                accessEnabled = Settings.Secure.getInt(this.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED);
            } catch (Settings.SettingNotFoundException e) {
                e.printStackTrace();
            }
            if (accessEnabled==0) {
                /** if not construct intent to request permission */
                Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                /** request permission via start activity for result */
                startActivity(intent);
                return false;
            } else {
                return true;
            }
        }
    
    
        @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {
            Log.d(TAG,"Key pressed");
    
    //this prevents the key from performing the base function. Replace with super.onKeyDown to let it perform it's original function, after being consumed by your app.
            return true;
    
        }
    }
    

    2
    你是传奇 - Heals Legodi
    辅助功能服务是否会像其他服务一样在一段时间后被操作系统杀死? - Kamil
    @Kamil,这取决于设备的运行情况。某些OEM(原始设备制造商)如OnePlus,如果用户没有手动为应用程序禁用电池优化,则会停止此类服务。 - Irfan S
    @IrfanS 我明白了。这也适用于常规(非辅助功能)服务吗?如果我想创建常规的Android服务并禁用电池优化 - 常规服务将永远运行而不会被操作系统杀死吗? - Kamil
    没错,但它需要成为前台服务。 - Irfan S

    6

    这需要Lollipop(v5.0/API 21)或更高版本,并且只能检测音量键

    它将覆盖音量键动作,因此全局使用可能不是所期望的。

    public class VolumeKeyController {
    
        private MediaSessionCompat mMediaSession;
        private final Context mContext;
    
        public VolumeKeyController(Context context) {
            mContext = context;
        }
    
        private void createMediaSession() {
            mMediaSession = new MediaSessionCompat(mContext, KeyUtil.log);
    
            mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
                    MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
            mMediaSession.setPlaybackState(new Builder()
                    .setState(PlaybackStateCompat.STATE_PLAYING, 0, 0)
                    .build());
            mMediaSession.setPlaybackToRemote(getVolumeProvider());
            mMediaSession.setActive(true);
        }
    
        private VolumeProviderCompat getVolumeProvider() {
            final AudioManager audio = mContext.getSystemService(Context.AUDIO_SERVICE);
    
            int STREAM_TYPE = AudioManager.STREAM_MUSIC;
            int currentVolume = audio.getStreamVolume(STREAM_TYPE);
            int maxVolume = audio.getStreamMaxVolume(STREAM_TYPE);
            final int VOLUME_UP = 1;
            final int VOLUME_DOWN = -1;
    
            return new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, maxVolume, currentVolume) {
                @Override
                public void onAdjustVolume(int direction) {
                    // Up = 1, Down = -1, Release = 0
                    // Replace with your action, if you don't want to adjust system volume
                    if (direction == VOLUME_UP) {
                        audio.adjustStreamVolume(STREAM_TYPE,
                                AudioManager.ADJUST_RAISE, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
                    }
                    else if (direction == VOLUME_DOWN) {
                        audio.adjustStreamVolume(STREAM_TYPE,
                                AudioManager.ADJUST_LOWER, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
                    }
                    setCurrentVolume(audio.getStreamVolume(STREAM_TYPE));
                }
            };
        }
    
        // Call when control needed, add a call to constructor if needed immediately
        public void setActive(boolean active) {
            if (mMediaSession != null) {
                mMediaSession.setActive(active);
                return;
            }
            createMediaSession();
        }
    
        // Call from Service's onDestroy method
        public void destroy() {
            if (mMediaSession != null) {
                mMediaSession.release();
            }
        }
    }
    

    不幸的是,这在Android 12中停止工作了。https://dev59.com/ym4NtIcB2Jgan1znWGCt - FindOutIslamNow
    很遗憾,这在Android 12中停止工作了。https://stackoverflow.com/questions/70603714/mediasessioncompat-behaviour-changed-in-android-12-and-setplaybacktoremote-stopp - undefined

    0

    虽然在服务中直接监听硬件按键是不可能的,但有时可以监听这些按键的效果。例如,此答案描述了如何从媒体音量的变化中推断出音量键的按下。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接