1、理论篇#sp TvManager:mTvManager; 强指针sp智能指针在Android的源代码中,经常会看到形如:sp、wp这样的类型定义,这其实是Android中的智能指针。智能指针是C+中的一个概念,通过基于引用计数的方法,解决对象的自动释放的问题。在C+编程中,有两个很让人头痛的问题:一是忘记释放动态申请的对象从而造成内存泄露;二是对象在一个地方释放后,又在别的地方被使用,从而引起内存访问错误。程序员往往需要花费很大精力进行精心设计,以避免这些问题的出现。在使用智能指针后,动态申请的内存将会被自动释放(有点类似Java的垃圾回收),不需要再使用delete来释放对象,也不需要考虑一
2、个对象是否已经在其它地方被释放了,从而使程序编写工作减轻不少,而程序的稳定性大大提高。 Android的智能指针相关的源代码在下面两个文件中: frameworks/base/include/utils/RefBase.h frameworks/base/libs/utils/RefBase.cpp 涉及的类以及类之间的关系如下图所示: Android中定义了两种智能指针类型,一种是强指针sp(strong pointer),一种是弱指针(weak pointer)。其实成为强引用和弱引用更合适一些。强指针与一般意义的智能指针概念相同,通过引用计数来记录有多少使用者在使用一个对象,如果所有使用
3、者都放弃了对该对象的引用,则该对象将被自动销毁。 弱指针也指向一个对象,但是弱指针仅仅记录该对象的地址,不能通过弱指针来访问该对象,也就是说不能通过弱智真来调用对象的成员函数或访问对象的成员变量。要想访问弱指针所指向的对象,需首先将弱指针升级为强指针(通过wp类所提供的promote()方法)。弱指针所指向的对象是有可能在其它地方被销毁的,如果对象已经被销毁,wp的promote()方法将返回空指针,这样就能避免出现地址访问错的情况。 是不是很神奇?弱指针是怎么做到这一点的呢?其实说穿了一点也不复杂,原因就在于每一个可以被智能指针引用的对象都同时被附加了另外一个weakref_impl类型的对
4、象,这个对象中负责记录对象的强指针引用计数和弱指针引用计数。这个对象是智能指针的实现内部使用的,智能指针的使用者看不到这个对象。弱指针操作的就是这个对象,只有当强引用计数和弱引用计数都为0时,这个对象才会被销毁。 说了这么多原理,下面该看看到底智能指针该怎么使用了。假设现在有一个类MyClass,如果要使用智能指针来引用这个类的对象,那么这个类需满足下列两个前提条件: (1) 这个类是基类RefBase的子类或间接子类; (2) 这个类必须定义虚构造函数,即它的构造函数需要这样定义: virtual MyClass(); 满足了上述条件的类就可以定义智能指针了,定义方法和普通指针类似。比如普通
5、指针是这样定义: MyClass* p_obj; 智能指针是这样定义: sp p_obj; 注意不要定义成 sp* p_obj。初学者容易犯这种错误,这样实际上相当于定义了一个指针的指针。尽管在语法上没有问题,但是最好永远不要使用这样的定义。 定义了一个智能指针的变量,就可以象普通指针那样使用它,包括赋值、访问对象成员、作为函数的返回值、作为函数的参数等。比如: p_obj = new MyClass(); / 注意不要写成 p_obj = new sp sp p_obj2 = p_obj; p_obj-func(); p_obj = create_obj(); some_func(p_obj
6、); 注意不要试图delete一个智能指针,即 delete p_obj。不要担心对象的销毁问题,智能指针的最大作用就是自动销毁不再使用的对象。不需要再使用一个对象后,直接将指针赋值为NULL即可: p_obj = NULL; 上面说的都是强指针,弱指针的定义方法和强指针类似,但是不能通过弱指针来访问对象的成员。下面是弱指针的示例: wp wp_obj = new MyClass(); p_obj = wp_obj.promote(); / 升级为强指针。不过这里要用.而不是-,真是有负其指针之名啊 wp_obj = NULL; 智能指针用起来是很方便,在一般情况下最好使用智能指针来代替普通指
7、针。但是需要知道一个智能指针其实是一个对象,而不是一个真正的指针,因此其运行效率是远远比不上普通指针的。所以在对运行效率敏感的地方,最好还是不要使用智能指针为好。#认识理解Java中native方法 摘要 Java不是完美的,Java的不足除了体现在运行速度上要比传统的C+慢许多之外,Java无法直接访问到操作系统底层(如系统硬件等),为此Java使用native方法来扩展Java程序的功能。关键字 Java native Java不是完美的,Java的不足除了体现在运行速度上要比传统的C+慢许多之外,Java无法直接访问到操作系统底层(如系统硬件等),为此Java使用native方法来扩展J
8、ava程序的功能。 可以将native方法比作Java程序同程序的接口,其实现步骤: 、在Java中声明native()方法,然后编译; 、用javah产生一个.h文件; 、写一个.cpp文件实现native导出方法,其中需要包含第二步产生的.h文件(注意其中又包含了JDK带的jni.h文件); 、将第三步的.cpp文件编译成动态链接库文件; 、在Java中用System.loadLibrary()方法加载第四步产生的动态链接库文件,这个native()方法就可以在Java中被访问了。 JAVA本地方法适用的情况 1.为了使用底层的主机平台的某个特性,而这个特性不能通过JAVA API访问 2
9、.为了访问一个老的系统或者使用一个已有的库,而这个系统或这个库不是用JAVA编写的 3.为了加快程序的性能,而将一段时间敏感的代码作为本地方法实现。 首先写好JAVA文件 /* * Created on 2005-12-19 Author shaoqi */ package com.hode.hodeframework.modelupdate; public class CheckFile public native void displayHelloWorld(); static System.loadLibrary(test); public static void main(String
10、 args) new CheckFile().displayHelloWorld(); 然后根据写好的文件编译成CLASS文件 然后在classes或bin之类的class根目录下执行javah -jni com.hode.hodeframework.modelupdate.CheckFile, 就会在根目录下得到一个com_hode_hodeframework_modelupdate_CheckFile.h的文件 然后根据头文件的内容编写com_hode_hodeframework_modelupdate_CheckFile.c文件 #include CheckFile.h #include
11、 #include JNIEXPORT void JNICALL Java_com_hode_hodeframework_modelupdate_CheckFile_displayHelloWorld(JNIEnv *env, jobject obj) printf(Hello world!n); return; 之后编译生成DLL文件如“test.dll”,名称与System.loadLibrary(test)中的名称一致 vc的编译方法:cl -I%java_home%include -I%java_home%includewin32 -LD com_hode_hodeframework_
12、modelupdate_CheckFile.c -Fetest.dll 最后在运行时加参数-Djava.library.path=dll存放的路径#MStar Android网络机代码组成从代码结构上分,MStar Android网络电视包含MBoot、PM、Kernel、Supernova、ICS、TvApp和Local MM这7个部分,以MSD6A801的代码包为例,如图所示:上面7个部分跟Android架构的关系图:下面针对这7个模块进行详细描述,内容主要涉及启动流程和代码结构等,最后将对各模块的相互关联做个总结。MBoot概述MBoot是MStar Boot Loader的缩写,由SB
13、oot和UBoot两部分组成。MBoot用于系统的启动引导,它会初始化硬件,然后从NAND flash加载Linux内核(kernel)和应用程序(applications)到DRAM。(说明:其实标准的UBoot程序已经做了MBoot的全部工作,MStar引入SBoot的概念,是沿用了MStar非网络机的用法)以下是上面提到的几个名词的对比:AcronymDescriptionMBootMStar Boot loaderSBootSmall Boot loaderUBootUniversal Boot loaderMBoot存储在flash,该flash一般采用16M/32M bit容量的s
14、pi flash,里面存放系统的引导程序及部分系统、用户数据。系统上电后首先通过Mboot引导启动,Mboot完成启动后再启动系统主程序。系统的主程序存放在NAND Flash中。不管是spi flash还是Nand flash,任何一个有故障,都会导致整机无法启动。MBoot主要包含sboot和uboot两部分: 说明:标准的GPL UBoot代码放在目录u-boot-1.1.6里面,经过MStar移植的UBoot代码放在u-boot-2011.06。SBoot介绍SBoot代码结构树SBoot是MBoot启动系统的入口点,用于初始化CPU(ARMv7),Cache,寄存器等。初始化工作完成
15、后,MBoot将跳到UBoot的入口点。SBoot的目录结构主要包括下面子目录:bin, inc, scripts, out, src。bin: 存放PM.bin,关于PM在后面会介绍;inc:board文件,如MSD6A608对应的BD_MST038B_10AHT_EAGLE.h;scripts:存放脚本文件,用于配置相关参数,即输入make menuconfig后弹出的menu configuration window;out:存放MBoot编译生成的bin;src:SBoot的源代码.SBoot启动流程启动入口和地址在sboot.lds(lds文件是决定一个可执行程序的各个段的存储位置,
16、以及入口地址,这也是链接定位的作用)文件里描述:下面以MSD6A608为例进行说明: 入口点即ENTRY(_vector),在/sboot/src/eagle/boot.S有实现代码,boot.S会生成对应的boot.o; 启动地址:0x00000000;Ram的地址,这里的Ram指的是DSP Ram;需要注意的是,启动代码即boot.o应该放在第一个section上并且boot.o不能大于8Kbytes。如下是SBoot的section定义,同样在/sboot/src/eagle/sboot.lds里能找到实现。入口Entry执行完后,执行/sboot/src/eagle/bootrom.S
17、,这一步进行寄存器设置,注意一点是,这一步还会进行Uart的初始化工作,具体实现在/sboot/src/eagle/include/Drv_uart.inc接着执行/sboot/src/eagle/bootram.S,在这一步会跳到UBoot执行,过程: BOOT_CopyBootRAM BOOTRAM_Entry UBoot entry.SBoot完整的流程如下:这个过程简单来说就是:SBoot从DSP Ram的地址0x1fc00000开始运行,使用DSP Ram进行硬件初始化工作,执行相关C代码的工作,并从SPI Flash拷贝UBoot到DRam里面,最终跳转到UBoot入口。UBoot
18、介绍UBoot代码结构树MStar UBoot是基于u-boot-1.1.6的,UBoot自带CLI(command line interface),通过串口可以跟用户交互。UBoot能做些啥?有下面几点: 通过CLI设置环境变量和执行一些操作; 初始化系统; 通过以太网口加载kernel到DRAM; 烧录kernel和application到NAND Flash; 设置kernel参数; 解压kernel; 传递启动参数给kernel;UBoot代码包通常包含下面子目录:board, common, cpu, disk, doc, drivers, fs,include, lib_gener
19、ic, lib_mips, net, pos, rtc, tools.各个包的作用如下:DirectoryDescriptionBoardboard definecommonCommand and environment setupCpuMIPS cpu start.sDiskReports device info to the userDocDocumentsdriversNAND driver, emac driver, usb driverFsext2, fat, fdos, jffs2, includeheader filelib_genericgeneric liblib_mipsM
20、IPS libnetnet, tftp, postPower on self testrtcReal time clockUBoot启动流程启动入口和地址在u-boot.lds描述,然后指向/u-boot-2011.06/arch/arm/cpu/armv7/start.S,在start.S描述了入口地址。同时,在u-boot.lds还描述了u_boot_cmd存放的section,关于u_boot-cmd在后面做详细描述。UBoot启动过程:MBoot/UBoot Command机制UBoot中通过宏定义U_BOOT_CMD来定义结构体变量,并且把这些同一种结构体的变量放在一个段中,充分利用
21、了链接器的作用。这样做的好处是,各个模块的研发人员不必去维护一个全局的结构体数组,而且也不用知道数组中的下标就能定位。与UBoot命令机制相关的定义放在/u-boot-2011.06/include/command.h,下面对关键定义做分析。命令结构体:其中Struct_Section的定义如下:由此可见,被U_BOOT_CMD定义过的结构体变量,最终会被链接器放到u_boot_cmd段中。链接脚本在U-boot.lds定义,如下:该脚本把所有.u_boot_cmd放在了一起。获取所有U_BOOT_CMD的命令的API:运行U_BOOT_CMD定义的CMD的API:UBoot环境变量(Envi
22、ronment Variable)U-Boot通过环境变量(env)为用户提供一定程度的可配置性,这些环境变量包括启动kernel的参数(bootargs)、本地IP(ipaddr)、网卡MAC地址(ethaddr),波特率(baudrate)等等。环境变量可以固化到非易失性存储介质中(比如spi flash),使用printenv/setenv/saveenv命令来查看和修改保存。环境变量可以理解为一个键值对,即name=value,通过name找到value,再对不同的value做不同的设置。MBoot把环境变量分成3类:普通的环境变量,process环境变量,kernel环境变量。通过下
23、面代码实现:几个关键的API:查找指定的环境变量:设置指定环境变量的值:保存环境变量:根据不同的硬件配置,环境变量存储的位置不一样,有spi flash、eeprom、nand flash等,mstar的android平台一般是用spi flash存储环境变量。具体使用哪种存储介质,在/MBoot/u-boot-2011.06/include/configs/Uboot_module_config.hPM概述PM即Power Manager,为待机控制管理模块。待机时有一颗C51内核的MCU在跑,负责待机时的电源管理,IR和按键处理等操作。进入PM的方式有两种,从MBoot进入PM和从Supe
24、rnova进入PM。从MBoot进入PM1. 定义Command2. 注册Process类型Command3. 执行CommandMain_loop()-MstarProcess()-App_Register_Process()、run_command(do_if_boot_to_pm)- If_Boot_To_PM()-msAPI_Power_PowerDown_EXEC()-MDrv_PM_PowerDown(&PmPowerDownCfg)在If_Boot_To_PM(void)根据环境变量factory_poweron_mode判断是否进入PM,可以开机后进入工厂菜单设置,也可以通过M
25、Boot的CLI改写环境变量的方式。在void msAPI_Power_PowerDown_EXEC(void)会传一个PmWakeCfg和PM_PowerDownCfg的结构体到PM,这些结构体定义了开机方法和中断的开关状态。另外需要注意,如果PM运行状态为E_PM_STANDBY,则表示PM是运行在Dram上,所以进入PM后Dram不能断电。从Supernova进入PM当按power off按键或者因为timer out而进入待机,将从Supernova进入PM:MSrv_Control_common:EnterSleepMode()-void mapi_system:PowerDown(
26、)-MDrv_PM_PowerDown() 过程类似MBoot进入PMAndroid内核(Kernel)概述内核(kernel)是操作系统最基本的部分,主要负责管理系统资源。内核提供一种硬件抽象的方法,通过进程间通信机制(IPC)及系统调用(system call),应用进程可间接控制所需的硬件资源。如下图所示:IPC内核(Kernel)应用软件系统调用Android内核在整个Android架构中的层次图:Android内核是基于Linux 2.6内核的(MSD6A608使用2.6.35.11版本的内核),并且是Linux 2.6内核的一个增强版本,除了修改部分Bug外,它提供了用于支持And
27、roid平台的设备驱动。内核代码的产生(Zip&unZip Kernel)Kernel代码编译连接后产生kernel镜像,kernel镜像分为压缩与非压缩两种。压缩内核镜像是把非压缩内核镜像作为数据进行压缩打包,并加上了解压缩代码。也就是说,它是一个自解压的可执行镜像。两种内核镜像的产生过程如下图:内核入口(Entry of kernel)Linux 内核编译连接后生成的ELF 映像文件是vmlinux,从内核源代码顶层目录下的Makefile(即顶层Makefile)中可以找到vmlinux 的生成规则:vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vm
28、linux-main) $(kallsyms.o) FORCE其中$(vmlinux-lds)是编译连接脚本,对于ARM 平台,就是arch/arm/kernel/vmlinux-lds 文件。vmlinux-init 也在顶层Makefile 中定义:vmlinux-init := $(head-y) $(init-y)head-y 在arch/arm/Makefile 中定义:head-y:= arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.oifeq ($(CONFIG_MMU),)MMUEXT := -nommuend
29、if对于有 MMU 的处理器,MMUEXT 为空白字符串,所以arch/arm/kernel/head.O 是第一个连接的文件,而这个文件是由arch/arm/kernel/head.S 编译产生成的。ARM Linux 启动过程分析综合以上分析,可以得出结论,非压缩 ARM Linux 内核的入口点在arch/arm/kernel/head.S中。head.S最后包含了head-common.S,在head-common.S中定义调转到C语言入口函数start_kernel()。start_kernel()函数start_kernel()函数是内核初始化C语言部分的主体。这个函数完成系统底层
30、基本机制,包括处理器、存储管理系统、进程管理系统、中断机制、定时机制等的初始化工作。start_kernel()函数的实现在init/main.c。start_kernel()函数完成基本的初始化工作后,最后调用了rest_init()函数。下面来看看rest_init()函数的实现,同样在init/main.c定义:static noinline void _init_refok rest_init(void)_releases(kernel_lock)int pid;rcu_scheduler_starting();/* * We need to spawn init first so t
31、hat it obtains pid 1, however * the init task will end up wanting to create kthreads, which, if * we schedule it before we create kthreadd, will OOPS. */kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);/创建第一个内核线程,入口点是kernel_init()函数numa_default_policy();pid = kernel_thread(kthreadd, NULL,
32、CLONE_FS | CLONE_FILES);/创建第二个内核线程,入口点是kthreadd()函数rcu_read_lock();kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);rcu_read_unlock();complete(&kthreadd_done);unlock_kernel();/* * The boot idle thread must execute schedule() * at least once to get things moving: */init_idle_bootup_task(current
33、);preempt_enable_no_resched();schedule();preempt_disable();/* Call into cpu_idle with preempt disabled */cpu_idle(); /进入空闲状态该函数创建了一个入口点init()函数的内核线程,新创建的内核线程pid=1,放入了调度队列中,系统转而执行kernel_init()函数,kernel_init()函数同样在/init/main.c中实现,该函数完成系统更高层次,比如驱动程序,根文件系统等等的初始化工作,其中的关键点为:do_basic_setup()函数比较重要,这个函数先调用d
34、river_init()函数完成驱动程序的初始化,又通过do_initcalls()函数依次调用了系统中所有的初始化函数。接着看kernel_init()函数,该函数接着加载了外部程序/init从而有了自己的用户态空间,进而变成了一个进程,然后该进程再执行用户态的初始化程序,如创建终端,等待用户登录等等,系统启动完成。Android应用系统Android源代码由两部分组成,Android内核代码和Android系统应用部分的代码。MStar android TV的系统应用代码放在android/ics目录下,顶层目录如下:Android/abi (abi相关代码。ABI:application
35、binary interface,应用程序二进制接口)Android/bionic(bionic C库)Android/bootable(启动引导相关代码)Android/build(存放系统编译规则及generic等基础开发配置包)Android/cts(Android兼容性测试套件标准)Android/dalvik(dalvik JAVA虚拟机)Android/development(应用程序开发相关)Android/device (设备相关代码)Android/docs (介绍开源的相关文档)Android/external(android使用的一些开源的模组)Android/frame
36、works(核心框架java及C+语言,是Android应用程序的框架。)Android/hardware(主要是硬件适配层HAL代码)Android/libcore(核心库相关)Android/ndk (ndk相关代码。AndroidNDK(Android NativeDevelopment Kit)是一系列的开发工具,允许程序开发人员在Android应用程序中嵌入C/C+语言编写的非托管代码。)Android/out(编译完成后的代码输出与此目录) Android/packages(应用程序包) Android/prebuilt(x86和arm架构下预编译的一些资源)Android/sdk
37、(sdk及模拟器)Android/system(文件系统、应用及组件C语言)Android/MakefileAndroid/v8.log上面三个红色标示的目录比较常用,建议用source insight建立好工程。Android应用系统启动流程上一节提到内核在最后加载根目录下的/init外部程序,从而离开kernel进入Android应用系统,/init的实现代码在system/core/init/init.c,入口函数是main(),这也是Android第一个启动进程和第一个用户进程/init,用它来完成引导。/init进程会读取init.rc, init_xxx.rc(跟制造商有关,MSD
38、6A608是init_eagle.rc), 按照脚本引导。引导完毕之后init并不退出,继续负责property service的工作。在脚本文件init.rc和init.eagle.rc里包含action和service,service通过fork()创建为一个个进程,如下图所示几个重量级进程:servicemanagerServiceMananger从名字上就可以看出来它是用来管理系统中的service,比如:tvservice、ActivityManagerService等。在ServiceManager中有两个比较重要的方法:add_service、check_service。系统的s
39、ervice需要通过add_service把自己的信息注册到ServiceManager中,当需要使用时,通过check_service检查该service是否存在。servicemanager的代码在目录:ics/frameworks/base/cmds/servicemanager从它的主函数代码开始:从main函数中可以看出,它主要做了三件事情:1.打开/dev/binder设备,并在内存中映射128K的空间。2.通知Binder设备,把自己变成context_manager3.进入循环,不停的去读Binder设备,看是否有对service的请求,如果有的话,就去调用svcmgr_han
40、dler函数回调处理请求。再来看看ServiceManager中是怎么样去注册服务的。当有对service的请求时,调用回调函数svcmgr_handler:在该回调函数中会判断Service有什么需要,如果是请求注册service,那么就执行:这样在ServiceManager中就完成了服务的注册和查找。来看下ServiceManager的功能图:Svcmgr handlersvclistBinder LooperServiceManagerBinder OpenBinder块设备system serversystem server用于启动各种系统服务线程,大部分系统服务都在该进程中运行,常
41、见的比如WindowManagerServer(Wms)、ActivityManagerSystemService(AmS)、 PackageManagerServer(PmS)等,这些系统服务都是以一个线程的方式存在于SystemServer进程中。system server的代码在目录:icsframeworksbaseservicesjavacomandroidserver从init2()函数开始:该函数首先创建了一个ServerThread对象,该对象是一个线程,然后直接运行该线程,从ServerThread的run()方法内部开始真正启动各种服务线程。Android IR按键处理流程
42、按键分为两种,虚拟按键和物理按键。IR按键属于物理按键的范畴,IR要作为输入设备,必须经过kernel驱动层注册为输入设别,这里主要是调用到kerneldriversinputinput.c里面的input_register_device函数去实现,如下:kerneldriversmstarirmdrv_ir.ckerneldriversinputinput.cKernel负责处理按键,然后通过设备文件的方式提供给framework。frameworks/base/services/input定义各功能模块实现按键处理:EventHub.cpp:实现对设备进行扫描并且判断是哪种设备;Input
43、Reader.cpp:对原始数据进行读取;InputDispatcher.cpp:实现数据的派发;InputManager.cpp:按键事件处理的核心,通过创建两个线程InputReader和InputDispatcher,实现管理功能;如果有输入法存在,则按键交给pinyinIME.java的onKeyDown处理;否则会判断是否是system key,如果是system key,交由PhoneWindwoManager.java和MstarTvServiceServerImpl.java处理,如果不是system key,交由具体activity处理。特别说明一下,hotkey的处理在Ms
44、tarTvServiceServerImpl.java的onKeyDown函数。Android Framework概述这里顺便讲下android开发的类型,有3种:1. 系统移植。系统移植就是将android移植到其他的硬件平台,需要掌握Linux内核层和系统运行库层。2. 系统开发。系统开发就是完善android系统本身,需要掌握系统运行库层和framework层,一般是在本地编写C/C+代码,然后通过JNI向上层提供调用接口。3. 应用开发。应用开发就是开发应用程序,即利用framework层提供的API实现应用的开发。Framework为应用开发提供API,如下框图:Android常用的一些make命令1.make -jXX XX表示数字,这个命令将编译Android系统并生成镜像,XX表示可以使用到的CPU核数,这在配置好的电脑上特别有用,公司的16核ubuntu服务器执行make -j16只要不到20分钟,而双核PC上需要4个小时!2.make snod 这条命令将重新生成镜像,消耗的时间很少,类似WinCE的makeimg过程,如果你修改了一些数据文件(如音乐、视频)等文件时比较有用。3.make cts