亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android原生音量控制實(shí)例詳解

 更新時間:2018年02月09日 09:50:02   作者:他叫小黑  
這篇文章主要介紹了Android原生音量控制實(shí)例詳解,分享了相關(guān)代碼示例,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下

本文主要涉及AudioService。還是基于5.1.1版本的代碼。

AudioService.java文件位于/framework/base/media/java/android/media/下。

音量控制是AudioService最重要的功能之一。先總結(jié)一下:

  1. AudioService音量管理的核心是VolumeStreamState。它保存了一個流類型所有的音量信息。
  2. VolumeStreamState保存了運(yùn)行時的音量信息,而音量的生效則是在底層AudioFlinger完成的。所以進(jìn)行音量設(shè)置需要做兩件事情:更新VolumeStreamState存儲的音量值,設(shè)置音量到Audio底層系統(tǒng)。
  3. VolumeDeathHandler是VolumeStreamState的一個內(nèi)部類。它的實(shí)例對應(yīng)在一個流類型上執(zhí)行靜音操作的一個客戶端,是實(shí)現(xiàn)靜音功能的核心對象。

音量定義

Andorid5.1在AudioSystem.java定義了有10種流類型。每種流類型的音量都是相互獨(dú)立的,Android也在AudioService.java定義了幾個數(shù)組:MAX_STREAM_VOLUME(最大音量),DEFAULT_STREAM_VOLUME(默認(rèn)音量大?。琒TREAM_VOLUME_ALIAS_VOICE(映射的流類型)。

雖然Android5.1中擁有10種流類型,但是為了便于使用,android通過判斷設(shè)備的類型,去映射具體流類型。Android5.1在AudioSystem.java中提供了3個設(shè)備(DEFAULT,VOICE,TELEVISION)作為可選擇項(xiàng),分別去映射我們具體的音頻流類型。其中,DEFAULT和VOICE類型的音頻映射是一致的。

所以,從上表中可以看出,在手機(jī)設(shè)備當(dāng)中,我們當(dāng)前可調(diào)控的流類型音量其實(shí)只有5個,當(dāng)你想調(diào)節(jié)STREAM_SYSTEM,STREAM_NOTIFICATION等流類型的音量時,實(shí)際上是調(diào)節(jié)了STREAM_RING的音量。當(dāng)前可控的流類型可以通過下表更直觀地顯示:

音量鍵處理流程

  • 音量鍵處理流程的發(fā)起者是PhoneWindow。
  • AudioManager僅僅起到代理的作用。
  • AudioService接受AudioManager的調(diào)用請求,操作VolumeStreamState的實(shí)例進(jìn)行音量的設(shè)置。
  • VolumeStreamState負(fù)責(zé)保存音量設(shè)置,并且提供了將音量設(shè)置到底層的方法。
  • AudioService負(fù)責(zé)將設(shè)置結(jié)果以廣播的形式通知外界。

先看到AudioService的adjustSuggestedStreamVolume()方法。

第一個參數(shù)direction指示了音量的調(diào)整方向,1為增大,-1為減小;第二個參數(shù)suggestedStreamType表示要求調(diào)整音量的流類型;第三個參數(shù)flags,其實(shí)是在AudioManager在handleKeyDown()中設(shè)置了兩個flags,分別是FLAG_SHOW_UI和FLAG_VIBRATE。前者告訴AudioService需要彈出一個音量控制面板。而在handleKeyUp()里設(shè)置了FLAG_PLAY_SOUND,這是為什么在松開音量鍵后”有時候“(在特定的流類型下,且沒有處于鎖屏狀態(tài))會有一個提示音。

  // 1.確定要調(diào)整音量的流類型 2.在某些情況下屏蔽FLAG_PLAY_SOUND 3.調(diào)用adjustStreamVolume()
  private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, String callingPackage, int uid) {
    ......
    //從這一小段代碼中可以看出,在AudioService中還有地方可以強(qiáng)行改變音量鍵控制的流類型。
    //mVolumeControlStream是VolumePanel通過forceVolumeControlStream()設(shè)置的,
    //VolumePanel顯示時會調(diào)用forceVolumeControlStream強(qiáng)制后續(xù)的音量鍵操作固定為促使它顯示的那個流類型,
    //并在它關(guān)閉時取消這個強(qiáng)制設(shè)置,設(shè)值為-1
    if (mVolumeControlStream != -1) { 
      streamType = mVolumeControlStream;
    } else {
      //通過getActiveStreamType()函數(shù)獲取要控制的流類型,這里根據(jù)建議的流類型與AudioService的實(shí)際情況,返回一個值
      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); //確認(rèn)一下調(diào)整的音量方向
    ensureValidStreamType(streamType); //確認(rèn)一下調(diào)整的流類型 
    int streamTypeAlias = mStreamVolumeAlias[streamType];//獲取streamType映射到的流類型
    //VolumeStreamState類,保存與一個流類型所有音量相關(guān)的信息
    VolumeStreamState streamState = mStreamStates[streamTypeAlias];
    final int device = getDeviceForStream(streamTypeAlias);
    int aliasIndex = streamState.getIndex(device);//獲取當(dāng)前音量
    ......

      //rescaleIndex用于將音量值的變化量從源流類型變換到目標(biāo)流類型下,
      //由于不同的流類型的音量調(diào)節(jié)范圍不同,所以這個轉(zhuǎn)換是必需的
      step = rescaleIndex(10, streamType, streamTypeAlias);
    }
    ......

      final int result = checkForRingerModeChange(aliasIndex, direction, step);
      adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0; //布爾變量,用來表示是否有必要繼續(xù)設(shè)置音量值
    ......

    int oldIndex = mStreamStates[streamType].getIndex(device);//取出調(diào)整前的音量值。這個值會在sendVolumeUpdate()調(diào)用

    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返回值,如果音量值在調(diào)整之后并沒有發(fā)生變化,比如到了最大值,就不需要繼續(xù)后面的操作了
      } else if (streamState.adjustIndex(direction * step, device)) {
        //這個消息將把音量設(shè)置到底層去,并將其存儲到Settingsprovider中
        sendMsg(mAudioHandler,
            MSG_SET_DEVICE_VOLUME,
            SENDMSG_QUEUE,
            device,
            0,
            streamState,
            0);
      }
    ......

    int index = mStreamStates[streamType].getIndex(device);
    sendVolumeUpdate(streamType, oldIndex, index, flags);// 通知外界音量值發(fā)生了變化
  }

總結(jié)一下這個函數(shù):

  • 計(jì)算按下音量鍵的音量步進(jìn)值。這個步進(jìn)值是10而不是1。在VolumeStreamState中保存的音量值是其實(shí)際值的10倍,這是為了在不同流類型之間進(jìn)行音量轉(zhuǎn)化時能夠保證一定精度的一種實(shí)現(xiàn)??梢岳斫鉃樵谵D(zhuǎn)化過程中保留了小數(shù)點(diǎn)后一位的精度。
  • 檢查是否需要改變情景模式。checkForRingerModeChange()和情景模式有關(guān)。
  • 調(diào)用adjustIndex()更改VolumeStreamState對象中保存的音量值。
  • 通過sendMsg()發(fā)送消息MSG_SET_DEVICE_VOLUME到mAudioHandler。
  • 調(diào)用sendVolumeUpdate()函數(shù),通知外界音量值發(fā)生了變化。

下面將分析adjustIndex()、MSG_SET_DEVICE_VOLUME消息的處理和sendVolumeUpdate()。

先看到VolumeStreamState類的adjustIndex()

    //更改VolumeStreamState對象中保存的音量值
    public boolean adjustIndex(int deltaIndex, int device) {
      return setIndex(getIndex(device) + deltaIndex, device);// 將現(xiàn)有的音量值加上變化量,然后調(diào)用setIndex進(jìn)行設(shè)置
    }

    public boolean setIndex(int index, int device) {
    ......

        mIndex.put(device, index);//保存設(shè)置的音量值

        if (oldIndex != index) {
          //同時設(shè)置所有映射到當(dāng)前流類型的其他流的音量
          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);//這個函數(shù)會調(diào)用AudioSystem.setStreamVolumeIndex(),
        //到這,音量就被設(shè)置到底層的AudioFlinger中

        // 對所有流應(yīng)用更改,使用此別名作為別名。處理流音量映射的情況
        int numStreamTypes = AudioSystem.getNumStreamTypes();
        for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
          ......

      }
      //發(fā)送消息,其處理函數(shù)將會調(diào)用persitVolume()函數(shù),這將會把音量的設(shè)置信息存儲到SettingsProvide中。
      //Audioservice在初始化時,將會從SettingsProvide中將音量設(shè)置讀取出來并進(jìn)行設(shè)置
      sendMsg(mAudioHandler,
          MSG_PERSIST_VOLUME,
          SENDMSG_QUEUE,
          device,
          0,
          streamState,
          PERSIST_DELAY);

    }

最后看到sendVolumeUpdate()

  // UI update and Broadcast Intent
  private void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) {
    //判斷設(shè)備是否擁有通話功能。對沒有通話能力的設(shè)備來說,RING流類型自然也就沒有意義了。這句話應(yīng)該算是一種從語義操作上進(jìn)行的保護(hù)
    if (!isPlatformVoice() && (streamType == AudioSystem.STREAM_RING)) { 
      streamType = AudioSystem.STREAM_NOTIFICATION;          
    }

    if (streamType == AudioSystem.STREAM_MUSIC) {
      flags = updateFlagsForSystemAudio(flags);
    }
    mVolumeController.postVolumeChanged(streamType, flags);//最后將顯示系統(tǒng)音量條的提示框

    if ((flags & AudioManager.FLAG_FIXED_VOLUME) == 0) {
      oldIndex = (oldIndex + 5) / 10; //+5的意義是實(shí)現(xiàn)四舍五入;除以10是因?yàn)榇鎯r先乘了10,轉(zhuǎn)換過程中保留小數(shù)點(diǎn)后一位的精度
      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()方法將會調(diào)用到mController.volumeChanged()方法,通過AIDL將調(diào)用到VolumeUI.java文件中的VolumeController.volumeChanged()方法,最后將會調(diào)用mPanel.postVolumeChanged更新系統(tǒng)音量條的UI,這里就是VolumePanel的內(nèi)容啦,具體可看上一篇文章系統(tǒng)音量條

通過音量設(shè)置函數(shù)setStreamVolume()

除了音量鍵調(diào)節(jié)音量以外,還可以通過系統(tǒng)設(shè)置中進(jìn)行調(diào)節(jié)。

控件會根據(jù)當(dāng)初的音量和模式去調(diào)用AudioManager的adjustStreamVolume(靜音或震動模式)或setStreamVolume(普通模式)去調(diào)整相對應(yīng)的音量。

AudioManager.setStreamVolume()是系統(tǒng)設(shè)置界面中調(diào)整音量所使用的接口。

  private void setStreamVolume(int streamType, int index, int flags, String callingPackage,
      int uid) {
    ......

    ensureValidStreamType(streamType);//先判斷一下流類型這個參數(shù)的有效性
    int streamTypeAlias = mStreamVolumeAlias[streamType];//對這個數(shù)組進(jìn)行流類型的轉(zhuǎn)換
    VolumeStreamState streamState = mStreamStates[streamTypeAlias];

    final int device = getDeviceForStream(streamType);//獲取當(dāng)前流將使用哪一個音頻設(shè)備進(jìn)行播放。最終會被調(diào)用到AudioPolicyService中
    ......

      oldIndex = streamState.getIndex(device);//獲取當(dāng)前流的音量

      index = rescaleIndex(index * 10, streamType, streamTypeAlias);//將原流類型下的音量值映射到目標(biāo)流類型下的音量值
      ......

      if (!checkSafeMediaVolume(streamTypeAlias, index, device)) {
        mVolumeController.postDisplaySafeVolumeWarning(flags);
        mPendingVolumeCommand = new StreamVolumeCommand(
                          streamType, index, flags, device);
      } else {
        onSetStreamVolume(streamType, index, flags, device);//將調(diào)用setStreamVolumeInt()方法
        index = mStreamStates[streamType].getIndex(device);//獲取設(shè)置結(jié)果
      }
    }
    sendVolumeUpdate(streamType, oldIndex, index, flags);//通知外界音量發(fā)生了變化
  }

onSetStreamVolume()方法主要就是調(diào)用了setStreamVolumeInt()方法,下面看下setStreamVolumeInt()

  private void setStreamVolumeInt(int streamType, int index, int device, boolean force) {
    VolumeStreamState streamState = mStreamStates[streamType];

    if (streamState.setIndex(index, device) || force) { //調(diào)用streamState.setIndex(),更改VolumeStreamState對象中保存的音量值
      //這個消息將把音量設(shè)置到底層去,并將其存儲到Settingsprovider中
      sendMsg(mAudioHandler,
          MSG_SET_DEVICE_VOLUME,
          SENDMSG_QUEUE,
          device,
          0,
          streamState,
          0);
    }
  }

仔細(xì)一看,會發(fā)現(xiàn)這與上面音量鍵控制音量的adjustStreamVolume()函數(shù)的代碼很類似,主要都是調(diào)用了那幾個方法。

總結(jié)

以上就是本文關(guān)于Android原生音量控制實(shí)例詳解的全部內(nèi)容,希望對大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!

相關(guān)文章

  • Android自定義View之繼承TextView繪制背景

    Android自定義View之繼承TextView繪制背景

    這篇文章主要為大家詳細(xì)介紹了Android自定義View之繼承TextView繪制背景的相關(guān)資料,需要的朋友可以參考下
    2016-05-05
  • Android自定義彈出框dialog效果

    Android自定義彈出框dialog效果

    這篇文章主要為大家詳細(xì)介紹了Android自定義彈出框dialog效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • Flutter 包管理器和資源管理使用學(xué)習(xí)

    Flutter 包管理器和資源管理使用學(xué)習(xí)

    這篇文章主要為大家介紹了Flutter 包管理器和資源管理使用學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • Android利用ContentProvider初始化組件的踩坑記錄

    Android利用ContentProvider初始化組件的踩坑記錄

    做Android SDK開發(fā)的時候,一般我們會將初始化的方法封裝,然后讓調(diào)用SDK的開發(fā)者在Application的onCreate方法中進(jìn)行初始化,下面這篇文章主要給大家介紹了關(guān)于Android利用ContentProvider初始化組件的踩坑記錄,需要的朋友可以參考下
    2022-04-04
  • Android最簡單的限制輸入方法(只包含數(shù)字、字母和符號)

    Android最簡單的限制輸入方法(只包含數(shù)字、字母和符號)

    這篇文章主要給大家介紹了關(guān)于Android最簡單的限制輸入的實(shí)現(xiàn)方法,限制輸入框只能輸入數(shù)字、字母和符號,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看 吧
    2018-11-11
  • Flutter 路由插件fluro的使用

    Flutter 路由插件fluro的使用

    使用原生的路由基本上能夠滿足大部分需求,但如果想要對頁面做類似瀏覽器 url 那樣的路由,或者控制頁面跳轉(zhuǎn)的轉(zhuǎn)場動畫,那么原生的路由需要做不少的改造。在 pub 上,有優(yōu)秀的路由插件 fluro 解決這類問題。本文介紹該插件的使用方法
    2021-06-06
  • 微信小程序在安卓的白屏問題原因及改進(jìn)講解

    微信小程序在安卓的白屏問題原因及改進(jìn)講解

    今天小編就為大家分享一篇關(guān)于微信小程序在安卓的白屏問題原因及改進(jìn)講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-02-02
  • Android實(shí)現(xiàn)ViewFlipper圖片動畫滑動

    Android實(shí)現(xiàn)ViewFlipper圖片動畫滑動

    這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)ViewFlipper圖片動畫滑動,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • Android編程實(shí)現(xiàn)ListView中item部分區(qū)域添加點(diǎn)擊事件功能

    Android編程實(shí)現(xiàn)ListView中item部分區(qū)域添加點(diǎn)擊事件功能

    這篇文章主要介紹了Android編程實(shí)現(xiàn)ListView中item部分區(qū)域添加點(diǎn)擊事件功能,涉及Android ListView相關(guān)適配器及事件響應(yīng)操作技巧,需要的朋友可以參考下
    2018-01-01
  • Android實(shí)現(xiàn)圓形圖片小工具

    Android實(shí)現(xiàn)圓形圖片小工具

    這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)圓形圖片小工具,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-09-09

最新評論