android 音量 广播,Android平台音量调节(一)音量键调节音量
Android平台音量调节
本文基于Android 8.0讲述Android平台原生音量控制功能。
流的定义
Android中,音量都是分开控制,各种流定义各种流的音量。在Android8.0中,定义了11种流类型。对每种流类型都定义了最大音量(MAX_STREAM_VOLUME),默认音量(DEFAULT_STREAM_VOLUME)和最小音量(MIN_STREAM_VOLUME)。一个流也可以用另外一个流的音量设置,所以需要一个别名映射。
最大音量
* frameworks/base/services/core/java/com/android/server/audio/AudioService.java
private static int[] MAX_STREAM_VOLUME = new int[] {
5, // STREAM_VOICE_CALL
7, // STREAM_SYSTEM
7, // STREAM_RING
15, // STREAM_MUSIC
7, // STREAM_ALARM
7, // STREAM_NOTIFICATION
15, // STREAM_BLUETOOTH_SCO
7, // STREAM_SYSTEM_ENFORCED
15, // STREAM_DTMF
15, // STREAM_TTS
15 // STREAM_ACCESSIBILITY
};
最小音量
* frameworks/base/services/core/java/com/android/server/audio/AudioService.java
private static int[] MIN_STREAM_VOLUME = new int[] {
1, // STREAM_VOICE_CALL
0, // STREAM_SYSTEM
0, // STREAM_RING
0, // STREAM_MUSIC
0, // STREAM_ALARM
0, // STREAM_NOTIFICATION
0, // STREAM_BLUETOOTH_SCO
0, // STREAM_SYSTEM_ENFORCED
0, // STREAM_DTMF
0, // STREAM_TTS
0 // STREAM_ACCESSIBILITY
};
默认音量
* frameworks/base/media/java/android/media/AudioSystem.java
public static int[] DEFAULT_STREAM_VOLUME = new int[] {
4, // STREAM_VOICE_CALL
7, // STREAM_SYSTEM
5, // STREAM_RING
11, // STREAM_MUSIC
6, // STREAM_ALARM
5, // STREAM_NOTIFICATION
7, // STREAM_BLUETOOTH_SCO
7, // STREAM_SYSTEM_ENFORCED
11, // STREAM_DTMF
11, // STREAM_TTS
11, // STREAM_ACCESSIBILITY
};
别名映射
Android中各种设备的映射不尽相同,Android中定义了3中设备DEFAULT,VOICE,TELEVISION对应的别名映射。(STREAM_VOLUME_ALIAS_VOICE)。
STREAM_VOLUME_ALIAS_VOICE对应能处理能处理Voice的设备,比如电话,请映射如下:
* frameworks/base/services/core/java/com/android/server/audio/AudioService.java
private final int[] STREAM_VOLUME_ALIAS_VOICE = new int[] {
AudioSystem.STREAM_VOICE_CALL, // STREAM_VOICE_CALL
AudioSystem.STREAM_RING, // STREAM_SYSTEM
AudioSystem.STREAM_RING, // STREAM_RING
AudioSystem.STREAM_MUSIC, // STREAM_MUSIC
AudioSystem.STREAM_ALARM, // STREAM_ALARM
AudioSystem.STREAM_RING, // STREAM_NOTIFICATION
AudioSystem.STREAM_BLUETOOTH_SCO, // STREAM_BLUETOOTH_SCO
AudioSystem.STREAM_RING, // STREAM_SYSTEM_ENFORCED
AudioSystem.STREAM_RING, // STREAM_DTMF
AudioSystem.STREAM_MUSIC, // STREAM_TTS
AudioSystem.STREAM_MUSIC // STREAM_ACCESSIBILITY
};
STREAM_VOLUME_ALIAS_TELEVISION对应电视,机顶盒等。
* frameworks/base/services/core/java/com/android/server/audio/AudioService.java
private final int[] STREAM_VOLUME_ALIAS_TELEVISION = new int[] {
AudioSystem.STREAM_MUSIC, // STREAM_VOICE_CALL
AudioSystem.STREAM_MUSIC, // STREAM_SYSTEM
AudioSystem.STREAM_MUSIC, // STREAM_RING
AudioSystem.STREAM_MUSIC, // STREAM_MUSIC
AudioSystem.STREAM_MUSIC, // STREAM_ALARM
AudioSystem.STREAM_MUSIC, // STREAM_NOTIFICATION
AudioSystem.STREAM_MUSIC, // STREAM_BLUETOOTH_SCO
AudioSystem.STREAM_MUSIC, // STREAM_SYSTEM_ENFORCED
AudioSystem.STREAM_MUSIC, // STREAM_DTMF
AudioSystem.STREAM_MUSIC, // STREAM_TTS
AudioSystem.STREAM_MUSIC // STREAM_ACCESSIBILITY
};
STREAM_VOLUME_ALIAS_DEFAULT其实和Voice映射是一样的,对应其他的设备,比如平板等。
private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] {
AudioSystem.STREAM_VOICE_CALL, // STREAM_VOICE_CALL
AudioSystem.STREAM_RING, // STREAM_SYSTEM
AudioSystem.STREAM_RING, // STREAM_RING
AudioSystem.STREAM_MUSIC, // STREAM_MUSIC
AudioSystem.STREAM_ALARM, // STREAM_ALARM
AudioSystem.STREAM_RING, // STREAM_NOTIFICATION
AudioSystem.STREAM_BLUETOOTH_SCO, // STREAM_BLUETOOTH_SCO
AudioSystem.STREAM_RING, // STREAM_SYSTEM_ENFORCED
AudioSystem.STREAM_RING, // STREAM_DTMF
AudioSystem.STREAM_MUSIC, // STREAM_TTS
AudioSystem.STREAM_MUSIC // STREAM_ACCESSIBILITY
};
映射关系如下:
流代号
流类型
默认音量
最大音量
最小音量
DEFAULT/Voice映射
TELEVISION映射
0
STREAM_VOICE_CALL
4
5
1
STREAM_VOICE_CALL
STREAM_MUSIC
1
STREAM_SYSTEM
5
7
0
STREAM_RING
STREAM_MUSIC
2
STREAM_RING
5
7
0
STREAM_RING
STREAM_MUSIC
5
STREAM_NOTIFICATION
5
7
0
STREAM_RING
STREAM_MUSIC
7
STREAM_SYSTEM_ENFORCED
7
7
0
STREAM_RING
STREAM_MUSIC
8
STREAM_DTMF
11
15
0
STREAM_RING
STREAM_MUSIC
3
STREAM_MUSIC
11
15
0
STREAM_MUSIC
STREAM_MUSIC
9
STREAM_TTS
11
15
0
STREAM_MUSIC
STREAM_MUSIC
10
STREAM_ACCESSIBILITY
11
15
0
STREAM_MUSIC
STREAM_MUSIC
4
STREAM_ALARM
6
7
0
STREAM_ALARM
STREAM_MUSIC
6
STREAM_BLUETOOTH_SCO
7
15
0
STREAM_BLUETOOTH_SCO
STREAM_MUSIC
所以在手机设备中,从上表来看,我们能调节的其实就5个音量。当你想调节STREAM_SYSTEM,STREAM_NOTIFICATION等流类型的音量时,实际上是调节了STREAM_RING的音量。当前可控的流类型可以通过下表更直观地显示:
流类型
默认音量
最大音量
意义
STREAM_VOICE_CALL
4
5
通话音量
STREAM_RING
5
7
响铃,通知,系统默认音等
STREAM_MUSIC
11
15
多媒体音量
STREAM_ALARM
6
7
闹钟音量
STREAM_BLUETOOTH_SCO
7
15
蓝牙音量
音量键的处理流程
音量键是常用的调节音量的方式,调节音量时,是调节音量节,比如媒体音STREAM_MUSIC的最大音量为15,其实是15节,你也可以理解为16节,因为还有0.
音量键的处理
Android设置了3个音量处理键,音量加,音量减和静音。3个音量键通过PhoneWindowManager进行处理。
* frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
private void dispatchDirectAudioEvent(KeyEvent event) {
if (event.getAction() != KeyEvent.ACTION_DOWN) {
return;
}
int keyCode = event.getKeyCode();
int flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND
| AudioManager.FLAG_FROM_KEY;
String pkgName = mContext.getOpPackageName();
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
try {
getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
} catch (Exception e) {
Log.e(TAG, "Error dispatching volume up in dispatchTvAudioEvent.", e);
}
break;
case KeyEvent.KEYCODE_VOLUME_DOWN:
try {
getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
} catch (Exception e) {
Log.e(TAG, "Error dispatching volume down in dispatchTvAudioEvent.", e);
}
break;
case KeyEvent.KEYCODE_VOLUME_MUTE:
try {
if (event.getRepeatCount() == 0) {
getAudioService().adjustSuggestedStreamVolume(
AudioManager.ADJUST_TOGGLE_MUTE,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
}
} catch (Exception e) {
Log.e(TAG, "Error dispatching mute in dispatchTvAudioEvent.", e);
}
break;
}
}
音量的加减都是一节一节的调节的,比如最大音量为 7,表示有7节,加上0,就是8节。
3音量按键对应如下:
按键
AudioService 对应的操作
实际意义
KEYCODE_VOLUME_UP
ADJUST_RAISE
音量加
KEYCODE_VOLUME_DOWN
ADJUST_LOWER
音量减
KEYCODE_VOLUME_MUTE
ADJUST_TOGGLE_MUTE
静音按钮
ADJUST_TOGGLE_MUTE是静音按钮,如果现在是静音的,那么将切为非静音;如果是非静音的,那么将设置为静音。相关的还有ADJUST_MUTE和ADJUST_UNMUTE。
此外,AudioService相关的还有ADJUST_SAME,这个只是弹出调节音量的框,不去修改音量。
adjustSuggestedStreamVolume通过Binder调到AudioService中。
* frameworks/base/services/core/java/com/android/server/audio/AudioService.java
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
String callingPackage, String caller) {
adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
caller, Binder.getCallingUid());
}
adjustSuggestedStreamVolume调同名函数adjustSuggestedStreamVolume:
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
String callingPackage, String caller, int uid) {
final int streamType;
if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1
streamType = mVolumeControlStream;
} else {
final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType);
final boolean activeForReal;
... ...
}
final boolean isMute = isMuteAdjust(direction);
ensureValidStreamType(streamType);
final int resolvedStream = mStreamVolumeAlias[streamType];
// Play sounds on STREAM_RING only.
if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&
resolvedStream != AudioSystem.STREAM_RING) {
flags &= ~AudioManager.FLAG_PLAY_SOUND;
}
if (mVolumeController.suppressAdjustment(resolvedStream, flags, isMute)) {
direction = 0;
flags &= ~AudioManager.FLAG_PLAY_SOUND;
flags &= ~AudioManager.FLAG_VIBRATE;
if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");
}
adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid);
}
这里主要做了以下几件事:
mUserSelectedVolumeControlStream 表示强制控制某一种流类型,这里应该很少走
getActiveStreamType获取要控制的流类型,从Binder那边传过来的 值为USE_DEFAULT_STREAM_TYPE,getActiveStreamType大概流程如下:
获取Stream的类型
获取映射别名 resolvedStream
对于通知音或铃声STREAM_RING,先显示调节音量的对话框,这个在AudioService的内部类VolumeController中处理。
最后,通过adjustStreamVolume函数设置音量
adjustStreamVolume函数
adjustStreamVolume函数比较长,我们分段来看
* frameworks/base/services/core/java/com/android/server/audio/AudioService.java
private void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage, String caller, int uid) {
... ...
ensureValidDirection(direction);
ensureValidStreamType(streamType);
boolean isMuteAdjust = isMuteAdjust(direction);
if (isMuteAdjust && !isStreamAffectedByMute(streamType)) {
return;
}
int streamTypeAlias = mStreamVolumeAlias[streamType];
VolumeStreamState streamState = mStreamStates[streamTypeAlias];
获取需要设置音量类型的映射别名streamTypeAlias
获取音量类型对应的音量信息状态 streamState,VolumeStreamState 音量控制的核心,映射后的每种音量都有各自的VolumeStreamState,它保存了一个流类型所有的音量信息。
* frameworks/base/services/core/java/com/android/server/audio/AudioService.java
private void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage, String caller, int uid) {
... ...
final int device = getDeviceForStream(streamTypeAlias);
int aliasIndex = streamState.getIndex(device);
boolean adjustVolume = true;
int step;
获取流类型对应的设备
流各自的VolumeStreamState通过observeDevicesForStream_syncVSS去获取对应的device,observeDevicesForStream_syncVSS的获取到的是一个device列表,最终是通过AudioSystem去native获取的。
再从获取的device列表中进行二次选择device:
private int getDeviceForStream(int stream) {
int device = getDevicesForStream(stream);
if ((device & (device - 1)) != 0) {
if ((device & AudioSystem.DEVICE_OUT_SPEAKER) != 0) {
device = AudioSystem.DEVICE_OUT_SPEAKER;
} else if ((device & AudioSystem.DEVICE_OUT_HDMI_ARC) != 0) {
device = AudioSystem.DEVICE_OUT_HDMI_ARC;
} else if ((device & AudioSystem.DEVICE_OUT_SPDIF) != 0) {
device = AudioSystem.DEVICE_OUT_SPDIF;
} else if ((device & AudioSystem.DEVICE_OUT_AUX_LINE) != 0) {
device = AudioSystem.DEVICE_OUT_AUX_LINE;
} else {
device &= AudioSystem.DEVICE_OUT_ALL_A2DP;
}
}
return device;
}
继续看adjustStreamVolume,这部分我们以注释的形式加在代码中
* frameworks/base/services/core/java/com/android/server/audio/AudioService.java
private void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage, String caller, int uid) {
... ...
// 绝对音量控制
if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) == 0 &&
(flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) {
return;
}
// user处理
if (uid == android.os.Process.SYSTEM_UID) {
uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid));
}
// 权限处理
if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)
!= AppOpsManager.MODE_ALLOWED) {
return;
}
// 清掉待处理的音量处理命令
synchronized (mSafeMediaVolumeState) {
mPendingVolumeCommand = null;
}
// 处理是否混音,获取step
flags &= ~AudioManager.FLAG_FIXED_VOLUME;
if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) &&
((device & mFixedVolumeDevices) != 0)) {
flags |= AudioManager.FLAG_FIXED_VOLUME;
// Always toggle between max safe volume and 0 for fixed volume devices where safe
// volume is enforced, and max and 0 for the others.
// This is simulated by stepping by the full allowed volume range
if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&
(device & mSafeMediaVolumeDevices) != 0) {
step = mSafeMediaVolumeIndex;
} else {
step = streamState.getMaxIndex();
}
if (aliasIndex != 0) {
aliasIndex = step;
}
} else {
// convert one UI step (+/-1) into a number of internal units on the stream alias
step = rescaleIndex(10, streamType, streamTypeAlias);
}
// 响铃模式处理
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
(streamTypeAlias == getUiSoundsStreamType())) {
int ringerMode = getRingerModeInternal();
// do not vibrate if already in vibrate mode
if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
flags &= ~AudioManager.FLAG_VIBRATE;
}
// Check if the ringer mode handles this adjustment. If it does we don't
// need to adjust the volume further.
final int result = checkForRingerModeChange(aliasIndex, direction, step,
streamState.mIsMuted, callingPackage, flags);
adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0;
// If suppressing a volume adjustment in silent mode, display the UI hint
if ((result & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) {
flags |= AudioManager.FLAG_SHOW_SILENT_HINT;
}
// If suppressing a volume down adjustment in vibrate mode, display the UI hint
if ((result & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) {
flags |= AudioManager.FLAG_SHOW_VIBRATE_HINT;
}
}
// 如果不设置音量adjustVolume
if (!volumeAdjustmentAllowedByDnd(streamTypeAlias, flags)) {
adjustVolume = false;
}
int oldIndex = mStreamStates[streamType].getIndex(device);
if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) {
mAudioHandler.removeMessages(MSG_UNMUTE_STREAM);
// Check if volume update should be send to AVRCP
if (streamTypeAlias == AudioSystem.STREAM_MUSIC &&
(device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
(flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
synchronized (mA2dpAvrcpLock) {
if (mA2dp != null && mAvrcpAbsVolSupported) {
mA2dp.adjustAvrcpAbsoluteVolume(direction);
}
}
}
// 静音设置
if (isMuteAdjust) {
boolean state;
if (direction == AudioManager.ADJUST_TOGGLE_MUTE) {
state = !streamState.mIsMuted;
} else {
state = direction == AudioManager.ADJUST_MUTE;
}
if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
setSystemAudioMute(state);
}
for (int stream = 0; stream < mStreamStates.length; stream++) {
if (streamTypeAlias == mStreamVolumeAlias[stream]) {
if (!(readCameraSoundForced()
&& (mStreamStates[stream].getStreamType()
== AudioSystem.STREAM_SYSTEM_ENFORCED))) {
mStreamStates[stream].mute(state);
}
}
}
} else if ((direction == AudioManager.ADJUST_RAISE) &&
!checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
//安全模式,提示音量过高
mVolumeController.postDisplaySafeVolumeWarning(flags);
} else if (streamState.adjustIndex(direction * step, device, caller)
|| streamState.mIsMuted) {
if (streamState.mIsMuted) {
// Unmute the stream if it was previously muted
if (direction == AudioManager.ADJUST_RAISE) {
// unmute immediately for volume up
streamState.mute(false);
} else if (direction == AudioManager.ADJUST_LOWER) {
if (mIsSingleVolume) {
sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE,
streamTypeAlias, flags, null, UNMUTE_STREAM_DELAY);
}
}
} // 设置到底层
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
0);
}
// 是否需要设置HDMI的音量
int newIndex = mStreamStates[streamType].getIndex(device);
if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags);
}
if (mHdmiManager != null) {
synchronized (mHdmiManager) {
// mHdmiCecSink true => mHdmiPlaybackClient != null
if (mHdmiCecSink &&
streamTypeAlias == AudioSystem.STREAM_MUSIC &&
oldIndex != newIndex) {
synchronized (mHdmiPlaybackClient) {
int keyCode = (direction == -1) ? KeyEvent.KEYCODE_VOLUME_DOWN :
KeyEvent.KEYCODE_VOLUME_UP;
final long ident = Binder.clearCallingIdentity();
try {
mHdmiPlaybackClient.sendKeyEvent(keyCode, true);
mHdmiPlaybackClient.sendKeyEvent(keyCode, false);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
}
}
}
// 更新音量
int index = mStreamStates[streamType].getIndex(device);
sendVolumeUpdate(streamType, oldIndex, index, flags);
}
在上面这段代码中,需要注意下面几个关键的流程:
计算step
如果不是音乐类型,且固定音量,那么音量调节将转换,转换的算法为:
private int rescaleIndex(int index, int srcStream, int dstStream) {
return (index * mStreamStates[dstStream].getMaxIndex() + mStreamStates[srcStream].getMaxIndex() / 2) / mStreamStates[srcStream].getMaxIndex();
}
index为10,所以STREAM_NOTIFICATION的step为10,STREAM_DTMF的step为(10x15 + 15/2) / 15 = 5
处理RingMode
Android定义了如下的模式:
public static final int RINGER_MODE_SILENT = 0;
public static final int RINGER_MODE_VIBRATE = 1;
public static final int RINGER_MODE_NORMAL = 2;
public static final int RINGER_MODE_MAX = RINGER_MODE_NORMAL;
设置音量
首先处理静音,处理安全音量,最后再处理音量。
音量首先保存在每个流类型的VolumeStreamState中:
public boolean adjustIndex(int deltaIndex, int device, String caller) {
return setIndex(getIndex(device) + deltaIndex, device, caller);
}
在通过 MSG_SET_DEVICE_VOLUME 消息设置到底层。AudioHandler通过setDeviceVolume处理MSG_SET_DEVICE_VOLUME消息。最后是通过AudioSystem的native接口设置到native。
通知音量更改
最后,通过sendVolumeUpdate广播整个系统,通知整个系统音量修改。
看完代码,我们来看看Java层的时序吧~
AudioService设置按键音量的过程
android 音量 广播,Android平台音量调节(一)音量键调节音量相关推荐
- android 屏蔽 广播,Android中使用BroadcastReceiver打开和关闭WIFI
由于自动化测试需要,我们希望能够简单的控制Android手机的WIFI开和关,而不是通过UI操作的方式.由于每个Android机型的UI都千差万别,所以需要找到一个通用得方式来满足我们的需求. 最开始 ...
- android 受限广播,Android受限广播(protected
在阅读Android源码Telephony模块时,发现在AndroidManifest.xml文件里声明了大量的protected-broadcast: 'android.intent.action. ...
- android 定位 广播,android - 如何触发广播接收器在GPS开启/关闭? - SO中文参考 - www.soinside.com...
如何触发广播接收器在GPS开启/关闭? 问题描述 投票:35回答:5 public class BootReceiver extends BroadcastReceiver { @Override p ...
- android 卸载 广播,Android利用系统广播---监听应用程序安装和卸载
在android系统中,安装和卸载都会发送广播,当应用安装完成后系统会发android.intent.action.PACKAGE_ADDED广播.可以通过intent.getDataString() ...
- android 蓝牙 广播,android蓝牙BLE(三) —— 广播
在蓝牙开发中,有些情况是不需要连接的,只要外设广播自己的数据即可,例如苹果的ibeacon.自Android 5.0更新蓝牙API后,手机可以作为外设广播数据. 广播包有两种: 广播包(Adver ...
- android 驻留广播,Android实现Service永久驻留
说实话,这是一种流氓行为.但有些时候又是不得不需要的.比如微信的NotifyReceiver.现在抛开这些伦理的东西不讲,我们只是来看看技术上怎么实现.在后台运行的service有几个途径可以将其停止 ...
- android耳机广播,Android利用广播实现耳机的线控
[实例简介] 借助广播来实现单按钮耳机的线控,能够识别单击和双击 代码讲解在我的博客里: http://blog.csdn.net/illidantao/article/details/1684790 ...
- Android驻留广播,Android实现Service永久驻留
说实话,这是一种流氓行为.但有些时候又是不得不需要的.比如微信的NotifyReceiver.现在抛开这些伦理的东西不讲,我们只是来看看技术上怎么实现.在后台运行的service有几个途径可以将其停止 ...
- android音量键调节听筒音量的大小
android音量键调节听筒音量的大小 最近发现微信的语音功能可以在听筒和喇叭间互相切换并且可以使用音量按键进行调节,之前在项目开发中只用到音频的多媒体类型播放音频文件但没用到听筒,所以就写了个dem ...
最新文章
- 入门学习webpack笔记
- 云服务器 ECS 建站教程:创建基于ECS和RDS的WordPress环境
- Equalize the Remainders(set二分+思维)
- 一个小白的转行Python的经历!
- less编译工具koala(考拉)和rem的使用
- php gpg,使用 gpg 验证 php
- ie不加载jre_国内银行为兼容XP/IE6 竟然篡改IE安全协议把所有用户拖下水
- 【鲲鹏HCIA考试】随堂习题卷六
- web开发必备的几个软件
- 迅雷 故意限速_故意记录的价值
- html播放监控视频教程,使用javascript实现监控视频播放并打印日志
- python怎么导入大小字母_python遍历小写英文字母的方法
- Mac 显示隐藏文件夹
- 木瓜从林。。。。。。。。。。。。。。。。。
- 网络与分布式计算复习
- ZGC是如何工作的?
- 各种音视频编解码学习详解 h264 ,mpeg4 ,aac 等所有音视频格式
- @支付宝@微信支付,世界第一要来和你们抢生意了!
- 绿色数据中心空调设计 书评_书评:负责任的响应式设计
- 第二届 IstioCon 演讲议题正在征集中
热门文章
- common injuries and treatment
- nodes are available: 1 node(s) had taints that the pod didn‘t tolerate
- 中学计算机课外小组活动计划,课外活动计划实施方案
- 64位linux安装adobe flash play插件
- 【汇编笔记】win10如何搭建汇编环境(dosbox)
- 关于EXE文件关联的.
- 修正获取BSSID和SSID的代码
- Ue4 TEXTURE STREAMING POOL OVER ****MiB BUDGET 问题解决
- 冷月手撕408之计算机组成原理(1)-导学
- MATLAB||清除指令clear,clear all,clc,clf,cla