Android原生音量控制
本文主要涉及AudioService。还是基于5.1.1版本的代码。
AudioService.java文件位于/framework/base/media/java/android/media/下。
音量控制是AudioService最重要的功能之一。先总结一下:
- AudioService音量管理的核心是VolumeStreamState。它保存了一个流类型所有的音量信息。
- VolumeStreamState保存了运行时的音量信息,而音量的生效则是在底层AudioFlinger完成的。所以进行音量设置需要做两件事情:更新VolumeStreamState存储的音量值,设置音量到Audio底层系统。
- VolumeDeathHandler是VolumeStreamState的一个内部类。它的实例对应在一个流类型上执行静音操作的一个客户端,是实现静音功能的核心对象。
音量定义
Andorid5.1在AudioSystem.java定义了有10种流类型。每种流类型的音量都是相互独立的,Android也在AudioService.java定义了几个数组:MAX_STREAM_VOLUME(最大音量),DEFAULT_STREAM_VOLUME(默认音量大小),STREAM_VOLUME_ALIAS_VOICE(映射的流类型)。
虽然Android5.1中拥有10种流类型,但是为了便于使用,android通过判断设备的类型,去映射具体流类型。Android5.1在AudioSystem.java中提供了3个设备(DEFAULT,VOICE,TELEVISION)作为可选择项,分别去映射我们具体的音频流类型。其中,DEFAULT和VOICE类型的音频映射是一致的。
所以,从上表中可以看出,在手机设备当中,我们当前可调控的流类型音量其实只有5个,当你想调节STREAM_SYSTEM,STREAM_NOTIFICATION等流类型的音量时,实际上是调节了STREAM_RING的音量。当前可控的流类型可以通过下表更直观地显示:
音量键处理流程
- 音量键处理流程的发起者是PhoneWindow。
- AudioManager仅仅起到代理的作用。
- AudioService接受AudioManager的调用请求,操作VolumeStreamState的实例进行音量的设置。
- VolumeStreamState负责保存音量设置,并且提供了将音量设置到底层的方法。
- AudioService负责将设置结果以广播的形式通知外界。
先看到PhoneWindow的onKeyDown(int featureId, int keyCode, KeyEvent event)方法:
...
switch (keyCode) {case KeyEvent.KEYCODE_VOLUME_UP:case KeyEvent.KEYCODE_VOLUME_DOWN: {int direction = keyCode == KeyEvent.KEYCODE_VOLUME_UP ? AudioManager.ADJUST_RAISE: AudioManager.ADJUST_LOWER;// If we have a session send it the volume command, otherwise// use the suggested stream.if (mMediaController != null) {mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);} else {MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(mVolumeControlStreamType, direction,AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE);}return true;}...
keycode用来判断音量增减,AudioManager.ADJUST_RAISE 表示增大,ADJUST_LOWER表示降低。
这里是会走到else的MediaSessionLegacyHelper,继续跟踪MediaSessionLegacyHelper类:
public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) {mSessionManager.dispatchAdjustVolume(suggestedStream, delta, flags);if (DEBUG) {Log.d(TAG, "dispatched volume adjustment");}}
比较简单,直接调用了MediaSessionManager类的dispatchAdjustVolume()方法,继而会发现dispatchAdjustVolume()里面只是调用了MediaSessionService的dispatchAdjustVolume()方法,所以看到MediaSessionService类,发现最终会调用到AudioService类的adjustSuggestedStreamVolume(direction, suggestedStream,flags, packageName);
看到AudioService的adjustSuggestedStreamVolume()方法。
第一个参数direction指示了音量的调整方向,1为增大,-1为减小;第二个参数suggestedStreamType表示要求调整音量的流类型;第三个参数flags,其实是在AudioManager在handleKeyDown()中设置了两个flags,分别是FLAG_SHOW_UI和FLAG_VIBRATE。前者告诉AudioService需要弹出一个音量控制面板。而在handleKeyUp()里设置了FLAG_PLAY_SOUND,这是为什么在松开音量键后”有时候“(在特定的流类型下,且没有处于锁屏状态)会有一个提示音。
// 1.确定要调整音量的流类型 2.在某些情况下屏蔽FLAG_PLAY_SOUND 3.调用adjustStreamVolume()private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, String callingPackage, int uid) {......//从这一小段代码中可以看出,在AudioService中还有地方可以强行改变音量键控制的流类型。//mVolumeControlStream是VolumePanel通过forceVolumeControlStream()设置的,//VolumePanel显示时会调用forceVolumeControlStream强制后续的音量键操作固定为促使它显示的那个流类型,//并在它关闭时取消这个强制设置,设值为-1if (mVolumeControlStream != -1) { streamType = mVolumeControlStream;} else {//通过getActiveStreamType()函数获取要控制的流类型,这里根据建议的流类型与AudioService的实际情况,返回一个值streamType = getActiveStreamType(suggestedStreamType);}final int resolvedStream = mStreamVolumeAlias[streamType];......adjustStreamVolume(streamType, direction, flags, callingPackage, uid);}
接着看看adjustStreamVolume()
private void adjustStreamVolume(int streamType, int direction, int flags,String callingPackage, int uid) {......ensureValidDirection(direction); //确认一下调整的音量方向ensureValidStreamType(streamType); //确认一下调整的流类型 int streamTypeAlias = mStreamVolumeAlias[streamType];//获取streamType映射到的流类型//VolumeStreamState类,保存与一个流类型所有音量相关的信息VolumeStreamState streamState = mStreamStates[streamTypeAlias];final int device = getDeviceForStream(streamTypeAlias);int aliasIndex = streamState.getIndex(device);//获取当前音量......//rescaleIndex用于将音量值的变化量从源流类型变换到目标流类型下,//由于不同的流类型的音量调节范围不同,所以这个转换是必需的step = rescaleIndex(10, streamType, streamTypeAlias);}......final int result = checkForRingerModeChange(aliasIndex, direction, step);adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0; //布尔变量,用来表示是否有必要继续设置音量值......int oldIndex = mStreamStates[streamType].getIndex(device);//取出调整前的音量值。这个值会在sendVolumeUpdate()调用if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) {......if ((direction == AudioManager.ADJUST_RAISE) &&!checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {Log.e(TAG, "adjustStreamVolume() safe volume index = "+oldIndex);mVolumeController.postDisplaySafeVolumeWarning(flags);//判断streamState.adjustIndex返回值,如果音量值在调整之后并没有发生变化,比如到了最大值,就不需要继续后面的操作了} else if (streamState.adjustIndex(direction * step, device)) {//这个消息将把音量设置到底层去,并将其存储到Settingsprovider中sendMsg(mAudioHandler,MSG_SET_DEVICE_VOLUME,SENDMSG_QUEUE,device,0,streamState,0);}......int index = mStreamStates[streamType].getIndex(device);sendVolumeUpdate(streamType, oldIndex, index, flags);// 通知外界音量值发生了变化}
总结一下这个函数:
- 计算按下音量键的音量步进值。这个步进值是10而不是1。在VolumeStreamState中保存的音量值是其实际值的10倍,这是为了在不同流类型之间进行音量转化时能够保证一定精度的一种实现。可以理解为在转化过程中保留了小数点后一位的精度。
- 检查是否需要改变情景模式。checkForRingerModeChange()和情景模式有关。
- 调用adjustIndex()更改VolumeStreamState对象中保存的音量值。
- 通过sendMsg()发送消息MSG_SET_DEVICE_VOLUME到mAudioHandler。
- 调用sendVolumeUpdate()函数,通知外界音量值发生了变化。
下面将分析adjustIndex()、MSG_SET_DEVICE_VOLUME消息的处理和sendVolumeUpdate()。
先看到VolumeStreamState类的adjustIndex()
//更改VolumeStreamState对象中保存的音量值public boolean adjustIndex(int deltaIndex, int device) {return setIndex(getIndex(device) + deltaIndex, device);// 将现有的音量值加上变化量,然后调用setIndex进行设置}public boolean setIndex(int index, int device) {......mIndex.put(device, index);//保存设置的音量值if (oldIndex != index) {//同时设置所有映射到当前流类型的其他流的音量boolean currentDevice = (device == getDeviceForStream(mStreamType));int numStreamTypes = AudioSystem.getNumStreamTypes();for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {if (streamType != mStreamType &&mStreamVolumeAlias[streamType] == mStreamType) {int scaledIndex = rescaleIndex(index, mStreamType, streamType);mStreamStates[streamType].setIndex(scaledIndex,device);if (currentDevice) {mStreamStates[streamType].setIndex(scaledIndex,getDeviceForStream(streamType));}}}return true;} else {return false;}}}
可以看出,VolumeStreamState.adjustIndex()除了更新自己所保存的音量值外,没有做其他的事情。接下来看看MSG_SET_DEVICE_VOLUME消息处理做了什么。
case MSG_SET_DEVICE_VOLUME:setDeviceVolume((VolumeStreamState) msg.obj, msg.arg1);break;
private void setDeviceVolume(VolumeStreamState streamState, int device) {synchronized (VolumeStreamState.class) { streamState.applyDeviceVolume_syncVSS(device);//这个函数会调用AudioSystem.setStreamVolumeIndex(),//到这,音量就被设置到底层的AudioFlinger中// 对所有流应用更改,使用此别名作为别名。处理流音量映射的情况int numStreamTypes = AudioSystem.getNumStreamTypes();for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {......}//发送消息,其处理函数将会调用persitVolume()函数,这将会把音量的设置信息存储到SettingsProvide中。//Audioservice在初始化时,将会从SettingsProvide中将音量设置读取出来并进行设置sendMsg(mAudioHandler,MSG_PERSIST_VOLUME,SENDMSG_QUEUE,device,0,streamState,PERSIST_DELAY);}
最后看到sendVolumeUpdate()
// UI update and Broadcast Intentprivate void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) {//判断设备是否拥有通话功能。对没有通话能力的设备来说,RING流类型自然也就没有意义了。这句话应该算是一种从语义操作上进行的保护if (!isPlatformVoice() && (streamType == AudioSystem.STREAM_RING)) { streamType = AudioSystem.STREAM_NOTIFICATION; }if (streamType == AudioSystem.STREAM_MUSIC) {flags = updateFlagsForSystemAudio(flags);}mVolumeController.postVolumeChanged(streamType, flags);//最后将显示系统音量条的提示框if ((flags & AudioManager.FLAG_FIXED_VOLUME) == 0) {oldIndex = (oldIndex + 5) / 10; //+5的意义是实现四舍五入;除以10是因为存储时先乘了10,转换过程中保留小数点后一位的精度index = (index + 5) / 10;Intent intent = new Intent(AudioManager.VOLUME_CHANGED_ACTION);intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType);intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);intent.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex);sendBroadcastToAll(intent);}}
mVolumeController.postVolumeChanged()方法将会调用到mController.volumeChanged()方法,通过AIDL将调用到VolumeUI.java文件中的VolumeController.volumeChanged()方法,最后将会调用mPanel.postVolumeChanged更新系统音量条的UI,这里就是VolumePanel的内容啦,具体可看上一篇文章系统音量条
通过音量设置函数setStreamVolume()
除了音量键调节音量以外,还可以通过系统设置中进行调节。
控件会根据当初的音量和模式去调用AudioManager的adjustStreamVolume(静音或震动模式)或setStreamVolume(普通模式)去调整相对应的音量。
AudioManager.setStreamVolume()是系统设置界面中调整音量所使用的接口。
private void setStreamVolume(int streamType, int index, int flags, String callingPackage,int uid) {......ensureValidStreamType(streamType);//先判断一下流类型这个参数的有效性int streamTypeAlias = mStreamVolumeAlias[streamType];//对这个数组进行流类型的转换VolumeStreamState streamState = mStreamStates[streamTypeAlias];final int device = getDeviceForStream(streamType);//获取当前流将使用哪一个音频设备进行播放。最终会被调用到AudioPolicyService中......oldIndex = streamState.getIndex(device);//获取当前流的音量index = rescaleIndex(index * 10, streamType, streamTypeAlias);//将原流类型下的音量值映射到目标流类型下的音量值......if (!checkSafeMediaVolume(streamTypeAlias, index, device)) {mVolumeController.postDisplaySafeVolumeWarning(flags);mPendingVolumeCommand = new StreamVolumeCommand(streamType, index, flags, device);} else {onSetStreamVolume(streamType, index, flags, device);//将调用setStreamVolumeInt()方法index = mStreamStates[streamType].getIndex(device);//获取设置结果}}sendVolumeUpdate(streamType, oldIndex, index, flags);//通知外界音量发生了变化}
onSetStreamVolume()方法主要就是调用了setStreamVolumeInt()方法,下面看下setStreamVolumeInt()
private void setStreamVolumeInt(int streamType, int index, int device, boolean force) {VolumeStreamState streamState = mStreamStates[streamType];if (streamState.setIndex(index, device) || force) { //调用streamState.setIndex(),更改VolumeStreamState对象中保存的音量值//这个消息将把音量设置到底层去,并将其存储到Settingsprovider中sendMsg(mAudioHandler,MSG_SET_DEVICE_VOLUME,SENDMSG_QUEUE,device,0,streamState,0);}}
仔细一看,会发现这与上面音量键控制音量的adjustStreamVolume()函数的代码很类似,主要都是调用了那几个方法。
后期补充
最近发现开机的时候,系统会设置2次音量!为什么要设置两次呢?主要是因为出厂设置后数据库SettingsProvider里面没有内容。其实SettingsProvider里面保存的音量只有开机的时候才会读取一次,之后获取音量值是直接从底层DSP中获取的,并不是去读取SettingsProvider存储的音量值。
第一次是初始化音量,音量值从AudioService的DEFAULT_STREAM_VOLUME数组中读取,并设置下去到DSP。第一次的设置音量的主要流程看下面log。
01-01 08:00:09.444 1976 2238 V AudioPolicyManager: initStreamVolume() stream 2, min 0, max 7
......
01-01 08:00:09.650 1976 1976 V AudioPolicyManager: setStreamVolumeIndex() stream 2, device 40000000, index 5, call 1990
01-01 08:00:09.654 1561 1561 W ti_hwc : Should have assigned z-layer for fb
01-01 08:00:09.658 1976 1976 D AudioPolicyManager: #00 pc 0000cff1 /system/lib/libutils.so (android::CallStack::update(int, int)+52)
01-01 08:00:09.658 1976 1976 D AudioPolicyManager: #01 pc 0000d107 /system/lib/libutils.so (android::CallStack::CallStack(char const*, int)+38)
01-01 08:00:09.658 1976 1976 D AudioPolicyManager: #02 pc 0001683d /system/lib/libaudiopolicymanagerdefault.so (android::AudioPolicyManager::setStreamVolumeIndex(audio_stream_type_t, int, unsigned int)+128)
01-01 08:00:09.658 1976 1976 D AudioPolicyManager: #03 pc 0000911f /system/lib/libaudiopolicyservice.so
01-01 08:00:09.658 1976 1976 D AudioPolicyManager: #04 pc 0006de93 /system/lib/libmedia.so (android::BnAudioPolicyService::onTransact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+186)
01-01 08:00:09.658 1976 1976 D AudioPolicyManager: #05 pc 0001a6fd /system/lib/libbinder.so (android::BBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+60)
01-01 08:00:09.658 1976 1976 D AudioPolicyManager: #06 pc 0001f7ab /system/lib/libbinder.so (android::IPCThreadState::executeCommand(int)+582)
01-01 08:00:09.658 1976 1976 D AudioPolicyManager: #07 pc 0001f8cf /system/lib/libbinder.so (android::IPCThreadState::getAndExecuteCommand()+38)
01-01 08:00:09.658 1976 1976 D AudioPolicyManager: #08 pc 0001f911 /system/lib/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+48)
01-01 08:00:09.658 1976 1976 D AudioPolicyManager: #09 pc 00001645 /system/bin/mediaserver
01-01 08:00:09.658 1976 1976 D AudioPolicyManager: #10 pc 00012dfd /system/lib/libc.so (__libc_init+44)
01-01 08:00:09.658 1976 1976 D AudioPolicyManager: #11 pc 000018b0 /system/bin/mediaserver
01-01 08:00:09.659 1561 1561 E ti_hwc : drmModeSetPlane error: (2)
01-01 08:00:09.659 1990 1990 W System.err: java.lang.Throwable
01-01 08:00:09.660 1990 1990 W System.err: at android.media.AudioService$VolumeStreamState.applyAllVolumes(AudioService.java:3636)
01-01 08:00:09.660 1990 1990 W System.err: at android.media.AudioService.checkAllAliasStreamVolumes(AudioService.java:809)
01-01 08:00:09.660 1990 1990 W System.err: at android.media.AudioService.createStreamStates(AudioService.java:836)
01-01 08:00:09.660 1990 1990 W System.err: at android.media.AudioService.<init>(AudioService.java:670)
01-01 08:00:09.660 1990 1990 W System.err: at com.android.server.SystemServer.startServiceAudio(SystemServer.java:1301)
01-01 08:00:09.660 1990 1990 W System.err: at com.android.server.SystemServerBolt.startServiceAudio(SystemServerBolt.java:177)
01-01 08:00:09.660 1990 1990 W System.err: at com.android.server.SystemServer.startOtherServices(SystemServer.java:582)
01-01 08:00:09.660 1990 1990 W System.err: at com.android.server.SystemServer.run(SystemServer.java:302)
01-01 08:00:09.660 1990 1990 W System.err: at com.android.server.SystemServer.main(SystemServer.java:212)
01-01 08:00:09.660 1990 1990 W System.err: at java.lang.reflect.Method.invoke(Native Method)
01-01 08:00:09.660 1990 1990 W System.err: at java.lang.reflect.Method.invoke(Method.java:372)
01-01 08:00:09.660 1990 1990 W System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1249)
01-01 08:00:09.660 1990 1990 W System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1044)
第二次设置音量是从数据库SettingsProvider中读取关机前保存的音量值,并设置到DSP。主要流程看下面log,主要是AudioServiceBroadcastReceiver收到一个MSG_SET_ALL_VOLUMES广播。
01-01 08:00:13.826 1976 2238 V AudioPolicyManager: setStreamVolumeIndex() stream 2, device 40000000, index 5, call 1990
01-01 08:00:13.834 1976 2238 D AudioPolicyManager: #00 pc 0000cff1 /system/lib/libutils.so (android::CallStack::update(int, int)+52)
01-01 08:00:13.834 1976 2238 D AudioPolicyManager: #01 pc 0000d107 /system/lib/libutils.so (android::CallStack::CallStack(char const*, int)+38)
01-01 08:00:13.834 1976 2238 D AudioPolicyManager: #02 pc 0001683d /system/lib/libaudiopolicymanagerdefault.so (android::AudioPolicyManager::setStreamVolumeIndex(audio_stream_type_t, int, unsigned int)+128)
01-01 08:00:13.834 1976 2238 D AudioPolicyManager: #03 pc 0000911f /system/lib/libaudiopolicyservice.so
01-01 08:00:13.834 1976 2238 D AudioPolicyManager: #04 pc 0006de93 /system/lib/libmedia.so (android::BnAudioPolicyService::onTransact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+186)
01-01 08:00:13.834 1976 2238 D AudioPolicyManager: #05 pc 0001a6fd /system/lib/libbinder.so (android::BBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+60)
01-01 08:00:13.834 1976 2238 D AudioPolicyManager: #06 pc 0001f7ab /system/lib/libbinder.so (android::IPCThreadState::executeCommand(int)+582)
01-01 08:00:13.834 1976 2238 D AudioPolicyManager: #07 pc 0001f8cf /system/lib/libbinder.so (android::IPCThreadState::getAndExecuteCommand()+38)
01-01 08:00:13.834 1976 2238 D AudioPolicyManager: #08 pc 0001f911 /system/lib/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+48)
01-01 08:00:13.834 1976 2238 D AudioPolicyManager: #09 pc 00023a93 /system/lib/libbinder.so
01-01 08:00:13.834 1976 2238 D AudioPolicyManager: #10 pc 00010519 /system/lib/libutils.so (android::Thread::_threadLoop(void*)+112)
01-01 08:00:13.834 1976 2238 D AudioPolicyManager: #11 pc 00010089 /system/lib/libutils.so
01-01 08:00:13.834 1976 2238 D AudioPolicyManager: #12 pc 00016fdf /system/lib/libc.so (__pthread_start(void*)+30)
01-01 08:00:13.834 1976 2238 D AudioPolicyManager: #13 pc 00014f23 /system/lib/libc.so (__start_thread+6)
01-01 08:00:13.835 1976 2233 D AudioFlinger: OS:MSG:virtual ssize_t android::AudioFlinger::PlaybackThread::threadLoop_write():16000
01-01 08:00:13.835 1976 2233 D AudioFlinger: OS:MSG:virtual android::AudioFlinger::PlaybackThread::mixer_state android::AudioFlinger::MixerThread::prepareTracks_l(android::Vector<android::sp<android::AudioFlinger::PlaybackThread::Track> >*): sampleRate=16000
01-01 08:00:13.835 1976 2233 D AudioFlinger: track 4096 s=0000c100 [OK] on thread 0xb54cb000
01-01 08:00:13.835 2088 2386 D AudioTrack: [AudioTrack][1352] obtainBuffer, proxy->obtainBuffer after, tryCounter = 5, status = 0
01-01 08:00:13.835 2088 2386 D AudioTrack: [AudioTrack][1350] obtainBuffer, proxy->obtainBuffer before
01-01 08:00:13.835 2088 2386 D AudioTrack: [AudioTrack][1352] obtainBuffer, proxy->obtainBuffer after, tryCounter = 5, status = 0
01-01 08:00:13.835 2088 2386 D AudioTrack: [AudioTrack][1350] obtainBuffer, proxy->obtainBuffer before
01-01 08:00:13.837 1990 1990 W System.err: java.lang.Throwable
01-01 08:00:13.837 1990 1990 W System.err: at android.media.AudioService$VolumeStreamState.applyAllVolumes(AudioService.java:3636)
01-01 08:00:13.837 1990 1990 W System.err: at android.media.AudioService.checkAllAliasStreamVolumes(AudioService.java:809)
01-01 08:00:13.837 1990 1990 W System.err: at android.media.AudioService.readAudioSettings(AudioService.java:2508)
01-01 08:00:13.837 1990 1990 W System.err: at android.media.AudioService.access$10000(AudioService.java:116)
01-01 08:00:13.837 1990 1990 W System.err: at android.media.AudioService$AudioServiceBroadcastReceiver.onReceive(AudioService.java:5209)
01-01 08:00:13.837 1990 1990 W System.err: at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:866)
01-01 08:00:13.837 1990 1990 W System.err: at android.os.Handler.handleCallback(Handler.java:739)
01-01 08:00:13.837 1990 1990 W System.err: at android.os.Handler.dispatchMessage(Handler.java:95)
01-01 08:00:13.837 1990 1990 W System.err: at android.os.Looper.loop(Looper.java:135)
01-01 08:00:13.837 1990 1990 W System.err: at com.android.server.SystemServer.run(SystemServer.java:315)
01-01 08:00:13.837 1990 1990 W System.err: at com.android.server.SystemServer.main(SystemServer.java:212)
01-01 08:00:13.837 1990 1990 W System.err: at java.lang.reflect.Method.invoke(Native Method)
01-01 08:00:13.837 1990 1990 W System.err: at java.lang.reflect.Method.invoke(Method.java:372)
01-01 08:00:13.837 1990 1990 W System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1249)
01-01 08:00:13.837 1990 1990 W System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1044)
参考文章
好文推荐
Android原生音量控制相关推荐
- 符号执行android,基于符号执行的android原生代码控制流图提取方法symbolic.pdf
基于符号执行的android原生代码控制流图提取方法symbolic 第3 卷第7 期 网络与信息安全学报 Vol.3 No.7 2017 年7 月 Chinese Journal of Networ ...
- android 声卡 音量控制,android audio 音量调节
这次的分析是从setting设置开始,进入声音设置,然后进入音量设置! 先上传上来,后期进行整理吧 调用流程: -------------------------------------------- ...
- android 原生音量条
android6.0原生音量条 按音量键 展开音量条后
- Android音量控制曲线
摘要:本文介绍了android音量的控制曲线的计算方法. 由于人耳对声音的听感具指数曲线型,也就是对小音量时比较敏感,随着声音的加大其听感随之变的不敏感,其变化近似指数函数曲线的形式.为了使听感变的近 ...
- Android应用--简、美音乐播放器增加音量控制
Android应用--简.美音乐播放器增加音量控制 2013年6月26日简.美音乐播放器继续完善中.. 题外话:上一篇博客是在6月11号发的,那篇博客似乎有点问题,可能是因为代码结构有点乱的原因,很难 ...
- 音效管理android,Android之声音管理器《AudioManager》的使用以及音量控制
以下为网上下载然后拼接-- Android声音管理AudioManager使用 手机都有声音模式,声音.静音还有震动,甚至震动加声音兼备,这些都是手机的基本功能.在Android手机中,我们同样可以通 ...
- Android 音量控制流程分析
在Android平台上,音量键,主页键(home),都是全局按键,但是主页键是个例外不能被应用所捕获.下面分析一下音量按键的流程,主要从framework层处理开始,至于 EventHub 从驱动的/ ...
- Android音量控制
0. Thanks To Android音量控制调节 android 音量控制setVolumeControlStream android 音量调节以及媒体音量界面 1. 音量调节 我们知道,在平常调 ...
- Android系统的音量控制
Android系统的音量控制 效果图: GitHub GitHub(源码):https://github.com/kongqw/VolumeController 方法 获取AudioManager m ...
- Android之声音管理器《AudioManager》的使用以及音量控制
以下为网上下载然后拼接-- Android声音管理AudioManager使用 手机都有声音模式,声音.静音还有震动,甚至震动加声音兼备,这些都是手机的基本功能.在Android手机中,我们同样可以通 ...
最新文章
- c语言的32个关键词
- 总线协议之I2C总线时序
- [Spring5]IOC容器_Bean管理注解方式_完全注解开发
- Java 中接口 interface 实例介绍
- 为什么都建议学java而不是python-为什么入门大数据选择Python而不是Java?
- 艾肯声卡没有声音处理方法
- 2021年12月最新大数据白皮书(附下载)
- Linux 环境下思源黑体字体与 Java 之间的兼容性问题的解决(补充说明)
- SQL 连接嵌套查询实验报告
- 毕业5年决定你的一生
- PowerApps 中的 Filter、Search 和 LookUp 函数
- composer 升级/降级安装包
- 3. Caller 服务调用 - dapr
- android手机文件快速扫描,并归类
- WRF嵌套网格的设计
- python 身份证号码有效性验证
- 2018秋c语言程序设计考试答案,2018年自学考试《C语言程序设计》模拟试题【四篇】...
- 品Spring:能工巧匠们对注解的“加持”
- 世界历史———俄国历史
- 成型滤波器设计matlab,MATLAB+VHDL脉冲成型滤波器的设计 附代码
热门文章
- 卫星导航定位误差之多路径地球自转相位缠绕相位中心误差地球潮汐
- coji小机器人_WowWee COJI 可编程机器人玩具——也许是我想多了
- DDPM代码详细解读(1):数据集准备、超参数设置、loss设计、关键参数计算
- 离谱!程序员业余时间开发的项目,版权也属于公司的?
- 机器学习笔记—模式识别与智能计算(一)模式识别概述
- 计算机网络:常见的网络拓扑结构
- 清华大学四连冠,南科大获得最高性能奖!国际大学生超算竞赛SC21结果出炉
- 计算机变量与变量地址,数据缓冲区与变量的地址(更新1)
- 贴片电阻各种封装规格及阻值标注方法
- 嵌入式开发:调试嵌入式软件的技巧