几个 Android 函数和类

使用 MediaRecoder 录音、使用 ExoPlayer 播放本地音频、全屏模式下键盘弹出遮盖内容区。

# 使用 MediaRecoder 录音

// <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
// <uses-permission android:name="android.permission.RECORD_AUDIO"/>

var mRecorder: MediaRecorder? = null
var mFile: File? = null

btnStart.setOnClickListener {
    if (Environment.getExternalStorageState() != android.os.Environment.MEDIA_MOUNTED) {
        toast("没有内存卡")
        return@setOnClickListener
    }
    mFile = File(Environment.getExternalStorageDirectory().canonicalPath + "/sound.amr")
    mRecorder = MediaRecorder()
    // 设置录音的声音来源
    mRecorder?.setAudioSource(MediaRecorder.AudioSource.MIC)
    // 设置录制的声音的输出格式(必须在设置声音编码格式之前设置)
    mRecorder?.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB)
    // 设置声音编码的格式
    mRecorder?.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
    mRecorder?.setOutputFile(mFile?.absolutePath)
    mRecorder?.prepare()
    // 开始录音
    mRecorder?.start()
}

btnStop.setOnClickListener {
    if (mFile != null && (mFile as File).exists()) {
        mRecorder?.stop()
        mRecorder?.release()
        mRecorder = null
    }
}

# 使用 ExoPlayer 播放本地音频

private fun initPlayer() {
    val bandwidthMeter = DefaultBandwidthMeter()
    val factory = AdaptiveTrackSelection.Factory(bandwidthMeter)
    val trackSelector = DefaultTrackSelector(factory)
    val loadControl = DefaultLoadControl()
    mPlayer = ExoPlayerFactory.newSimpleInstance(this, trackSelector, loadControl)
    mPlayer?.playWhenReady = true
    mPlayer?.addListener(object : ExoPlayer.EventListener {
        override fun onTracksChanged(trackGroups: TrackGroupArray?,
                                     trackSelections: TrackSelectionArray?) {
            warn { "onTracksChanged" }
        }

        override fun onPlayerError(error: ExoPlaybackException?) {
            error?.printStackTrace()
        }

        override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
            if (playbackState == ExoPlayer.STATE_ENDED) {
            }
        }

        override fun onLoadingChanged(isLoading: Boolean) {
            warn { "isLoading: $isLoading" }
        }

        override fun onPositionDiscontinuity() {
            warn { "onPositionDiscontinuity" }
        }

        override fun onTimelineChanged(timeline: Timeline?, manifest: Any?) {
            warn { "timeline: ${timeline?.isEmpty}" }
        }

    })
}

private fun getMediaSource(filePath: String): ExtractorMediaSource {
    val bandwidthMeter2 = DefaultBandwidthMeter()
    val dataSourceFactory = DefaultDataSourceFactory(this,
            Util.getUserAgent(this, "com.keng42.lab"), bandwidthMeter2)
    val extractorsFactory = DefaultExtractorsFactory()
    val mediaSource = ExtractorMediaSource(
            Uri.fromFile(File(filePath)),
            dataSourceFactory, extractorsFactory, null, null)
    return mediaSource
}

private fun playAudio(filePath: String) {
    mPlayer?.stop()
    val mediaSource = getMediaSource(filePath)
    mPlayer?.prepare(mediaSource)
}

// 在不需要的时候调用 mPlayer?.release() 释放资源

# 全屏模式下键盘弹出遮盖内容区

/**
 * For more information, see https://code.google.com/p/android/issues/detail?id=5497
 * To use this class, simply invoke assistActivity() on an Activity that already has its content view set.
 */
public class AndroidBug5497Workaround {

    public static void assistActivity(Activity activity) {
        new AndroidBug5497Workaround(activity);
    }

    private View mChildOfContent; // activity_xxx.xml 布局 rootView
    private int usableHeightPrevious;
    private FrameLayout.LayoutParams frameLayoutParams; // 用于设置 rootView 宽高
    private int navBarHeight;

    private AndroidBug5497Workaround(Activity activity) {
        FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
        mChildOfContent = content.getChildAt(0);
        navBarHeight = getNavBarHeight(activity);
        mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(
          new ViewTreeObserver.OnGlobalLayoutListener() {
            public void onGlobalLayout() {
                possiblyResizeChildOfContent();
            }
        });
        frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
    }

    private void possiblyResizeChildOfContent() {
        int usableHeightNow = computeUsableHeight();
        if (usableHeightNow != usableHeightPrevious) {
            int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
            int heightDifference = usableHeightSansKeyboard - usableHeightNow;
            if (heightDifference > (usableHeightSansKeyboard / 4)) {
                // keyboard probably just became visible
                frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
            } else {
                // keyboard probably just became hidden
                frameLayoutParams.height = usableHeightSansKeyboard - navBarHeight;
            }
            mChildOfContent.requestLayout();
            usableHeightPrevious = usableHeightNow;
        }
    }

    private int computeUsableHeight() {
        Rect r = new Rect();
        mChildOfContent.getWindowVisibleDisplayFrame(r);
        return r.bottom;
    }

    private int getNavBarHeight(Activity activity) {
        Resources resources = activity.getResources();
        int resourceId = resources.getIdentifier("navigation_bar_height",
                                                 "dimen", "android");
        if (resourceId > 0) {
            return resources.getDimensionPixelSize(resourceId);
        }
        return 0;
    }
}