Android核心知识点
面试成功其实是必然的,因为我做足了充分的准备工作,包括刷题啊,看一些Android核心的知识点,看一些面试的博客吸取大家面试的一些经验。
下面这份PDF是我翻阅了差不多3个月左右一些Android大博主的博客从他们那里取其精华去其糟泊所整理出来的一些Android的核心知识点,全部都是精华中的精华,我能面试到现在2-2资深开发人员跟我整理的这本Android核心知识点有密不可分的关系,在这里本着共赢的心态分享给各位朋友。
不管是Android基础还是Java基础以及常见的数据结构,这些是无原则地必须要熟练掌握的,尤其是非计算机专业的同学,面试官一上来肯定是问你基础,要是基础表现不好很容易被扣上基础不扎实的帽子,常见的就那些,只要你平时认真思考过基本上面试是没太大问题的。
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上我搜集整理的2019-2021BAT 面试真题解析,我把大厂面试中常被问到的技术点整理成了PDF,包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
开启播放线程
thread= Thread(Runnable {
readDataFromFile()
})
thread?.start()
将数据不断的送入缓存区并通过AudioTrack播放
private fun readDataFromFile() {
val byteArray = ByteArray(bufferSizeInBytes)
val file = File(externalCacheDir?.absolutePath + File.separator + filename)
if (!file.exists()) {
Toast.makeText(this, “请先进行录制PCM音频”, Toast.LENGTH_SHORT).show()
return
}
val fis = FileInputStream(file)
var read: Int
status = Status.STARTING
while ({ read = fis.read(byteArray);read }() > 0) {
var ret = audioTrack?.write(byteArray, 0, bufferSizeInBytes)!!
if (ret == AudioTrack.ERROR_BAD_VALUE || ret == AudioTrack.ERROR_INVALID_OPERATION || ret == AudioManager.ERROR_DEAD_OBJECT) {
break
}
}
fis.close()
}
释放资源
首先停止播放
if (audioTrack != null && audioTrack?.state != AudioTrack.STATE_UNINITIALIZED) {
audioTrack?.stop()
}
然后停止线程
if (thread!=null){
thread?.join()
thread =null
}
最后释放AudioTrack
if (audioTrack != null) {
audioTrack?.release()
audioTrack = null
}
经过这样几个步骤,我们就可以听到刚刚我们录制的PCM数据声音啦!这就是使用Android提供的AudioRecord
和AudioTrack
对PCM数据进行操作。
但是仅仅这样是不够的,因为我们生活中肯定不是使用PCM进行音乐播放,那么怎么才能让音频在主流播放器上播放呢?这就需要我们进行压缩编码了,比如mp3或aac压缩编码格式。
AAC
压缩编码是一种高压缩比的音频压缩算法,AAC压缩比通常为18:1;采样率范围通常是8KHz~96KHz,这个范围比MP3更广一些(MP3的范围一般是:16KHz~48KHz),所以在16bit的采样格式上比MP3更精细。
方便我们处理AAC编码,Android SDK中提供了MediaCodec
API,可以将PCM数据编码成AAC数据。大概需要以下几个步骤:
-
创建
MediaCodec
-
为
MediaCodec
配置音频参数 -
启动线程,循环往缓冲区送入数据
-
通过
MediaCodec
将缓冲区的数据进行编码并写入文件 -
释放资源
创建MediaCodec
通过MediaCodec.createEncoderByType
创建编码MediaCodec
mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC)
配置音频参数
// 配置采样率和声道数
mediaFormat = MediaFormat.createAudioFormat(MINE_TYPE,sampleRate,channel)
// 配置比特率
mediaFormat?.setInteger(MediaFormat.KEY_BIT_RATE,bitRate)
// 配置PROFILE,其中属AAC-LC兼容性最好
mediaFormat?.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC)
// 最大输入大小
mediaFormat?.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 10 * 1024)
mediaCodec!!.configure(mediaFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE)
mediaCodec?.start()
inputBuffers = mediaCodec?.inputBuffers
outputBuffers = mediaCodec?.outputBuffers
启动线程
启动线程,循环读取PCM数据送入缓冲区
thread = Thread(Runnable {
val fis = FileInputStream(pcmFile)
fos = FileOutputStream(aacFile)
var read: Int
while ({ read = fis.read(byteArray);read }() > 0) {
encode(byteArray)
}
})
thread?.start()
AAC编码
将送入的PCM数据通过MediaCodec
进行编码,大致流程如下:
-
通过可用缓存去索引,获取可用输入缓冲区
-
将pcm数据放入输入缓冲区并提交
-
根据输出缓冲区索引,获取输出缓冲区
-
创建输出数据
data
,并添加ADTS头部信息(有7byte) -
将
outputBuffer
编码后数据写入data
(data有7byte偏移) -
将编码数据
data
写入文件 -
重复以上过程
private fun encode(byteArray: ByteArray){
mediaCodec?.run {
//返回要用有效数据填充的输入缓冲区的索引, -1 无限期地等待输入缓冲区的可用性
val inputIndex = dequeueInputBuffer(-1)
if (inputIndex > 0){
// 根据索引获取可用输入缓存区
val inputBuffer = this@AACEncoder.inputBuffers!![inputIndex]
// 清空缓冲区
inputBuffer.clear()
// 将pcm数据放入缓冲区
inputBuffer.put(byteArray)
// 提交放入数据缓冲区索引以及大小
queueInputBuffer(inputIndex,0,byteArray.size,System.nanoTime(),0)
}
// 指定编码器缓冲区中有效数据范围
val bufferInfo = MediaCodec.BufferInfo()
// 获取输出缓冲区索引
var outputIndex = dequeueOutputBuffer(bufferInfo,0)
while (outputIndex>0){
// 根据索引获取可用输出缓存区
val outputBuffer =this@AACEncoder.outputBuffers!![outputIndex]
// 测量输出缓冲区大小
val bufferSize = bufferInfo.size
// 输出缓冲区实际大小,ADTS头部长度为7
val bufferOutSize = bufferSize+7
// 指定输出缓存区偏移位置以及限制大小
outputBuffer.position(bufferInfo.offset)
outputBuffer.limit(bufferInfo.offset+bufferSize)
// 创建输出空数据
val data = ByteArray(bufferOutSize)
// 向空数据先增加ADTS头部
addADTStoPacket(data, bufferOutSize)
// 将编码输出数据写入已加入ADTS头部的数据中
outputBuffer.get(data,7,bufferInfo.size)
// 重新指定输出缓存区偏移
outputBuffer.position(bufferInfo.offset)
// 将获取的数据写入文件
fos?.write(data)
// 释放输出缓冲区
releaseOutputBuffer(outputIndex,false)
// 重新获取输出缓冲区索引
outputIndex=dequeueOutputBuffer(bufferInfo,0)
}
}
}
释放资源
编码完成后,一定要释放所有资源,首先关闭输入输出流
fos?.close()
fis.close()
停止编码
if (mediaCodec!=null){
mediaCodec?.stop()
}
然后就是关闭线程
if (thread!=null){
thread?.join()
thread =null
}
最后释放MediaCodec
if (mediaCodec!=null){
mediaCodec?.release()
mediaCodec = null
mediaFormat = null
inputBuffers = null
outputBuffers = null
}
通过以上一个流程,我们就可以得到一个AAC压缩编码的音频文件,可以听一听是不是自己刚刚录制的。我听了一下我自己唱的一首歌,觉得我的还是可以的嘛,也不是那么五音不全~~
虽然我们通过压缩编码生成了AAC音频文件,但是有个问题:毕竟AAC音频不是主流的音频文件呀,我们最常见的是MP3的嘛,可不可以将PCM编码成MP3呢?
当然是可以的,但是Android SDK没有直接提供这样的API,只能使用Android NDK,通过交叉编译其他C或C++库来进行实现。
Android NDK 是由Google提供一个工具集,可让您使用 C 和 C++ 等语言实现应用。
Android NDK 一般有两个用途,一个是进一步提升设备性能,以降低延迟,或运行计算密集型应用,如游戏或物理模拟;另一个是重复使用您自己或其他开发者的 C 或 C++ 库。当然我们使用最多的应该还是后者。
想使用Android NDK调试代码需要以下工具:
-
Android 原生开发套件 (NDK):这套工具使您能在 Android 应用中使用 C 和 C++ 代码。
-
CMake:一款外部编译工具,可与 Gradle 搭配使用来编译原生库。如果您只计划使用 ndk-build,则不需要此组件。
-
LLDB:Android Studio 用于调试原生代码的调试程序。
可以进入Tools > SDK Manager > SDK Tools 选择 NDK (Side by side) 和 CMake 应用安装
在应用以上选项之后,我们可以看到SDK的目录中多了一个ndk-bundle
的文件夹,大致目录结构如下
-
ndk-build:该Shell脚本是Android NDK构建系统的起始点,一般在项目中仅仅执行这一个命令就可以编译出对应的动态链接库了,后面的编译mp3lame 就会使用到。
-
platforms:该目录包含支持不同Android目标版本的头文件和库文件,NDK构建系统会根据具体的配置来引用指定平台下的头文件和库文件。
-
toolchains:该目录包含目前NDK所支持的不同平台下的交叉编译器——ARM、x86、MIPS,其中比较常用的是ARM和x86。不论是哪个平台都会提供以下工具:
·CC:编译器,对C源文件进行编译处理,生成汇编文件。
·AS:将汇编文件生成目标文件(汇编文件使用的是指令助记符,AS将它翻译成机器码)。
·AR:打包器,用于库操作,可以通过该工具从一个库中删除或者增加目标代码模块。
·LD:链接器,为前面生成的目标代码分配地址空间,将多个目标文件链接成一个库或者是可执行文件。
·GDB:调试工具,可以对运行过程中的程序进行代码调试工作。
·STRIP:以最终生成的可执行文件或者库文件作为输入,然后消除掉其中的源码。
·NM:查看静态库文件中的符号表。
·Objdump:查看静态库或者动态库的方法签名。
了解Android NDK 之后,就可新建一个支持C/C++ 的Android项目了:
-
在向导的 Choose your project 部分中,选择 Native C++ 项目类型。
-
点击 Next。
-
填写向导下一部分中的所有其他字段。
-
点击 Next。
-
在向导的 Customize C++ Support 部分中,您可以使用 C++ Standard 字段来自定义项目。使用下拉列表选择您想要使用哪种 C++ 标准化。选择 Toolchain Default 可使用默认的 CMake 设置。
-
点击 Finish,同步完成之后会出现如下图所示的目录结构,即表示原生项目创建完成
LAME是一个开源的MP3音频压缩库,当前是公认有损质量MP3中压缩效果最好的编码器,所以我们选择它来进行压缩编码,那如何进行压缩编码呢?主流的由两种方式:
-
Cmake
-
ndk-build
下面就详细讲解这两种方式
Cmake编译Lame
配置Cmake之后可以直接将Lame代码运行于Android中
准备
下载Lame-3.100并解压大概得到如下目录
然后将里面的libmp3lame
文件夹拷贝到我们上面创建的支持c/c++项目,删除其中的i386和vector文件夹,以及其他非.c 和 .h 后缀的文件
需要将以下文件进行修改,否则会报错
- 将util.h中570行
extern ieee754_float32_t fast_log2(ieee754_float32_t x)
替换成
extern float fast_log2(float x)
- 在id3tag.c和machine.h两个文件中,将
HAVE_STRCHR
和HAVE_MEMCPY
注释
#ifdef STDC_HEADERS
include <stddef.h>
include <stdlib.h>
include <string.h>
include <ctype.h>
#else
/*# ifndef HAVE_STRCHR
define strchr index
define strrchr rindex
endif
*/
char *strchr(), *strrchr();
/*# ifndef HAVE_MEMCPY
define memcpy(d, s, n) bcopy ((s), (d), (n))
endif*/
#endif
- 在fft.c中,将47行注释
//#include “vector/lame_intrin.h”
- 将set_get.h中24行
#include <lame.h>
替换成
#include “lame.h”
编写Mp3编码器
首先在自己的包下(我这里是com.coder.media
,这个很重要,后面会用到),新建Mp3Encoder
的文件,大概如下几个方法
-
init,将声道,比特率,采样率等信息传入
-
encode,根据init中提供的信息进行编码
-
destroy,释放资源
class Mp3Encoder {
companion object {
init {
System.loadLibrary(“mp3encoder”)
}
}
external fun init(
pcmPath: String,
channel: Int,
bitRate: Int,
sampleRate: Int,
mp3Path: String
): Int
external fun encode()
external fun destroy()
}
在cpp目录下新建两个文件
-
mp3-encoder.h
-
mp3-encoder.cpp
这两个文件中可能会提示错误异常,先不要管它,这是因为我们还没有配置CMakeList.txt
导致的。
在mp3-encoder.h
中定义三个变量
FILE* pcmFile;
FILE* mp3File;
lame_t lameClient;
然后在mp3-encoder.c
中分别实现我们在Mp3Encoder
中定义的三个方法
首先导入需要的文件
#include <jni.h>
#include
#include “android/log.h”
#include “libmp3lame/lame.h”
#include “mp3-encoder.h”
#define LOGD(…) __android_log_print(ANDROID_LOG_DEBUG , “mp3-encoder”, VA_ARGS)
然后实现init方法
extern “C” JNIEXPORT jint JNICALL
Java_com_coder_media_Mp3Encoder_init(JNIEnv *env, jobject obj, jstring pcmPathParam, jint channels,
jint bitRate, jint sampleRate, jstring mp3PathParam) {
LOGD(“encoder init”);
int ret = -1;
const char* pcmPath = env->GetStringUTFChars(pcmPathParam, NULL);
const char* mp3Path = env->GetStringUTFChars(mp3PathParam, NULL);
pcmFile = fopen(pcmPath,“rb”);
if (pcmFile){
mp3File = fopen(mp3Path,“wb”);
if (mp3File){
lameClient = lame_init();
lame_set_in_samplerate(lameClient, sampleRate);
lame_set_out_samplerate(lameClient,sampleRate);
lame_set_num_channels(lameClient,channels);
lame_set_brate(lameClient,bitRate);
lame_init_params(lameClient);
ret = 0;
}
}
env->ReleaseStringUTFChars(mp3PathParam, mp3Path);
env->ReleaseStringUTFChars(pcmPathParam, pcmPath);
return ret;
}
这个方法的作用就是将我们的音频参数信息送入lameClient
需要注意我这里的方法Java_com_coder_media_Mp3Encoder_init
中的com_coder_media
需要替换成你自己的对应包名,下面的encode和destroy也是如此,切记!!!
实现通过lame
编码encode
extern “C” JNIEXPORT void JNICALL
Java_com_coder_media_Mp3Encoder_encode(JNIEnv *env, jobject obj) {
LOGD(“encoder encode”);
int bufferSize = 1024 * 256;
short* buffer = new short[bufferSize / 2];
short* leftBuffer = new short[bufferSize / 4];
short* rightBuffer = new short[bufferSize / 4];
unsigned char* mp3_buffer = new unsigned char[bufferSize];
size_t readBufferSize = 0;
while ((readBufferSize = fread(buffer, 2, bufferSize / 2, pcmFile)) > 0) {
for (int i = 0; i < readBufferSize; i++) {
if (i % 2 == 0) {
leftBuffer[i / 2] = buffer[i];
} else {
rightBuffer[i / 2] = buffer[i];
}
}
size_t wroteSize = lame_encode_buffer(lameClient, (short int *) leftBuffer, (short int *) rightBuffer,
(int)(readBufferSize / 2), mp3_buffer, bufferSize);
fwrite(mp3_buffer, 1, wroteSize, mp3File);
}
delete[] buffer;
delete[] leftBuffer;
delete[] rightBuffer;
delete[] mp3_buffer;
}
最后释放资源
extern “C” JNIEXPORT void JNICALL
Java_com_coder_media_Mp3Encoder_destroy(JNIEnv *env, jobject obj) {
LOGD(“encoder destroy”);
if(pcmFile) {
fclose(pcmFile);
}
if(mp3File) {
fclose(mp3File);
lame_close(lameClient);
}
}
配置Cmake
打开CPP目录下的CMakeList.txt文件,向其中添加如下代码
// 引入目录
include_directories(libmp3lame)
// 将libmp3lame下所有文件路径赋值给 SRC_LIST
aux_source_directory(libmp3lame SRC_LIST)
// 加入libmp3lame所有c文件
add_library(mp3encoder
SHARED
mp3-encoder.cpp ${SRC_LIST})
并且向target_link_libraries
添加mp3encoder
target_link_libraries(
mp3encoder
native-lib
${log-lib})
修改CMakeList.txt之后,点击右上角Sync Now
就可以看到我们mp3-encoder.cpp
和mp3-encoder.h
中的错误提示不见了,至此已基本完成
然后在我们的代码中调用Mp3Encoder
中的方法就可以将PCM
编码成Mp3
了
总结
【Android 详细知识点思维脑图(技能树)】
我个人是做Android开发,已经有十来年了,目前在某创业公司任职CTO兼系统架构师。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
最后,赠与大家一句话,共勉!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
所有c文件
add_library(mp3encoder
SHARED
mp3-encoder.cpp ${SRC_LIST})
并且向target_link_libraries
添加mp3encoder
target_link_libraries(
mp3encoder
native-lib
${log-lib})
修改CMakeList.txt之后,点击右上角Sync Now
就可以看到我们mp3-encoder.cpp
和mp3-encoder.h
中的错误提示不见了,至此已基本完成
然后在我们的代码中调用Mp3Encoder
中的方法就可以将PCM
编码成Mp3
了
总结
【Android 详细知识点思维脑图(技能树)】
[外链图片转存中…(img-Anvurita-1714967913285)]
我个人是做Android开发,已经有十来年了,目前在某创业公司任职CTO兼系统架构师。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
[外链图片转存中…(img-PR56loFQ-1714967913286)]
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
最后,赠与大家一句话,共勉!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!