是否可以创建一个监听硬件按键的 Android 服务?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2986337/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is it possible to create an Android Service that listens for hardware key presses?
提问by Brian
I'd like to run an Android background service that will act as a keylistener from the home screen or when the phone is asleep. Is this possible?
我想运行一个 Android 后台服务,该服务将在主屏幕或手机处于睡眠状态时充当密钥侦听器。这可能吗?
From semi-related examples online, I put together the following service, but get the error, "onKeyDown is undefined for the type Service". Does this mean it can't be done without rewriting Launcher, or is there something obvious I'm missing?
从网上的半相关示例中,我将以下服务放在一起,但收到错误消息“onKeyDown 未定义类型服务”。这是否意味着不重写 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);
}
}
I realize Android defaults to the search bar when you type from the home screen, but this really is just for a very particular use. I don't really expect anyone but me to want this. I just think it'd be nice, for example, to use the camera button to wake the phone.
我意识到当您从主屏幕键入时,Android 默认为搜索栏,但这确实只是用于非常特殊的用途。除了我,我真的不期望任何人想要这个。例如,我只是认为使用相机按钮唤醒手机会很好。
采纳答案by MarioB.
As far as I know KeyEvents can only be handled by Activities as they are the interface to the user pressing the keys. Services run in the background and are not intended to react on user input. That's also the reason of your compiler warning "onKeyDown is undefined for the type Service". Service or any of it's Superclasses don't implement the KeyEvent.Callback interface. As a workaround you could register an Activity in your AndroidManifest.xml
to react on certain system notifications such as android.intent.action.SCREEN_ON
. When the power button is pressed to turn on the screen your activity could be started, initializing a service of some kind and go back to the background. If that's what you intend to do. See Intent docsfor possible Actions.
据我所知,KeyEvents 只能由活动处理,因为它们是用户按键的界面。服务在后台运行,不会对用户输入做出反应。这也是您的编译器警告“onKeyDown 类型服务未定义”的原因。服务或其任何超类不实现 KeyEvent.Callback 接口。作为一种解决方法,您可以在您的 Activity 中注册一个 ActivityAndroidManifest.xml
以对某些系统通知做出反应,例如android.intent.action.SCREEN_ON
. 当按下电源按钮打开屏幕时,您的活动可以启动,初始化某种服务并返回后台。如果这就是你打算做的。有关可能的操作,请参阅Intent 文档。
Hope that helped...
希望有所帮助...
回答by Gibolt
This requires Lollipop (v5.0/API 21) or higher, and can only detect volume keys
这需要 Lollipop (v5.0/API 21) 或更高版本,并且只能检测音量键
It will override volume key action, so using it globally may not be desired.
它将覆盖音量键操作,因此可能不需要全局使用它。
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();
}
}
}
回答by Irfan S
KeyEvents require an Activity to be triggered. Hence, hardware key presses can't be detected via Services,as Services do not have a host activity. You could ask for a SystemOverlay, and create a transparent Activity. But this approach doesn't seem to work on API 26+ devices.
KeyEvents 需要触发 Activity。因此,无法通过服务检测到硬件按键,因为服务没有主机活动。您可以要求 SystemOverlay,并创建一个透明的 Activity。但是这种方法似乎不适用于 API 26+ 设备。
A workaround for this would be set up an observer through AccessibilityServices. This allows you to globally detect hardware key presses.
解决方法是通过 AccessibilityServices 设置观察者。这允许您全局检测硬件按键按下。
Note: Enabling an app as an Accessibility App can lead to major security issues, and users would be wary of enabling this. So this would be advisable in situations where your application can be "transparent" to the user with regard to the data your app will be handling. This method works for all API 21+ devices, I haven't tested on devices below this, so it may or may not work.
注意:将应用程序启用为辅助功能应用程序可能会导致重大安全问题,用户会谨慎启用此功能。因此,在您的应用程序对于您的应用程序将处理的数据对用户“透明”的情况下,这将是可取的。此方法适用于所有 API 21+ 设备,我尚未在低于此的设备上进行过测试,因此它可能有效也可能无效。
Steps:
脚步:
Create an XML file, with the following options
<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" />
Define your AccessibilityService in the Manifest
<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>
Create an AccessibilityService Class
使用以下选项创建一个 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() {
}
}
- Create a MainActivity, that handles the permission request for this.
- 创建一个 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;
}
}
回答by Tad
While it is not possible to listen for hardware key presses directly in a service, you can sometimes listen for the effects of those key presses. For example, this answerdescribes how to infer volume key presses from changes in media volume.
虽然无法直接在服务中监听硬件按键,但您有时可以监听这些按键的效果。例如,这个答案描述了如何从媒体音量的变化推断音量按键。