Android10 AudioRecord简单解析

这篇具有很好参考价值的文章主要介绍了Android10 AudioRecord简单解析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

AudioRecord简介: 音频子系统,从音频输入设备录制音频PCM数据供应用层使用 业务场景:蓝牙语音遥控器,带麦克风的摄像头,耳机等。应用拿到音频PCM数据可以直接播放(比如常见的录音apk,录音达人) 或者对语音进行解析(语音转文字,语音助手应用)

本文是对国科平台android10进行梳理,其它版本SDK可能有所差异,但总体框架是类似的
涉及源码路径:
AudioRecord.java(frameworks/base/media/java/android/media/AudioRecord.java)
android_media_AudioRecord.cpp(frameworks/base/core/jni/android_media_AudioRecord.cpp)
AudioRecord.cpp(frameworks/av/media/libaudioclient/AudioRecord.cpp)
AudioSystem.cpp(frameworks/av/media/libaudioclient/AudioSystem.cpp)
AudioFlinger.cpp(frameworks/av/services/audioflinger/AudioFlinger.cpp)
AudioPolicyService.cpp(frameworks/av/services/audiopolicy/service/AudioPolicyService.cpp)
AudioPolicyInterfaceImpl.cpp(frameworks/av/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp)
AudioPolicyManager.cpp(frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp)
Engine.cpp(frameworks/av/services/audiopolicy/enginedefault/src/Engine.cpp)
AudioInputDescriptor.cpp(frameworks/av/services/audiopolicy/common/managerdefinitions/src/AudioInputDescriptor.cpp)
AudioPolicyClientImpl.cpp(frameworks/av/services/audiopolicy/service/AudioPolicyClientImpl.cpp)
main_audioserver.cpp(frameworks/av/media/audioserver/main_audioserver.cpp)
DeviceHalLocal.cpp(frameworks/av/media/libaudiohal/impl/DeviceHalLocal.cpp)
Threads.cpp(frameworks/av/services/audioflinger/Threads.cpp)

概述:
AudioRecord:audio系统对外的API类,负责音频数据采集(录音)
AudioFlinger: audio系统的工作引擎,管理输入输出流,和底层音频相关的硬件交互
AudioPolicyService: 音频策略服务,管理音频设备的选择和切换,管理音量等

1.AudioRecord用例介绍:

int recordBufferSize = AudioRecord.getMinBufferSize(16000,//采样率
					   AudioFormat.CHANNEL_IN_STEREO,//声道数,双声道
					   AudioFormat.ENCODING_PCM_16BIT//采样精度,一个采样点16bit,两个字节
					   );
//②创建AudioRecord
AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,//输入源,麦克风类型
						16000,
						AudioFormat.CHANNEL_IN_STEREO,
						AudioFormat.ENCODING_PCM_16BIT,
						recordBufferSize);
//③启动录音
audioRecord.startRecording();
//④读取录音数据
audioRecord.read(data, 0, recordBufferSize);
.......
//⑤停止录音,释放底层资源
audioRecord.stop();
audioRecord.release();

Frame(帧):描述数据量大小,比如一帧等于多少字节, 在音频系统中 1单位Frame等于1个采样点字节数 X 声道数
(比如上面的采样精度 16BIT,双声道,那么一Frame就是 2X2=4字节)
最小缓冲区大小=最低帧数∗声道数∗采样精度
(最小缓冲区大小也和底层硬件有关,比如硬件是否支持当前采样率,采样精度,硬件延时等情况,最后综合计算出一个大小)
2.AudioRecord分析(java层)
AudioRecord.java

public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
            int bufferSizeInBytes){
			//这个会调用下面这个同名重载的构造函数
			}
public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
            int sessionId) throws IllegalArgumentException {
		//检查参数是否合法
        audioParamCheck(attributes.getCapturePreset(), rate, encoding);
        audioBuffSizeCheck(bufferSizeInBytes);
		......
		//到native层
        int initResult = native_setup( new WeakReference<AudioRecord>(this),
                mAudioAttributes, sampleRate, mChannelMask, mChannelIndexMask,
                mAudioFormat, mNativeBufferSizeInBytes,
                session, getCurrentOpPackageName(), 0 /*nativeRecordInJavaObj*/);
    }

android_media_AudioRecord.cpp

static jint
android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this,
        jobject jaa, jintArray jSampleRate, jint channelMask, jint channelIndexMask,
        jint audioFormat, jint buffSizeInBytes, jintArray jSession, jstring opPackageName,
        jlong nativeRecordInJavaObj)
{
	//前面主要是做了一些java层值到jni层值的转换
	......
	//创建native层的AudioRecord
    lpRecorder = new AudioRecord(String16(opPackageNameStr.c_str()));
	......
	//调用 AudioRecord::set()
    const status_t status = lpRecorder->set(paa->source,
            sampleRateInHertz,
            format,        // word length, PCM
            localChanMask,
            frameCount,
            recorderCallback,// callback_t
            lpCallbackData,// void* user
            0,             // notificationFrames,
            true,          // threadCanCallJava
            sessionId,
            AudioRecord::TRANSFER_DEFAULT,
            flags,
            -1, -1,        // default uid, pid
            paa.get());
	//native层AudioRecord保存到java层的mNativeRecorderInJavaObj变量中
    setAudioRecord(env, thiz, lpRecorder);
}

java层AudioRecord总结:
在构造函数中调用native层接口,创建native层的AudioRecord,后续的startRecording,read,stop,release等操作都是通过这个native层的AudioRecord去处理

3.AudioRecord分析(native层)
AudioRecord.cpp

//构造函数中对成员变量进行初始化
AudioRecord::AudioRecord(const String16 &opPackageName)
    : mActive(false), mStatus(NO_INIT), mOpPackageName(opPackageName),
      mSessionId(AUDIO_SESSION_ALLOCATE),
      mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(SP_DEFAULT),
      mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE), mRoutedDeviceId(AUDIO_PORT_HANDLE_NONE),
      mSelectedMicDirection(MIC_DIRECTION_UNSPECIFIED),
      mSelectedMicFieldDimension(MIC_FIELD_DIMENSION_DEFAULT)
{
}
status_t AudioRecord::set(audio_source_t inputSource, uint32_t sampleRate,
		audio_format_t format, audio_channel_mask_t channelMask, size_t frameCount,
		callback_t cbf, void* user, uint32_t notificationFrames,
		bool threadCanCallJava, audio_session_t sessionId,transfer_type transferType, 
		audio_input_flags_t flags, uid_t uid, pid_t pid, 
		const audio_attributes_t* pAttributes, audio_port_handle_t selectedDeviceId, 
		audio_microphone_direction_t selectedMicDirection, float microphoneFieldDimension)
{
	//如果有回调函数,会启动AudioRecordThread 处理回调函数
    if (cbf != NULL) {
        mAudioRecordThread = new AudioRecordThread(*this);
        mAudioRecordThread->run("AudioRecord", ANDROID_PRIORITY_AUDIO);
    }
    // create the IAudioRecord
    status = createRecord_l(0 /*epoch*/, mOpPackageName);
	......
}
status_t AudioRecord::createRecord_l(const Modulo<uint32_t> &epoch, const String16& opPackageName)
{
	......
	//IAudioRecord record  通过Binder调用到AudioFlinger去初始化音频服务里录音相关的流程
    record = audioFlinger->createRecord(input, output, &status);
	......
    //audio_track_cblk_t* cblk 共享内存,
	//音频数据从音频服务进程(AudioFlinger所在的进程)到客户端进程,通过共享内存来实现
	//AudioRecordClientProxy类定义了一些操作共享内存的方法
    mProxy = new AudioRecordClientProxy(cblk, buffers, mFrameCount, mFrameSize);
}

native层AudioRecord总结:
native层流程里主要拿到了两个客户端资源,一个是record,能和AudioFlinger里的一个RecordTrack类型的track交互,控制录音启动,停止等
从底层硬件采集音频数据的工作是在音频服务进程中进行的,客户端拿到的这个record能通过远程调用的方式对录音流程进行控制
IAudioRecord是联系AudioRecord和AudioFlinger的纽带,IAudioRecord在AudioFlinger那边具体是什么,在后续的流程中会有解释
Android10 AudioRecord简单解析

Android10 AudioRecord简单解析
                                          AudioRecord和AudioFlinger交互流程图

4.AudioFlinger分析
AudioFlinger驻留在audioserver进程中
main_audioserver.cpp

int main(int argc __unused, char **argv)
{
		......
        sp<ProcessState> proc(ProcessState::self());
        sp<IServiceManager> sm = defaultServiceManager();
		//注册AudioFlinger和AudioPolicyService服务
        AudioFlinger::instantiate();
        AudioPolicyService::instantiate();
		......
        ProcessState::self()->startThreadPool();
        IPCThreadState::self()->joinThreadPool();
    }
}

AudioFlinger.cpp

sp<media::IAudioRecord> AudioFlinger::createRecord(const CreateRecordInput& input,
                                                   CreateRecordOutput& output,
                                                   status_t *status)
{
    sp<RecordThread::RecordTrack> recordTrack;
    sp<RecordHandle> recordHandle;
	......
	//初始化open输入流   启动Record线程
    lStatus = AudioSystem::getInputForAttr(&input.attr, &output.inputId,
                                      input.riid,
                                      sessionId,
                                      clientPid,
                                      clientUid,
                                      input.opPackageName,
                                      &input.config,
                                      output.flags, &output.selectedDeviceId, &portId);
		......
		//output.inputId是一个索引号, 根据索引号可以获取一个录音用的工作线程 RecordThread
        RecordThread *thread = checkRecordThread_l(output.inputId);
		......
		//创建线程对应的recordTrack
        recordTrack = thread->createRecordTrack_l(client, input.attr, &output.sampleRate,
						input.config.format, input.config.channel_mask, &output.frameCount, 
						sessionId, &output.notificationFrameCount, callingPid, clientUid,
						&output.flags, input.clientInfo.clientTid, &lStatus, portId,
						input.opPackageName);
	......
    // RecordHandle继承BnAudioRecord实现了服务端,可对recordTrack进行一些流程上的控制(start stop啥的)
    recordHandle = new RecordHandle(recordTrack);
	......
	//当recordHandle通过Binder服务被从 AudioFlinger送到 AudioRecord时, AudioRecord那边拿到的其实是客户端BpAudioRecord,这个和匿名Binder有关,详细可自行了解
    return recordHandle;
}

AudioSystem.cpp
AudioSystem::getInputForAttr()流程跟踪

status_t AudioSystem::getInputForAttr(const audio_attributes_t *attr, 
							audio_io_handle_t *input, audio_unique_id_t riid, 
							audio_session_t session, pid_t pid, uid_t uid, 
							const String16& opPackageName, const audio_config_base_t *config,
							audio_input_flags_t flags, audio_port_handle_t *selectedDeviceId, 
							audio_port_handle_t *portId)
{
	//AudioSystem里的接口基本都是这样,封装了对AudioPolicyService(或者AudioFlinger)的远程调用,方便其它模块访问使用服务的功能
    const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
    if (aps == 0) return NO_INIT;
    return aps->getInputForAttr(attr, input, riid, session, pid, uid, opPackageName,config, flags, selectedDeviceId, portId);
	}

 AudioPolicyInterfaceImpl.cpp  中实现了许多AudioPolicyService的接口

status_t AudioPolicyService::getInputForAttr(const audio_attributes_t *attr, 
						audio_io_handle_t *input, audio_unique_id_t riid,
						audio_session_t session,pid_t pid, uid_t uid, 
						const String16& opPackageName,  const audio_config_base_t *config, 
						audio_input_flags_t flags, audio_port_handle_t *selectedDeviceId, 
						audio_port_handle_t *portId)
{
	......
    //检查客户端进程是否有录音权限   android.permission.RECORD_AUDIO
    if (!recordingAllowed(opPackageName, pid, uid)){
		......
	}
	//android.permission.CAPTURE_AUDIO_OUTPUT  比如通话录音情景
    bool canCaptureOutput = captureAudioOutputAllowed(pid, uid);
	......
	//到AudioPolicyManager去, mAudioPolicyManager在AudioPolicyService构造函数中被初始化
   status = mAudioPolicyManager->getInputForAttr(attr, input, riid, session, uid, config, flags, selectedDeviceId,
                                                         &inputType, portId);
	......
}

AudioPolicyManager.cpp

status_t AudioPolicyManager::getInputForAttr(const audio_attributes_t *attr, 
								audio_io_handle_t *input,  audio_unique_id_t riid, 
								audio_session_t session, uid_t uid,  
								const audio_config_base_t *config,  audio_input_flags_t flags,
								audio_port_handle_t *selectedDeviceId, input_type_t *inputType,
								audio_port_handle_t *portId)
{
		...... //忽略前面的一系列判断,一般情况会走到这里
		//通过attributes 来拿到符合的输入设备
        device = mEngine->getInputDeviceForAttributes(attributes, &policyMix);
		//通过输入设备和其它的音频参数,拿到一个符合的输入流
    	*input = getInputForDevice(device, session, attributes, config, flags, policyMix);
}

Engine.cpp

sp<DeviceDescriptor> Engine::getInputDeviceForAttributes(const audio_attributes_t &attr,
                                                         sp<AudioPolicyMix> *mix) const
{
	......
	//用source类型拿到一个输入设备的类型, 我们这里是 AUDIO_SOURCE_MIC
    audio_devices_t deviceType = getDeviceForInputSource(attr.source);
	......
	//availableInputDevices是当前活跃(可用)的所有输入设备列表
    return availableInputDevices.getDevice(deviceType,
                                           String8(address.c_str()),
                                           AUDIO_FORMAT_DEFAULT);
}
audio_devices_t Engine::getDeviceForInputSource(audio_source_t inputSource) const
{
	......
    switch (inputSource) {
    case AUDIO_SOURCE_DEFAULT:
    case AUDIO_SOURCE_MIC:
    if (availableDeviceTypes & AUDIO_DEVICE_IN_BLUETOOTH_A2DP) {
        device = AUDIO_DEVICE_IN_BLUETOOTH_A2DP;
    } else if ((getForceUse(AUDIO_POLICY_FORCE_FOR_RECORD) == AUDIO_POLICY_FORCE_BT_SCO) &&
        (availableDeviceTypes & AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET)) {
        device = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET;
    } else if (availableDeviceTypes & AUDIO_DEVICE_IN_WIRED_HEADSET) {
        device = AUDIO_DEVICE_IN_WIRED_HEADSET;
    } else if (availableDeviceTypes & AUDIO_DEVICE_IN_USB_HEADSET) {
        device = AUDIO_DEVICE_IN_USB_HEADSET;
    } else if (availableDeviceTypes & AUDIO_DEVICE_IN_USB_DEVICE) {
        device = AUDIO_DEVICE_IN_USB_DEVICE;
    } else if (availableDeviceTypes & AUDIO_DEVICE_IN_BUILTIN_MIC) {
		//蓝牙语音遥控匹配到这个
        device = AUDIO_DEVICE_IN_BUILTIN_MIC;
    }
    break;
	.......
    return device;
}

拿到输入设备后再回到AudioPolicyManager::getInputForAttr()中,下一步是通过输入设备打开一个输入流

audio_io_handle_t AudioPolicyManager::getInputForDevice(const sp<DeviceDescriptor> &device,
                                                        audio_session_t session,
                                                        const audio_attributes_t &attributes,
                                                        const audio_config_base_t *config,
                                                        audio_input_flags_t flags,
                                                        const sp<AudioPolicyMix> &policyMix)
{
	......
    for (;;) {
        //匹配audio_policy_configuration.xml中的解析到的 profile字段
		//xml中描述定义了许多可用输入或者输出设备,AudioPolicyManager构造初始化时会解析该xml,并加载对应hal层so
		//解析结果可通过 dumpsys media.audio_policy命令查看
        profile = getInputProfile(device, profileSamplingRate, 
					profileFormat, profileChannelMask, profileFlags);
		......
    }
	//根据匹配到的参数构造一个输入流描述, mpClientInterface类型是AudioPolicyClient,用于直接和AudioFlinger交互的
    sp<AudioInputDescriptor> inputDesc = new AudioInputDescriptor(profile, mpClientInterface);
	......
	//去打开输入流, 最后会走到 hal层so对应的 adev_open_output_stream()
    status_t status = inputDesc->open(&lConfig, device, halInputSource, profileFlags, &input);
	...
    return input;
}

AudioInputDescriptor.cpp

status_t AudioInputDescriptor::open(const audio_config_t *config, 
						const sp<DeviceDescriptor> &device, audio_source_t source, 
						audio_input_flags_t flags, audio_io_handle_t *input)
{
	......
    //mpClientInterface类型是AudioPolicyClient, 也在AudioPolicyService构造函数中被初始化
	//用于直接和AudioFlinger交互的
    status_t status = mClientInterface->openInput(mProfile->getModuleHandle(), 
										input, &lConfig,  &deviceType, 
										mDevice->address(), source, flags);
	......
}

AudioPolicyClientImpl.cpp

status_t AudioPolicyService::AudioPolicyClient::openInput(audio_module_handle_t module, 
									audio_io_handle_t *input, audio_config_t *config, 
									audio_devices_t *device, const String8& address, 
									audio_source_t source, audio_input_flags_t flags)
{
    sp<IAudioFlinger> af = AudioSystem::get_audio_flinger();
	......
	//调用到AudioFlinger::openInput()
    return af->openInput(module, input, config, device, address, source, flags);
}

流程到这里,可以先稍微暂停总结一下,我们一开始在AudioFlinger::createRecord()中,然后调用AudioSystem::getInputForAttr()去拿一个输入流,但是流程经过一层又一层调用后,最后又回到了AudioFlinger。这也体现了android audio的设计框架,AudioPolicyService管理音频策略,它帮AudioFlinger去选了一个合适的输入设备,但最终打开设备,和底层audio模块交互,还得AudioFlinger自己来做,AudioFlinger是执行者。

Android10 AudioRecord简单解析

AudioFlinger.cpp

status_t AudioFlinger::openInput(audio_module_handle_t module,
					audio_io_handle_t *input, audio_config_t *config,
					audio_devices_t *devices, const String8& address,
					audio_source_t source, audio_input_flags_t flags)
{
	......
	//调用openInput_l() 打开输入流,启动一个thread
    sp<ThreadBase> thread = openInput_l(
            module, input, config, *devices, address, source, flags, AUDIO_DEVICE_NONE, String8{});
}

sp<AudioFlinger::ThreadBase> AudioFlinger::openInput_l(audio_module_handle_t module,
								audio_io_handle_t *input, audio_config_t *config, 
								audio_devices_t devices, const String8& address,
								audio_source_t source, audio_input_flags_t flags,
								audio_devices_t outputDevice, 
								const String8& outputDeviceAddress)
{
	......
	//inHwHal类型是 DeviceHalLocal
    status_t status = inHwHal->openInputStream(
            *input, devices, &halconfig, flags, address.string(), source,
            outputDevice, outputDeviceAddress, &inStream);
   ......
			//RecordThread启动
            sp<RecordThread> thread = new RecordThread(this,
                                      inputStream,
                                      *input,
                                      primaryOutputDevice_l(),
                                      devices,
                                      mSystemReady
                                      );
            mRecordThreads.add(*input, thread);
            return thread;
}

DeviceHalLocal.cpp

status_t DeviceHalLocal::openInputStream(audio_io_handle_t handle,
				audio_devices_t devices, struct audio_config *config,
				audio_input_flags_t flags, const char *address,
				audio_source_t source,audio_devices_t /*outputDevice*/,
				const char */*outputDeviceAddress*/,
				sp<StreamInHalInterface> *inStream) {
    audio_stream_in_t *halStream;
	//到对应的audio hal层 so中, open_input_stream()
    int openResult = mDev->open_input_stream(
            mDev, handle, devices, config, &halStream, flags, address, source);
}

总结: AudioFlinger 调用AudioSystem::getInputForAttr()后会走到AudioPolicyService(音频策略服务)
策略服务综合各种条件(录音的参数, 可用输入设备, 结合实际场景选择一个合适的输入设备)。
AudioFlinger拿到输入设备后通过openInput() 去打开实际的输入流。auido hal层的so库,不同平台,不同项目会有差异
hardware/libhardware/modules/audio/audio_hw.c 是源码中默认的模板,可以看下代码结构

打开输入流,启动RecordThread后,还剩最后一个关键步骤,RecordTrack的创建:
Threads.cpp

sp<AudioFlinger::RecordThread::RecordTrack> AudioFlinger::RecordThread::createRecordTrack_l(
		const sp<AudioFlinger::Client>& client, const audio_attributes_t& attr,
		uint32_t *pSampleRate, audio_format_t format, audio_channel_mask_t channelMask,
		size_t *pFrameCount, audio_session_t sessionId, size_t *pNotificationFrameCount,
		pid_t creatorPid, uid_t uid, audio_input_flags_t *flags, pid_t tid, status_t *status,
		audio_port_handle_t portId, const String16& opPackageName)
{
		......
    	//RecordThread里的 RecordTrack
        track = new RecordTrack(this, client, attr, sampleRate,
                      format, channelMask, frameCount,
                      nullptr /* buffer */, (size_t)0 /* bufferSize */, sessionId, creatorPid, uid,
                      *flags, TrackBase::TYPE_DEFAULT, opPackageName, portId);
	......
    return track;
}

创建的RecordTrack会被打包进RecordHandle,RecordHandle对应客户端通过Binder发送给AudioRecord
这样AudioRecord就能和RecordTrack交互了,音频数据会通过RecordTrack送到AudioRecord文章来源地址https://www.toymoban.com/news/detail-406704.html

到了这里,关于Android10 AudioRecord简单解析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux系统简介(简单粗暴)

    Linux之父(Linus Torwalds),1991年10月,发布了0.02版(第一个公开版)内核,1994年03月,发布1.0版内核,UNIX诞生时间为1970年1月1日,这里为什么要说到UNIX呢,主要是Linux的创始人为了纪念UNIX的诞生,把Linux系统的世界时间 ,它的起始点设置为1970年的1月1日,以后写脚本用得上,

    2024年02月05日
    浏览(50)
  • Mariadb简介、安装和简单使用

    1、介绍 MariaDB是MySQL关系数据库管理系统的一个分支,由社区开发,有商业支持,旨在继续保持在GNU GPL下开源。MariaDB的开发是由MySQL的一些原始开发者领导的,他们担心甲骨文公司收购MySQL后会有一些隐患。 MariaDB打算保持与MySQL的高度兼容性,与MySQL API和命令精确匹配。Mar

    2024年04月28日
    浏览(37)
  • 机器学习简介[01/2]:简单线性回归

    Python 中的机器学习简介:简单线性回归         简单线性回归为机器学习提供了优雅的介绍。它可用于标识自变量和因变量之间的关系。使用梯度下降,可以训练基本模型以拟合一组点以供未来预测。         这是涵盖回归、梯度下降、分类和机器学习的其他基本方

    2024年02月11日
    浏览(42)
  • 6.2 声音编辑工具GoldWave5简介(10)

    3.淡入与淡出 多首不同的音乐相互切换时,如果不经过处理,给人的感觉很突兀。如果给所有的音乐都加上淡入淡出的效果,不同的音乐相互切换的时候给人的感觉会比较自然了。 (1) 选择【效果】|【音量】|【淡入】命令,打开“淡入”对话框,如图6-2-16所示。 图6-2-16 “

    2024年01月18日
    浏览(43)
  • Android系统架构简介

    Android的系统架构主要分为五层,见下图: 从下至上依次是: Linux内核: Android基于Linux,由Linux提供核心系统服务,如安全、内存管理、进程管理、网络堆栈、驱动模型等等; 除了标准的Linux内核之外,Android还增加了内核的驱动程序,如显示驱动、音频驱动、 Binder驱动、输入

    2024年02月10日
    浏览(33)
  • Android 开发简介

    Android 是由 Google 领导的开放手机联盟开发的基于 Linux 的开源移动操作系统。有关一般详细信息,请参阅 Android 主网站。 Android 开发与其他平台的开发有很大不同。因此,在开始针对 Android 编程之前,我们建议您确保熟悉以下关键主题: Java 编程语言是 Android 操作系统的主要

    2024年01月16日
    浏览(46)
  • Android UT开发简介

            Android UT(Unit Testing)开发是指在 Android 应用程序中进行单元测试的开发过程。单元测试是一种软件测试方法,用于测试应用程序中的最小可测试单元(通常是函数或方法)的正确性。 Android UT 开发的主要目标是确保应用程序的各个单元在不同情况下能够按照预期

    2024年02月09日
    浏览(42)
  • Android 命令行工具简介

    关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。 我们继续总结学习 基础知识 ,温故知新。 本文简单介绍 Android 命令行工具。 Android SDK 中包含了开发

    2024年02月08日
    浏览(35)
  • Android SdkManager简介

    关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。 我们继续总结学习** 基础知识**,温故知新。 本文讲述SdkManager sdkmanager 是一个命令行工具,可以用来

    2024年02月04日
    浏览(29)
  • Android apkanalyzer简介

    关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。 我们继续总结学习 基础知识 ,温故知新。 apkanalyzer 用于分析我们生成的apk,可以得到各种我们想要

    2024年02月08日
    浏览(24)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包