Android 线控处理逻辑-线控注册过程

Windows无线控制iPhone:WebDriverAgent与tidevice开源实战 在Windows上无线控制iPhone,是许多开发者和测试人员遇到的痛点。传统投屏软件只解决“看”的问题,无法进行点击、滑动等反向操作。通过WebDriverAgent与tidevice这套开源组合,可以在Windows端调用iOS的XCTest自动化接口,将iPhone变为可接收WebDriver指令的设备,实现截图预览与触摸控制。该方案无需越狱、无需商业授权,适用于iOS自动化测试、真机调试和临时远程操作。文章从环境准备、USB验证到无线切换,系统梳理了关键配置与高频报错排查步骤,帮助新手快速跑通这套可 阅读详情

    【线控】:机电行业特定短语。指机电控制里边的一种物理控制方式,主要是指信号发生器与信号接收器之间的连接方式是通过线缆或其他动作传到物体进行连接的。

    Android线控:我们经常使用耳机上面的上一曲、下一曲、播放、暂停等操作。大体逻辑是耳机上的物理按键按下后会作为系统系统键值处理。之前看过android4.4的源码最近看android6.0的源码发现两者处理的方式有明显不同,本篇主要从adnroid4.4跟android6.0两套源码对比分析下线控的注册过程 后续分析源码中线控的下发逻辑。

【Android 4.4.3线控注册过程】

        一。android应用软件中线控的注册

                1.1 实现BroadcastReceiver 类,如下:

public class MediaKeyReceiverextends BroadcastReceiver {
    private String Tag = "MediaKeyReceiver001";
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(Intent.ACTION_MEDIA_BUTTON)) {
            KeyEvent keyEvent = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT);
            switch (keyEvent.getKeyCode()) {
                case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: //播放暂停
                    if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
                        L.i(Tag, "KEYCODE_MEDIA_PLAY_PAUSE  ACTION_UP");
                    } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
                        L.i(Tag, "KEYCODE_MEDIA_PLAY_PAUSE  ACTION_DOWN");
                    }
                    break;
                case KeyEvent.KEYCODE_MEDIA_NEXT://下一曲
                    if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
                        L.i(Tag, "KEYCODE_MEDIA_NEXT  ACTION_UP");
                    } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
                        L.i(Tag, "KEYCODE_MEDIA_NEXT  ACTION_DOWN");
                    }
                    break;
                case KeyEvent.KEYCODE_MEDIA_PREVIOUS://上一曲
                    if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
                        L.i(Tag, "KEYCODE_MEDIA_PREVIOUS  ACTION_UP");
                    } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
                        L.i(Tag, "KEYCODE_MEDIA_PREVIOUS  ACTION_DOWN");
                    }
                    break;
            }
        }
    }
}

                1.2 在AndroidManifest.xml中注册自己的BroadcastReceiver

<receiver android:name=".MediaKeyReceiver">
    <intent-filter>
        <action android:name="android.intent.action.MEDIA_BUTTON" />
    </intent-filter>
</receiver>

                1.3 注册线控广播

AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
ComponentName name = new ComponentName(getPackageName(), MediaKeyReceiver.class.getName());
audioManager.registerMediaButtonEventReceiver(name);

        二。Framework中线控的注册过程。

                    注册线控使用AudioManager的registerMediaButtonEventReceiver(ComponentName eventReceiver) ,传递一个ComponentName对象给AudioManager,源码如下:



/**
 * Register a component to be the sole receiver of MEDIA_BUTTON intents.
 *
 * @param eventReceiver identifier of a {@link android.content.BroadcastReceiver}
 *                      that will receive the media button intent. This broadcast receiver must be declared
 *                      in the application manifest. The package of the component must match that of
 *                      the context you're registering from.
 */
public void registerMediaButtonEventReceiver(ComponentName eventReceiver) {
    if (eventReceiver == null) {
        return;
    }
    if (!eventReceiver.getPackageName().equals(mContext.getPackageName())) {
        Log.e(TAG, "registerMediaButtonEventReceiver() error: " +
                "receiver and context package names don't match");
        return;
    }
    // construct a PendingIntent for the media button and register it
    Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
    //     the associated intent will be handled by the component being registered
    mediaButtonIntent.setComponent(eventReceiver);
    PendingIntent pi = PendingIntent.getBroadcast(mContext,
            0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/);
    registerMediaButtonIntent(pi, eventReceiver);
}

        可以看到此处会做下判断,如果当前传递ComponentName的包名跟AudioManager的包名不一致的话不作处理(正常情况下包名都是一致的)。此处构造一个PendingIntent对象,后面我们会看到这个对象是做什么用的。



/**
 * @hide no-op if (pi == null) or (eventReceiver == null)
 */
public void registerMediaButtonIntent(PendingIntent pi, ComponentName eventReceiver) {
    if (pi == null) {
        Log.e(TAG, "Cannot call registerMediaButtonIntent() with a null parameter");
        return;
    }
    IAudioService service = getService();
    try {
        // pi != null
        service.registerMediaButtonIntent(pi, eventReceiver,
                eventReceiver == null ? mToken : null);
    } catch (RemoteException e) {
        Log.e(TAG, "Dead object in registerMediaButtonIntent" + e);
    }
}

    可以看到最后实际上调用的是AudioManager的隐藏的registerMediaButtonIntent方法将PendingIntent跟ComponentName参数传递给AudioService,AudioService又传递给MediaFocusControl

public void registerMediaButtonIntent(PendingIntent pi, ComponentName c, IBinder token) {
    mMediaFocusControl.registerMediaButtonIntent(pi, c, token);
}

      直接看MediaFocusControl中的处理



/**
 * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c)
 * precondition: mediaIntent != null
 */
protected void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver, IBinder token) {
    Log.i(TAG, "  Remote Control   registerMediaButtonIntent() for " + mediaIntent);
    synchronized (mAudioFocusLock) {
        synchronized (mRCStack) {
            if (pushMediaButtonReceiver_syncAfRcs(mediaIntent, eventReceiver, token)) {
                // new RC client, assume every type of information shall be queried
                checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
            }
        }
    }
}
/**
 * Helper function:
 * Set the new remote control receiver at the top of the RC focus stack.
 * Called synchronized on mAudioFocusLock, then mRCStack
 * precondition: mediaIntent != null
 *
 * @return true if mRCStack was changed, false otherwise
 */
private boolean pushMediaButtonReceiver_syncAfRcs(PendingIntent mediaIntent,
                                                  ComponentName target, IBinder token) {
    // already at top of stack?
    if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(mediaIntent)) {
        return false;
    }
    if (mAppOps.noteOp(AppOpsManager.OP_TAKE_MEDIA_BUTTONS, Binder.getCallingUid(),
            mediaIntent.getCreatorPackage()) != AppOpsManager.MODE_ALLOWED) {
        return false;
    }
    RemoteControlStackEntry rcse = null;
    boolean wasInsideStack = false;
    try {
        for (int index = mRCStack.size() - 1; index >= 0; index--) {
            rcse = mRCStack.elementAt(index);
            if (rcse.mMediaIntent.equals(mediaIntent)) {
                // ok to remove element while traversing the stack since we're leaving the loop
                mRCStack.removeElementAt(index);
                wasInsideStack = true;
                break;
            }
        }
    } catch (ArrayIndexOutOfBoundsException e) {
        // not expected to happen, indicates improper concurrent modification
        Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
    }
    if (!wasInsideStack) {
        rcse = new RemoteControlStackEntry(this, mediaIntent, target, token);
    }
    mRCStack.push(rcse); // rcse is never null

    // post message to persist the default media button receiver
    if (target != null) {
        mEventHandler.sendMessage(mEventHandler.obtainMessage(
                MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, target/*obj*/));
    }

    // RC stack was modified
    return true;
}

    可以看到当前线控消息已经在保存的栈并且在栈顶时不作处理,如果没有在栈顶,则移除站内的线控消息同时创建新的线控对象保存在栈顶。到此线控的注册过程完成。

【Android 6.0.1线控注册过程】

  

  一。android应用软件中线控的注册

       通过android4.4的注册过程看到是通过广播方式来接收线控消息,通过PendingIntent传递,可能因为广播本身是不可靠的通讯方式所以android5.0后修改为Binder来通讯,通过MediaSession来管理。

        1.1 MediaSession的初始化

               

public class MediaKeyService extends Service {
    private MediaSession mediaSession;
    private AudioManager audioManager;
    @RequiresApi(api = 26)
    @Override
    public void onCreate() {
        super.onCreate();
        audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        initMediaSession();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mediaSession!=null){
            mediaSession.release();
        }
    }

    private void play(){
        //自己的播放逻辑,需要更新MediaSession状态
        updatePlayState(false);
    }

    private void pause(){
        //自己的暂停逻辑,需要更新MediaSession状态
        updatePlayState(true);
    }

    private void updatePlayState(boolean isPlaying){
        if (mediaSession==null)
            return;
        PlaybackState.Builder stateBuilder = new PlaybackState.Builder()
                .setActions(PlaybackState.ACTION_PLAY
                        | PlaybackState.ACTION_PLAY_PAUSE
                        | PlaybackState.ACTION_PLAY_FROM_MEDIA_ID
                        | PlaybackState.ACTION_PAUSE
                        | PlaybackState.ACTION_SKIP_TO_NEXT
                        | PlaybackState.ACTION_SKIP_TO_PREVIOUS);
        if (isPlaying) {
            stateBuilder.setState(PlaybackState.STATE_PLAYING,
                    PlaybackState.PLAYBACK_POSITION_UNKNOWN,
                    SystemClock.elapsedRealtime());
        } else {
            stateBuilder.setState(PlaybackState.STATE_PAUSED,
                    PlaybackState.PLAYBACK_POSITION_UNKNOWN,
                    SystemClock.elapsedRealtime());
        }
        mediaSession.setPlaybackState(stateBuilder.build());
    }

    private void initMediaSession() {
        mediaSession = new MediaSession(this, "com.android.music");
        mediaSession.setCallback(new MediaSession.Callback() {

            @Override
            public void onSkipToPrevious() { //上一曲

            }

            @Override
            public void onSkipToNext() {//下一曲

            }

            @Override
            public void onPlay() {//播放

            }

            @Override
            public void onPause() {//暂停

            }
        });
mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);

 } AudioManager.OnAudioFocusChangeListener audioFocusLinister = new AudioManager.OnAudioFocusChangeListener() { @Override public void onAudioFocusChange(int focusChange) { switch (focusChange) { case AudioManager.AUDIOFOCUS_LOSS://焦点被别的应用抢占,失去焦点此时不再接收线控消息 mediaSession.setActive(false); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT://短暂的失去音频焦点此时不再接收线控消息 mediaSession.setActive(false); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK://焦点会短暂失去,但是可以继续压低音量播放,如果是压低音量播放此处可以继续接收线控消息 mediaSession.setActive(true); break; case AudioManager.AUDIOFOCUS_GAIN://完全获得焦点,接收线控消息 mediaSession.setActive(true); break; default: } } }; }

    以上为伪代码,在Service的onCreate中初始化MediaSession在onDestory()中注销,当需要监听线控消息的时候调用 mediaSession.setActive(true)接口来激活,不需要监听线控消息的时候调用 mediaSession.setActive(false)来注销。因为线控消息一般跟AudioFocus同步,即获取焦点的时候激活线控回调,失去焦点时注销。所以我在AudioFocus的回调中处理。

    细心的同学会注意到代码里面有更新当前的播放状态updatePlayState(),可能有疑问为什么要将当前的播放状态传递给MediaSession。一般手机线控播放暂停是同一个按键,在android 4.4直接接收的是按键值,KEYCODE_MEDIA_PLAY_PAUSE键代表了播放暂停键需要根据自己当前的状态来做处理,在android 6.0中没有播放暂停的接口,所以需要将自己当前播放状态上传至MediaSession中,在消息分发过程中会判断当前的播放状态来分发。

  二。Framework中线控的注册过程

        

public MediaSession(@NonNull Context context, @NonNull String tag, int userId) {
    if (context == null) {
        throw new IllegalArgumentException("context cannot be null.");
    }
    if (TextUtils.isEmpty(tag)) {
        throw new IllegalArgumentException("tag cannot be null or empty");
    }
    mMaxBitmapSize = context.getResources().getDimensionPixelSize(
            com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize);
    mCbStub = new CallbackStub(this);
    MediaSessionManager manager = (MediaSessionManager) context
            .getSystemService(Context.MEDIA_SESSION_SERVICE);
    try {
        mBinder = manager.createSession(mCbStub, tag, userId);
        mSessionToken = new Token(mBinder.getController());
        mController = new MediaController(context, mSessionToken);
    } catch (RemoteException e) {
        throw new RuntimeException("Remote error creating session.", e);
    }
}

ESP32与BLE无线控制入门:MicroPython实战手机APP控制LED BLE(低功耗蓝牙)是一种无需配网、点对点通信的无线技术,基于GATT协议栈,通过服务、特征值和UUID实现数据交互。其技术价值在于低功耗、低成本、易集成,广泛应用于物联网设备控制、传感器数据上报和智能家居等场景。在ESP32开发中,使用MicroPython可以快速搭建BLE外设,定义服务与特征值,接收手机指令并控制硬件。本文以手机APP控制LED为例,详细讲解ESP32端BLE服务搭建、UUID配置、数据收发及常见问题排查,帮助零基础读者快速掌握无线控制核心技能。 阅读详情

相关推荐

空调线控器弱电接线与蓝牙调试标准化指南

智能家居设备接入的稳定性,本质取决于物理层与协议层的协同可靠性。弱电接线定义了24V供电、RS485差分通信和屏蔽接地等基础电气特性,而蓝牙调试则涉及HC-05/JDY-31等模块的AT指令交互、电平匹配与时序控制。二者脱节将导致‘连得上但控不了’‘配对成功却频繁掉线’等典型故障。本指南从毫米级剥线工艺、毫秒级AT响应优化出发,融合RS485抗干扰设计与蓝牙SPP透传稳定性保障,提供可落地的施工验收标准与12步调试SOP,适用于弱电工程师、集成商技术负责人及硬件产品经理,直击现场90%返工根源。

weixin_33841503的博客 242

Android AudioFocus以及MediaFocuControl处理流程

Android 使用AudioFocus控制多个应用的声音输出。用户可以通过requestAudioFocus(OnAudioFocusChangeListener l, int Stream_type,int dura_hint)或者abandonAudioFocus(onAudioFocusChangedListener)来申请或者释放焦点,同时可以接收onAudioFocusChangeLi

wxr_feixiang的博客 3036

BLE蓝牙无线控制开发全攻略:从协议原理到工程实践

低功耗蓝牙(BLE)作为一种主流的短距离无线通信技术,其核心在于低功耗、低成本以及与智能手机的天然互通性。其工作原理基于客户端-服务器模型,通过GATT协议定义服务和特征值进行数据交换。在物联网和消费电子领域,BLE的技术价值体现在为电池供电设备提供长效续航,并为用户带来便捷的交互体验。典型的应用场景包括智能家居控制、可穿戴设备及各类传感器网络。本文将深入探讨BLE开发中的关键工程实践,例如针对**嵌入式开发**中的功耗优化策略,以及如何利用**nRF Connect**等工具进行高效调试,帮助开发者构建稳

weixin_30235225的博客 345

android 音乐焦点,Android音频焦点详解(下)——源码详解

耽搁了几天,最近一直在忙找工作的事情,今天把这篇文章补上。本文基于Android7.1.1版本进行分析,主要涉及以下几个文件:1 AudioManager --> /frameworks/base/media/java/android/media/2 AudioService --> /frameworks/base/services/core/java/com/android/ser...

weixin_42564147的博客 1115

Android 13媒体按键失灵?MediaSession适配与修复全攻略

在移动应用开发中,媒体播放功能与系统交互的稳定性直接影响用户体验。MediaSession作为连接应用与系统媒体控件的核心桥梁,其配置的规范性在Android 13及更高版本中变得尤为关键。系统通过MediaSession的PlaybackState声明来判断应用支持的按键操作,如播放、暂停、切换曲目等;若声明不完整或状态未正确更新,耳机线控、蓝牙AVRCP指令及锁屏媒体面板就会失效。此外,音频焦点的正确申请、MediaButtonReceiver的注册以及Android 13新增的POST_NOTIFIC

weixin_34279579的博客 322

基于BLE双MCU架构的嵌入式机器人无线控制系统设计与实现

蓝牙低功耗(BLE)作为一种低功耗、短距离的无线通信技术,其核心原理基于GATT(通用属性协议)架构,通过定义服务、特征值和属性来实现设备间的结构化数据交换。在嵌入式系统领域,BLE的技术价值在于为资源受限的微控制器(MCU)提供了稳定、高效的双向数据通道,特别适合需要实时控制与状态反馈的应用场景,如物联网设备、智能传感器网络和机器人控制。本文聚焦于一个典型的嵌入式机器人项目,深入剖析如何采用“应用处理器+网络处理器”的双MCU架构,将BLE通信无缝集成到实时控制系统中。通过解析MSP432与CC2650的

weixin_30732487的博客 490

android媒体焦点音量压低/暂停逻辑源码简析

请求焦点的过程中,其他媒体客户端会因媒体焦点丢失进行媒体压低或暂停,下面从framework层简单分析其过程。添加到压低媒体apc(AudioPlaybackConfiguration)列表。以上为媒体焦点音量压低的简单逻辑分析。得到的是IPlayer实例,内部类。进行音量压低,apc即。实现音频音量控制,调用。...

分享技术与经验的博客 2637

Android音频子系统(driver+android)

Linux 驱动 ALSA模型: Linux ALSA声卡驱动之一:ALSA架构简介 Linux ALSA声卡驱动之二:声卡的创建 Linux ALSA声卡驱动之三:PCM设备的创建 Linux ALSA声卡驱动之四:Control设备的创建 Linux ALSA声卡驱动之五:移动设备中的ALSA(ASoC) Linux ALSA声卡驱动之六:ASoC架构中的Machine Linux ALSA声卡驱动之七:ASoC架构中的Codec Linux ALSA声卡驱动之八:ASoC架构中的Platform AL

weixin_44991625的博客 1733

Android耳机线控-播放/暂停/上一曲/下一曲

起因前一阵子完成了用有线耳机控制Android手机App的音频播放,具体实现了用耳机线的按键完成播放、暂停、上一曲、下一曲的功能。在网上查阅了一些资料,但不是特别尽如人意,记得有一篇写的很不错的这方面的文章里有两处很细微的错误,我不经分辨拿来用的时候没有达到我想要的线控效果。后来从头做起,每写完一块代码,就进行打Log测试,查看输出日志是否实现了我期待的结果。就这样一步一步,最后完美解决。我想这种方

种因收果 1万+

Android Audio焦点——音频焦点监听(三)

其实上面的流程就是应用申请/放弃焦点的回调方法,以及应用被动失去焦点和重新获取焦点的回调流程。

小旭的专栏 1974

利用Android广播实现耳机线控功能

在现代智能手机中,耳机线控功能已成为用户交互体验的重要组成部分。它不仅允许用户在不触碰手机的情况下控制音乐播放,还能进行通话接听等操作。实现这一功能需要深入了解Android系统的广播接收器和Intent机制。广播接收器(BroadcastReceiver)是Android中用于监听系统或应用发出的广播消息的一种组件。它是一种全局的监听器,能够接收来自应用或系统的各种广播消息(如电量变化、时间变化、电话状态变化等),并根据接收到的广播进行相应的处理。

weixin_36012152的博客 602

Android锁屏实现与总结

Android锁屏实现与总结(网易云阅读) 一、自定义锁屏基本原理 二、重要步骤 1、广播注册 2、Activity设置 3、按键的屏蔽 4、滑屏解锁 5、Event bus的使用 三、出现的问题 1、小米和魅族等手机锁屏权限问题 2、透明栏与沉浸模式 3、手机适配 4、处理黑色闪屏 5、线控耳机 6、Android上的「安全音量」 一、自定义锁屏基本原理 先上效果...

idaretobe的专栏 5408

Android利用广播实现耳机的线控

借助广播来实现单按钮耳机的线控,能够识别单击和双击 代码讲解在我的博客里: http://blog.youkuaiyun.com/illidantao/article/details/16847909

Android底层无线控制:从API到驱动的深度实现方案

在Android系统开发中,对无线连接(如WiFi和蓝牙)的底层控制是系统定制与设备管理的核心需求之一。其原理涉及Android的分层架构,从应用层API、框架层服务到硬件抽象层(HAL)和Linux内核驱动,每一层都提供了不同层级的控制能力。这种深度控制的技术价值在于实现强约束的设备管理,例如在企业级MDM(移动设备管理)场景中确保数据安全,或在功耗优化中彻底禁用硬件模块。常见的应用场景包括行业定制设备、ROM深度定制以及系统级功能管控。本文聚焦于Android底层禁用WiFi和蓝牙功能的多种实现路径,涵

weixin_33709364的博客 725

台达伺服B3驱动器参数的设定和CN1的接法

3. B2的电子齿轮比是直接设定P1-44(分子)和P1-45(分母)就就可以的,但是B3的有个隐藏的细节,就是P1-44的默认值77216不用去更改,这是官方客服说的,需要改的是P1-45这个参数,比如说,我使用的丝杆螺距是5mm的,那么就是以5000脉冲走完5mm为一圈,则就把P1-45设定为5000即可;我的是三菱FX3S系列的PLC,是NPN型的,输出点是24V-作为信号的,所以接41、37,具体可参考B3的。,跟着客服的讲解操作才解决的,以下就是解决方法,希望能够帮到需要的各位。

qq_38681017的博客 1万+

VSCode + ESP-IDF 项目搭建及配置

VSCode 搭建 ESP-IDF 开发环境

weixin_51358957的博客 2万+

Android中的Audio播放:控制Audio输出通道切换

Audio 输出通道有很多,Speaker、headset、bluetooth A2DP等。通话或播放音乐等使用Audio输出过程中,可能发生Audio输出通道的切换。比如,插入有线耳机播放音乐时,声音是从耳机发出的;而此时拔出耳机,Audio输出通道会发生切换。如果音乐播放器不做处理,Audio输出是被切换到扬声器的,声音直接从Speaker发出。我们在编写程序时,要捕获并按照需求来处理这样的事

371

利用PV操作控制进程的并发进行---经典题解

前趋图 先理清楚前趋图中的逻辑关系:P1没有前驱,P2的前驱是P1,P3的前驱是P1、P2,P4的前驱是P2,P5的前驱是P3、P4。 前驱就是指只有在前驱进程完成后,该进程才能开始执行。由图可知,这里进程之间有6条有向弧,分别表示为P1→P2,P1→P3,P2→P3,P2→P4,P3→P5,P4→P5, 各个进程间的逻辑关系,那么我们需要设定6个信号量(S1、S2、S3、S4、S5、S6),利用PV操作来控制这些过程。 对于第一个空,P1执行完成之后,需要通知P2、P3可以开始,此处需要V(S

Yan Ka Kue的博客 6137

【Android】监听媒体按键按下--响应媒体按钮

媒体按钮是 Android 设备和其他外围设备上的硬件按钮,例如蓝牙耳机或者线控耳机上的暂停/播放按钮。当用户按下媒体按钮时,Android 会生成 KeyEvent,其中包含用于标识按钮的键码。 可先阅读一下官方的文档,了解下整个流程和机制: 官方文档:响应媒体按钮 本文是监听播放、上一首、下一首等按键事件的代码实现。 不同版本的实现方式,差异有点大,比较碎片化。本人肤浅地认为Android5.0以下通过广播的方式接收,5.0及以上通过MediaSession,官方也封装了MediaSessionComp

toaksg的博客 6005

PID、模糊PID、SkyHook、LQR、H2/H∞、ADRC等悬架控制合集

PID、模糊PID、SkyHook、LQR、H2/H∞等悬架控制合集

Mr.邹的博客 2013

深度学习论文: Towards Total Recall in Industrial Anomaly Detection

在大规模工业制造中,能够准确检测出有缺陷的零件至关重要。我们在这项工作中面临的一个特殊挑战是冷启动问题:仅使用正常的示例图像来训练模型。虽然可以针对每个类别手动设计解决方案,但我们的目标是构建能够自动适用于多个任务的系统。最佳的方法是将ImageNet模型的嵌入特征与异常检测模型结合起来。在本文中,我们在这一研究基础上进行了扩展,并提出了PatchCore,它利用了一个具有最大代表性的正常补丁特征内存库。通过这种方法,我们能够更好地解决冷启动问题,并提高系统在多任务上的性能。

mingo_敏 1878
下一篇: 流媒体测试地址
Devin.wang
博客等级 码龄8年 150粉丝 40原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值