33

我想运行一个 Android 后台服务,该服务将在主屏幕或手机处于睡眠状态时充当按键监听器。这可能吗?

从网上的半相关示例中,我整理了以下服务,但收到错误消息“onKeyDown is undefined for the type 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 默认使用搜索栏,但这实际上只是为了非常特殊的用途。我真的不希望除了我以外的任何人想要这个。例如,我认为使用相机按钮唤醒手机会很好。

4

4 回答 4

18

据我所知,KeyEvents 只能由活动处理,因为它们是用户按键的界面。服务在后台运行,并不打算对用户输入做出反应。这也是您的编译器警告“onKeyDown 对于类型服务未定义”的原因。服务或其任何超类不实现 KeyEvent.Callback 接口。作为一种解决方法,您可以在您的 Activity 中注册一个 ActivityAndroidManifest.xml以对某些系统通知做出反应,例如android.intent.action.SCREEN_ON. 当按下电源按钮打开屏幕时,您的活动可以启动,初始化某种服务并返回后台。如果那是你打算做的。有关可能的操作,请参阅Intent 文档

希望有帮助...

于 2010-11-16T13:10:03.967 回答
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();
        }
    }
}
于 2018-03-10T19:45:03.593 回答
6

KeyEvent 需要触发 Activity。因此,无法通过服务检测到硬件按键,因为服务没有主机活动。你可以要求一个 SystemOverlay,并创建一个透明的 Activity。但这种方法似乎不适用于 API 26+ 设备。

解决方法是通过 AccessibilityServices 设置观察者。这使您可以全局检测硬件按键。

注意:将应用程序启用为辅助功能应用程序可能会导致重大安全问题,用户会警惕启用此功能。因此,在您的应用程序对于您的应用程序将要处理的数据对用户“透明”的情况下,这将是可取的。此方法适用于所有 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;

    }
}
于 2020-03-31T15:16:23.727 回答
0

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

于 2015-05-22T13:21:20.530 回答