Android嵌入式底层开发技术(应试)

目录

  • 第一章 Android 操作系统概述
    • Android 软件架构介绍(五层架构)(填空题)
    • Android的子系统
    • Android 应用程序开发过程(2种形式的开发,开发过程)
  • 第二章 Android 源码开发环境搭建
    • Android源码开发环境搭建步骤(简答题)
      • 编译Android源码
      • 搭建Android SDK开发仿真环境
    • 编译命令
  • 第三章 Android 系统的启动
    • Android系统的启动过程(简答题)
    • init进程完成的四大主要功能
    • Android初始化语言(init.rc)四种类型声明
    • Android中主要的本地守护进程
  • 第四章 Android 编译系统与定制 Android 平台系统
    • Android.mk文件
      • Android.mk文件完成的操作
    • 编译HelloWorld应用程序的步骤(简答题)
    • 定制开机界面
    • 定制系统开机动画
      • 蒙版动画
      • 逐帧动画
  • 第五章 JNI机制
    • JNI概述
      • 使用JNI通常的场景
    • JNI的数据类型和其签名
      • 类型和签名对照表
      • 方法签名相关(非常重要)
    • JNI引用类型
    • JNI 访问 Java成员
      • 获得Java类
      • 获得Java属性ID和方法ID
      • JNI操作Java属性和方法
      • 本地代码创建Java对象
      • 本地代码操作 Java String 对象
      • 本地代码操作 Java数组
    • 局部引用和全局引用(填空题&简答题)
      • 局部引用和全局引用对比
      • 局部引用和全局引用创建
      • 局部引用需要手动释放的情况
    • Java环境中保存JNI对象
    • 本地方法的注册(重要)
    • JNI 实例(非常重要)
  • 第六章 Android的对象管理
    • 智能指针
    • 轻量级指针
      • 轻量级指针类的成员
      • 轻量级指针实现的规则
    • 强引用和弱引用
  • 第七章 Binder 通信
    • Binder通信和传统的Linux进程间通信方式相比有什么优点
    • Binder的操作步骤
  • 第八章 Android HAL 硬件抽象层
    • HAL介绍
    • HAL Stub 架构
      • 为什么弃用Module架构改用Stub架构
      • HAL Stub 架构分析
        • 三个结构体
        • 两个常量
        • 一个函数
      • HAL Stub 注册
        • 1、需要修改一下结构体
        • 2、实现操作接口里对硬件操作的函数
        • 3、需要实现设备打开的函数,并将其封装到 `struct hw_module_methods_t` 结构体中去,将扩展的函数封装到struct led_control_device_t结构体中去
        • 4、模块变量的名字必须为`HMI`或者是`HAL_MODULE_INFO_SYM`(只有是这个名字才能从上层调用时使用hw_get_module函数找到它)
    • 用语言描述HAL的实现(简答题)
  • 第九章 HAL硬件抽象层进阶 Sensor HAL 实例
    • Android系统内置支持的传感器
    • 编写一个传感器的应用程序的步骤
  • 扩展 Makefile
    • 工作原理
    • 清空目标文件的规则
    • make的工作方式
    • 变量
    • Makefile变量的赋值
    • 自动化变量
    • Makefile中的关键字
      • 1、override
      • 2、define
      • 3、wildcard
      • 4、export
      • 5、include
    • 函数
      • 1、字符串替换函数--subst
      • 2、模式字符串替换函数--patsubst
      • 3、查找字符串函数--findstring
      • 4、过滤函数--filter
      • 5、去空格函数--strip
      • 6、排序函数--sort
      • 7、取单词函数--word
      • 8、去单词串函数--wordlist
      • 9、单词个数统计函数--words
      • 10、首单词函数--firstword
      • 11、取目录函数--dir
      • 12、取文件函数--notdir
      • 13、取后缀函数--suffix
      • 14、取前缀函数--basename
      • 15、加后缀函数--addsuffix
      • 16、加前缀函数--addprefix
      • 17、连接函数--join
      • 18、遍历函数--foreach
    • 问题(重要)
  • 扩展 Shell
    • 命令
      • echo命令
      • read命令
    • 条件测试
      • 内置测试命令test测试表达式的值
      • 字符串测试
      • 整数测试
      • 逻辑测试
      • 文件测试
      • 检查空值
    • 控制语句
      • if条件语句
      • case选择语句
      • for循环语句
      • while循环语句
      • until循环语句
      • select循环语句
    • 字符串操作
    • 位置参量
    • 函数
    • 问题(重要)
  • 题目预测
    • 写JNI的方法签名
    • 列举题
      • 1、Android软件架构列举(五层)
      • 2、Android中主要的本地守护进程
      • 3、Android.mk中的include的主要预定义编译变量
      • 4、JNI将传递给本地代码的对象分为哪两种
      • 5、Binder框架定义的四个角色是
      • 6、Binder中常用的命令
      • 7、HAL Stub 的框架中的三个结构体是什么
      • 8、Android 4.0系统对传感器的支持多达13种,它们分别是
    • 简答题
      • 1、Android源码开发环境搭建步骤
        • 编译Android源码
        • 搭建Android SDK开发仿真环境
      • 2、Android系统的启动过程
      • 3、编译一个应用程序并制作成镜像仿真的步骤
      • 4、局部引用和全局引用对比
      • 5、对Android中智能指针的了解
      • 6、Binder通信和传统的Linux进程间通信方式相比有什么优点
      • 7、Binder的操作步骤
      • 8、对Binder的了解
      • 9、用语言描述HAL的实现
      • 10、编写一个传感器的应用程序的步骤
    • 编程题
      • 1、如果只考JNI
        • (1)较为完整写法
        • (2)简化写法
      • 2、如果结合HAL硬件抽象层

第一章 Android 操作系统概述

Android 软件架构介绍(五层架构)(填空题)

英文名称 中文名称
Application 应用层
Application Framework 应用框架层
Android Runtime & Libraries 运行时库和本地库层
Android HAL 硬件抽象层
Linux Kernel Linux内核层

Android的子系统

子系统 介绍
Android RIL子系统 RIL( Radio Interface Layer)子系统即无线电接口系统用于管理用户的电话、短信、数据通信等相关功能,它是每个移动通信设备必备的系统。
Android Input子系统 Input子系统用来处理所有来自用户的输入数据,如触摸屏、声音控制物理按键等。
Android GUI子系统 GUI即图形用户接口,也就是图形界面,它用来负责显示系统图形化界面,使用户与系统及信息交互更加便利。 Android的GUI系统和其他各子系统关系密切相关,是 Android中最重要的子系统之一,如绘制一个2D图形、通过 OpenGL库处理3D游戏、通过 Surface Flinger来重叠几个图形界面。
Android Audio子系统 Android的音频处理子系统,主要用于音频方面的数据流传输和控制功能,也负责音频设备的管理。 Android的 Audio系统和多媒体处理紧密相连,如视频的音频处理和播放、电话通信及录音等。
Android Media子系统 Android的多媒体子系统,它是 Android系统中最庞大的子系统,与硬件编解码、 Open Core多媒体框架、 Android多媒体框架等相关,如音频播放器、视频播放器、 Camera摄像预览等。
Android Connectivity子系统 Android连接子系统是智能设备的重要组成部分,它除了一般网络连接,如以太网、Wi-Fi外,还包含蓝牙连接、GPS定位连接、NFC等。
Android Sensor子系统 Android的传感器子系统为当前智能设备大大提高了交互性,它在一些创新的应用程序和应用体验中发挥了重要作用,传感器子系统和手机的硬件设备紧密相关,如gyroscope(陀螺仪)、 accelerometer(加速度计)、 proximity(距离感应器)、 magnetic(磁力传感器)等。

Android 应用程序开发过程(2种形式的开发,开发过程)

1、Android SDK开发(基于Android SDK的Android应用层开发) 特点:最快捷

2、Android源码开发(基于Android源码的底层开发,可以自定义操作系统) 特点:最大程度上体现了开源的优势

源码开发过程:

​ (1)搭建开发环境

​ (2)下载Android源码

​ (3)编译Android源码

​ (4)配置开发环境安装

第二章 Android 源码开发环境搭建

Android源码开发环境搭建步骤(简答题)

编译Android源码

(1)下载安装Vmware虚拟机,安装Ubuntu12.04系统。

(2)下载Android源码及其Linux内核。

(3)建立Linux编译环境:gcc和g++降版本、安装JDK1.6 ,配置JDK的环境变量。

(4)安装编译依赖工具包。

(5)编译Linux内核。

(6)初始化编译环境 source build/envsetup.sh。

(7)选择编译选项 lunch之后,选择目标编译项,应该选择ARM版本。

(8)编译源码 make -j4,-j之后的数字是编译使用的线程数,一般最大可以是内核数的两倍。

(9)源码编译完成,在out/target/product/generic/目录里出现三个文件:system.img、ramdisk.img、userdata.img说明编译成功。

注:选择目标编译项时(1)eng:工程版本 工程机上安装 (2)user:最终用户版本 最终用户机发行版本(3)userdebug:调试版本 (4)tests:测试版本

搭建Android SDK开发仿真环境

(1)下载安装eclipse,安装ADT插件。

(2)下载配置Android SDK工具包,搭建Android SDK平台。

(3)在Android源码目录下通过android create avd -n avd -t 1或者直接通过Android SDK Manager创建模拟器,定制Android模拟器。

(4)运行模拟器,加载编译好的linux内核和android源码镜像文件,如果成功启动说明搭建完成,可以进行仿真。

编译命令

命令 解释
make 编译android操作系统
make ramdisk 单独编译ramdisk.img
make snod 打包system文件系统,生成system.img
mmm 模块目录(带有Android.mk文件) 单独编译某个模块或程序
mm 在模块目录下面执行,单独编译某个模块或程序

第三章 Android 系统的启动

Android系统的启动过程(简答题)

​ 1、开机上电后,启动BootLoader初始化堆栈和内存空间,为加载内核提供条件。

​ 2、BootLoader把Linux内核加载到内存,挂载根文件系统。

​ 3、启动init进程,init进程完成解析init.rc等初始化脚本文件,启动linux本地服务,启动Android本地守护进程。

​ 4、init进程解析init.rc启动zygote进程,zygote进程启动DVM,DVM中运行第一个java程序zygoteInit。zygote进程fork出System Server进程,在System Server进程启动和初始化时启动Android系统服务。

​ 5、Android系统服务启动完毕后,System Server进程通知开启Android桌面应用程序。

​ 6、启动完成之后可以创建Android应用程序进程,每一个应用进程都是一个独立的DVM实例,其都是客户端通过Binder机制与System Server进程进行通信,然后System Server进程再通过socket与zygote进程通信,最后由zygote进程fork出来的。这些DVM实例通过共享zygote进程预加载的一部分资源来加快应用启动速度。

init进程完成的四大主要功能

(1)解析init.rc及init.{hardware}.rc初始化脚本文件

(2)监听keychord组合按键事件

(3)监听属性服务

(4)监听并处理子进程死亡事件

Android初始化语言(init.rc)四种类型声明

(1)actions(行为):就是一个command序列,每个action都有一个触发器,决定了触发行为的条件。

(2)commands(命令):是一些命令。

(3)services(服务):就是一个程序,是一个本地守护进程。

(4)options(选项):是service的属性,决定了service的运行机制,状态和功能。

Android中主要的本地守护进程

Service 对应程序
ueventd /sbin/ueventd
console /system/bin/sh
adbd /sbin/adbd
servicemanager /system/bin/servicemanager
vold /system/bin/vold
netd /system/bin/netd
debugged /system/bin/debugged
ril-daemon /system/bin/rild
zygote /system/bin/app_process -Xzygote
/system/bin --zygote–start-system-server
media /system/bin/mediaserver

第四章 Android 编译系统与定制 Android 平台系统

Android.mk文件

Android.mk文件完成的操作

(1)指定当前模块的目录

(2)清除所有的local_xx变量

(3)指定源码文件

(4)指定编译细节

(5)指定目标模块名

(6)指定目标模块类型

(7)在Android源码的编译系统中编译Android应用程序、库、可执行程序

Android.mk中的include的主要预定义编译变量有: BUILD_SHARD_LIBRARY、 BUILD_STATIC_LIBRARY、BUILD_EXECUTABLE、BUILD_PACKAGE。

Android.mk中主要预定义编译变量

编译变量 功能
BUILD_SHARED_LIBRARY 将模块编译成共享库
BUILD_STATIC_LIBRARY 将模块编译成静态库
BUILD_EXECUTABLE 将模块编译成可执行文件
BUILD_JAVA_LIBRARY 将模块编译成Java类库
BUILD_PACKAGE 将模块编译成Android应用程序包

注:上述预定义编译变量的定义在build/core/definitions.mk中。

编译HelloWorld应用程序的步骤(简答题)

(1)打开eclipse开发环境,创建一个新的Android工程名为HelloWorld,对工程进行配置。

(2)将创建的HelloWorld工程复制到源码目录中的packages/apps目录下,并且删除eclipse自动生成的文件,保留项目目录结构。

(3)修改HelloWorld工程的Android.mk文件:可以仿照Android自带应用程序的Android.mk文件,将Android.mk文件复制到HelloWorld中,然后进行修改。重点是将LOCAL_PACKAGE_NAME修改为程序的项目名,也就是HelloWord,才能编译成功。

(4)回到android源码目录下,初始化编译环境。然后使用lunch选择编译目标项。

(5)编译HelloWorld工程:可以重新回到工程目录中去使用mm命令进行编译,可以使用mmm命令指定工程位置进行编译,也可以整个工程重新编译,不过这样太慢,没有必要。

(6)回到源码目录中,使用make snod重新生成镜像文件system.img

(7)启动模拟器,查看HelloWorld应用程序运行效果

定制开机界面

(1)制作和Android模拟器长宽一致的图片,图片的格式需要为16色位图bmp格式。

(2)需要生成图片数据数组,将图片的数据保存在c语言的数组中,然后以头文件的形式引入到我们需要操作的文件中使用。这个过程可以使用一款图片转换工具Image2Lcd。导出数据,保存为mylogo.h文件,做为头文件。

(3)将导出的mylogo.h文件复制到虚拟机中Ubuntu环境下。Goldfish目录中的devices/video目录中,在这个目录中更改framebuffer驱动,进而修改开机画面。

(4)修改goldfishfb.c文件,这个文件为framebuffer驱动的主要文件,在这里面修改开机画面输出内容。

(5)重新编译goldfish内核,编译成功后zImage文件成功更新。

(6)运行Android模拟器查看结果

定制系统开机动画

蒙版动画

(1)使用 Photoshop图像处理软件制作一张中间镂空的PNG格式的图片,命名为:android-logo-mask.png.

(2)将android-logo-mask.png复制到 frameworks/base/core/res/assets/images/目录下替换Android默认的图片,为了防止源码不编译图片资源,将图片时间戳更新一下。

(3)重新编译Android的系统资源包framework-res.apk。

(4)make snod 重新生成系统镜像文件system.img

(5)运行Android模拟器查看结果

逐帧动画

(1)创建存放有图片的两个目录p1和p2,和动画属性的描述文件desc.txt

(2)将三个文件以不压缩的形式打包为bootanimation.zip

(3)将bootanimation.zip放在system/media目录

(4)运行Android模拟器查看结果

第五章 JNI机制

JNI概述

使用JNI通常的场景

(1)对处理速度有要求

(2)硬件控制

(3)复用本地代码

JNI的数据类型和其签名

类型和签名对照表

JAVA类型 JNI类型 签名
boolean jboolean Z
byte jbyte B
char jchar C
short jshort S
int jint I
long jlong J
float jfloat F
double jdouble D
引用类型 jobject L
void V
数组类型 jarray [
二维数组类型 [[

注:void 用于方法的返回值。

方法签名相关(非常重要)

JAVA方法 JNI方法签名
boolean isLedOn(); ()Z 或者 (V)Z
double Div(int x , int y); (II)D
int GetNameBy(int ModuleID); (I)I
int GetArrayElementNumber(long[] num ); ([J)I
double Sub(double x , double y); (DD)D
void setLedOn(int ledNo); (I)V
String substr(String s, int idx, int count); (Ljava/lang/String;II)Ljava/lang/String;
char fun(int n, String s, int[] count); (ILjava/lang/String;[I)C
char fun(int n, String s, int[] value); (ILjava/lang/String;[I)C
boolean showMsg(android.View v, String msg); (Landroid/View;Ljava/lang/String;)Z
int GetLedCount(); ()I 或者 (V)I
String GetMoudleNameBy(int ModuleID); (I)Ljava/lang/String;
long GetArrayElement(long[] num , int index); ([JI)J
float Add(float x , float y); (FF)F
boolean isSameName(String mName); (Ljava/lang/String;)Z
void function(); ()V 或者 (V)V

注:事实上当方法没有参数时,括号内是不需要写V的。

JNI引用类型

JNI引用类型 Java引用类型
jobject java.lang.Object类型
jclass java.lang.Class类型
jstring java.lang.String类型
jarray 数组类型
jobjectArray 对象数组类型
jbooleanArray 布尔数组类型
jbyteArray 字节数组类型
jcharArray 字符数组类型
jshortArray 短整型数组类型
jintArray 整型数组类型
jlongArray 长整型数组类型
jfloatArray 浮点数组类型
jdoubleArray 双精度浮点型数组类型
jthrowable java.lang.Throwable类型

​ 注:jobject表示一个对象,所有的引用类型都是jobject的子类,jclass对象表示对应的一个类(对应类的class文件)。注意常用 jstring数组类型。

JNI 访问 Java成员

获得Java类

jclass clazz = env->FindClass("java/lang/String"); // 通过类全名获得类,包名间隔用 / 代替 .
jclass clazz = env->GetObjectClass(obj);	// 通过jobject对象获得类

获得Java属性ID和方法ID

jfieldIDjmethodID 用来标识java对象的属性和方法。需要获得ID才能对属性和方法进行操作。而由于java中的重载机制,所以需 要使用签名确定属性和方法。

// 第一个参数为要查找的类的jclass对象,第二个参数为属性或方法名,第三个参数为属性或方法签名
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig);	// 获取属性ID
jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig);	// 获取静态属性ID
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig);	// 获取方法ID
jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig);	// 获取静态方法ID

JNI操作Java属性和方法

// 获取和设置java属性
j<类型> Get<类型>Field(jobject obj, jfieldID fieldID);	// 通过fieldID从一个object中获取属性
j<类型> GetStatic<类型>Field(jobject obj, jfieldID fieldID);	// 通过fieldID从一个object中获取静态属性
void Set<类型>Field(jobject obj, jfieldID fieldID, j<类型> val);	// 向一个对象的一个属性设置值
void GetStatic<类型>Field(jobject obj, jfieldID fieldID, j<类型> val);	// 向一个对象的一个静态属性设置值
// 调用Java方法 <类型>为方法返回值的类型
// 调用Java成员方法
Call<类型>Method(jobject obj, jmethodID methodID, ...);	// 第三个参数以参数列表形式传参
Call<类型>MethodV(jobject obj, jmethodID methodID, va_listargs);	// 第三个参数以向量表形式传参
Call<类型>MethodA(jobject obj, jmethodID methodID, const jvalue* args);	// 第三个参数以jvalue数组形式传参
// 调用Java静态方法
CallStatic<类型>Method(jobject obj, jmethodID methodID, ...);
CallStatic<类型>MethodV(jobject obj, jmethodID methodID, va_list args);
CallStatic<类型>MethodA(jobject obj, jmethodID methodID, const jvalue* args);

本地代码创建Java对象

// 创建对象的函数和调用方法类似,只不过是调用的是类的构造方法,第二个参数是类对应构造方法的ID,第三个参数是传递构造方法的参数
jobject NewObject(jclass clazz, jmethodID methodID, ...);
jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args);
jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue* args);

本地代码操作 Java String 对象

jstring NewString(const jchar* unicode, jsize len);	// 根据传入的宽字符串创建一个String对象
jstring NewStringUTF(const char* utf);	// 根据传入的UTF-8字符串创建一个String对象

// isCopy的值可以为NULL、JNI_TRUE、JNI_FALSE
// 如果是 JNI_TRUE 的话表示将String复制到新开辟的内存地址中,返回该地址的指针
// 如果是 JNI_FALSE 的话表示将直接返回String的内存指针,这个时候不要修改内存中的内容,否则破坏了java中字符串不可变的规定
// 如果是 NULL 的话表示不关心是否复制
const jchar* GetStringChars(jstring str, jboolean* isCopy);	// 将一个jstring对象转换为宽字符串(UTF-16编码)
const char* GetStringUTFChars(jstring str, jboolean* isCopy);	// 将一个jstring对象转换为字符串(UTF-8编码)

// 当获取到的字符不再使用的时候要使用以下两个函数对应的释放内存
ReleaseStringChars(jstring jstr, const jchar* str);	// 第一个参数为本地字符串的资源,第二个参数为本地字符串
ReleaseStringUTFChars(jstring jstr, const char* str);

本地代码操作 Java数组

// 对象类型数组的操作
jsize GetArrayLength(jarray array);	// 获取数组长度
jobjectArray NewObjectArray(jsize len, jclass clazz, jobject init);	// 创建一个新的数组对象
jobject GetObjectArrayElement(jobjectArray array, jsize index);	// 获取数组中的元素
void SetObjectArrayElement(jobjectArray array, jsize index, jobject val);	// 设置数组的元素

// 基本类型数组的操作
// isCopy的值可以为NULL、JNI_TRUE、JNI_FALSE,同String对象的处理
j<类型>* Get<类型>ArrayElements(j<类型>Array array, jboolean* isCopy);
// 该函数用于释放数组,第三个参数mode可以取到以下值
// 0:对Java数组进行更新并释放C/C++数组
// JNI_COMMIT:对Java的数组进行更新但是不释放C/C++的数组
// JNI_ABORT:对Java的数组不进行更新,释放C/C++的数组
void Release<类型>ArrayElements(j<类型>Array array, j<类型>* elems, jint mode);

局部引用和全局引用(填空题&简答题)

局部引用和全局引用对比

局部引用:

​ (1)只在上层Java调用本地代码的函数内有效,当本地代码返回时,局部引用自动回收。

​ (2)局部引用只在创建他们的线程里有效,本地代码不能将局部引用在多线程之间传递,使用时不能在一个线程调用另一个线程创 建的局部引用,不能将一个局部引用保存在全局变量中在其他线程使用。(一定不能使用)

​ (3)默认传递给本地代码的引用都是局部引用,所有JNI函数的返回值都是局部引用。

全局引用:

​ (1)只有显式通知VM时,全局引用才会被回收,否则一直有效,Java的GC不会释放该引用的对象。

​ (2)全局引用可以跨多个线程使用,可以将全局引用保存在全局变量中或者使用另一个线程访问。(不过不建议保存在全局变量)

​ (3)全局引用在程序员手动释放之前一直有效,使用全局引用必须手动创建销毁,可以使用局部引用创建全局引用,不使用全局引 用时必须手动释放。

局部引用和全局引用创建

jobect NewLocalRef(jobject ref);	// 创建指向某个对象的局部引用,失败返回NULL
void DeleteLocalRef(jobject obj);	// 删除局部引用
jobject NewGlobalRef(jobject lobj);	// 创建指向某个对象的全局引用,失败返回NULL,全局引用只能由此函数创建
void DeleteGlobalRef(jobject gref);	// 删除全局引用,全局引用不再访问之后必须要调用此函数销毁,若调用失败JVM不会回收该对象

局部引用需要手动释放的情况

​ (1)本地代码访问一个很大的java对象,使用完对象之后本地代码去执行耗时操作,这时本地代码还没有返回,需要手动释放引用对象。

​ (2)本地代码创建了大量的局部引用,导致JNI局部引用表溢出,这个时候需要及时的删除不被使用的局部引用

​ (3)不返回的本地函数,如果函数中持续执行耗时操作不返回(比如无限循环),需要避免因无限创造局部引用导致内存泄漏手动释放局部引用。

Java环境中保存JNI对象

​ 最好不要使用全局引用的方式在本地保存JNI的对象,一般可以在Java域中定义一个int类型的属性,在本地层将本地对象的指针转换为int类型,然后设置到Java域的属性中,然后需要的时候从Java域获取该int值,再转换为对应的指针类型,然后就可以通过指针获取值了。(不过需要注意的是需要创建的对象一定是在堆上创建的,要不然对象在函数结束之后就会被回收)

// 保存JNI对象
static void java_native_setup(JNIEnv* env, jobject thiz)
{
    JNIExcampleContext* context = new JNIExampleContext(env);	// 在堆上创建对象,如果在栈上创建函数结束后会被回收
    jclass clazz = env->GetObjectClass(thiz);	// 获取jclass对象
    jfieldID fieldId = env->GetFieldID(clazz, "mNativeContext", "I");
    env->SetIntField(thiz, fieldId, (int)context);	// 将对象转换为int类型设置到属性中
}
// 获取JNI对象
static void java_native_get(JNIEnv* env, jobject thiz)
{
    jclass clazz = env->GetObjectClass(thiz);
    jfieldID fieldId = env->GetFieldID(clazz, "mNativeContext", "I");
    // 取出int类型值转换为指针类型
    JNIExcampleContext* context = (JNIExcampleContext*)(env->GetIntField(thiz, fieldId));
    // 进行后续操作
}

本地方法的注册(重要)

​ 需要将本地函数与Java中的native方法关联起来,这是通过一个结构体JNINativeMethod实现的。这个结构体实现了本地函数和Java方法的映射。

typedef struct {
    char* name;			// Java方法名
    char *signature;	// 本地签名表示字符串
    void* fnPtr;		// Java方法对应的本地函数指针
} JNINativeMethod;

​ 当Java代码中通过System.loadLibrary方法加载本地库的时候,本地代码库被JVM加载,JVM自动调用本地代码中的JNI_OnLoad函数。这个函数实现了本地方法注册的流程。

jint JNI_OnLoad(JavaVM* vm, void* reserved); // 第一个参数表示了一个JVM实例,第二个参数是保留的

​ 在JNI_OnLoad函数中需要做的工作:

​ (1)调用GetEnv函数,获取到JNIEnv实例,这是Java运行环境,里面封装了许多本地操作

​ (2)通过RegisterNatives函数注册本地方法

​ (3)返回JNI版本号

jint RegisterNatives(jclass clazz, const JNINativeMethod *methods, jint nMethods); // 注册映射关系
jint UnregisterNatives(jclass clazz);	// 删除映射关系

JNI 实例(非常重要)

在Linux操作系统中硬件通常有:open、read、write、close等相关操作接口,每个设备硬件还有一些自己的属性。

(1)用Java编写一个Screen “屏幕”设备类

(2)设备的初始化,设备打开,关闭,读/写都交给本地代码去实现。

​ a. 当写屏幕设备时,将写入内容存放在本地代码缓冲区中

​ b. 当读屏幕设备时,则将数据经过简单处理读取出来

例如:向Screen中写人a〜z的小写字母,读出来变成A〜Z的大写。

在Ubuntu系统中编写Java程序和本地C++代码,编写Makefile文件用来编译Java代码和本地代码库,最终正常运行Java与C++本地代码。

// ScreenTest.java
package com.test.practice4_8;

// 定义一个屏幕类
class Screen {

    // 静态块 在类第一次被加载的时候调用
    static {
        System.loadLibrary("screen");
    }

    private String mDevName; // 设备名
    private int mDevNo; // 设备号
    private boolean isInit = false; // 是否被初始化
    private int mWidth; // 宽度
    private int mHeight; // 高度

    public Screen() {
        mDevName = null;
        mDevNo = 0;
    }

    // 返回初始化状态
    public boolean isInit(){
        return isInit;
    }
    // 获取屏幕宽度
    public int getWidth(){
        return mWidth;
    }
    // 获取屏幕高度
    public int getHeight(){
        return mHeight;
    }

    // 打印设备的信息
    public void printInfo(){
        System.out.println("屏幕名:" + mDevName);
        System.out.println("设备号:" + mDevNo);
        System.out.println("屏幕宽度:" + mWidth);
        System.out.println("屏幕高度:" + mHeight);
    }

    public native boolean open();                   // 开启设备
    public native int read(byte[] data, int len);   // 读取设备
    public native int write(byte[] data, int len);  // 写入设备
    public native void close();                     // 关闭设备
}

// 测试效果
public class ScreenTest {

    public static void main(String[] args) {
        Screen dev = new Screen();
        // 开启设备
        if (!dev.open()){
            System.out.println("屏幕开启错误");
            return;
        }
        // 如果开启成功打印信息
        dev.printInfo();

        // 往屏幕里写数据
        byte[] data = new byte[26];
        for (int i = 0; i < data.length; i++){
            data[i] = (byte)(97 + i);
        }
        System.out.println("向屏幕写入 a-z:");
        dev.write(data, data.length);
        // 从屏幕里读数据
        byte[] buf = new byte[64];
        int size = dev.read(buf, buf.length);   // size为实际读出的字节个数
        if (size < 0) {
            System.out.println("从屏幕读取失败");
            return;
        }
        System.out.println("从屏幕读取成功 A-Z");
	System.out.print("大写字母:");
        for (int i = 0; i < 26; i++) {
            System.out.print((char) buf[i] + " ");
        }
        System.out.println();
        // 关闭设备
        dev.close();
    }
}
// com_test_practice4_8_ScreenTest.cpp
#include 
#include 
#include 
#include 
#include 

typedef struct _screen {
    jclass clazz;
    jfieldID id_dev_name;
    jfieldID id_dev_no;
    jfieldID id_is_init;
    jfieldID id_width;
    jfieldID id_height;
} screen, *screen_ptr;

screen_ptr gfieldID;
// 读写缓冲区
char _data[64];

// 初始化屏幕的相关ID信息,起到缓存的作用
static int native_id_init(JNIEnv *env) {

    gfieldID = (screen_ptr)malloc(sizeof(screen));
    jclass _clazz = env->FindClass("com/test/practice4_8/Screen");
    gfieldID->clazz = _clazz;
    gfieldID->id_dev_name = env->GetFieldID(_clazz, "mDevName", "Ljava/lang/String;");
    gfieldID->id_dev_no = env->GetFieldID(_clazz, "mDevNo", "I");
    gfieldID->id_is_init = env->GetFieldID(_clazz, "isInit", "Z");
    gfieldID->id_width = env->GetFieldID(_clazz, "mWidth", "I");
    gfieldID->id_height = env->GetFieldID(_clazz, "mHeight", "I");
    if(gfieldID == NULL){
        return -1;
    }
    return 0;
}

static jboolean native_open(JNIEnv *env, jobject thiz){
    if (native_id_init(env) != 0){
        return JNI_FALSE;
    }
    jstring dev_nm = env->NewStringUTF("NAPO LCD Device");
    if (dev_nm == NULL){
        return JNI_FALSE;
    }
    env->SetObjectField(thiz, gfieldID->id_dev_name, dev_nm);
    env->SetIntField(thiz, gfieldID->id_dev_no, 0x1024);
    env->SetBooleanField(thiz, gfieldID->id_is_init, JNI_TRUE);
    env->SetIntField(thiz, gfieldID->id_width, 2048);
    env->SetIntField(thiz, gfieldID->id_height, 800);
    return JNI_TRUE;
}

static jint native_read(JNIEnv *env, jobject thiz, jbyteArray arr, jint len){
    if (len < 0) {
        return len;
    }
    // arr数组 的 内存地址就是java中的那一个byte数组的内存地址
    // 获得java层定义的数组
    jbyte* byte_arr = env->GetByteArrayElements(arr, NULL);
    int i = 0;
    for ( ; i < len; i++) {
        if (_data[i] - 32 < 0) break;
        byte_arr[i] = _data[i] - 32;
    }
    env->ReleaseByteArrayElements(arr, byte_arr, 0);
    return i;
}

static jint native_write(JNIEnv *env, jobject thiz, jbyteArray arr, jint len){
    if (len > sizeof(_data) || len <= 0){
        return len;
    }
    // 获得java层定义的数组
    jbyte *byte_arr = env->GetByteArrayElements(arr, NULL);
    int i = 0;
    printf("小写字母:");
    for (; i < len; i++) {
        _data[i] = byte_arr[i];
        printf("%c ", _data[i]);
    }
    printf("\n");
    env->ReleaseByteArrayElements(arr, byte_arr, JNI_ABORT);
    return i;
}

static void native_close(JNIEnv *env, jobject thiz){
    env->SetBooleanField(thiz, gfieldID->id_is_init, JNI_FALSE);
    free(gfieldID);
    gfieldID = NULL;
}

// String fun(String s, int[] is, byte b)  的签名  (Ljava/lang/String;[IB)Ljava/lang/String;
// 该数组描述了一个 java方法 -> 本地函数的映射关系
static const JNINativeMethod gMethods[] = {
        {(char*)"open", (char*)"()Z", (void*)native_open},
        {(char*)"read", (char*)"([BI)I", (void*)native_read},
        {(char*)"write", (char*)"([BI)I", (void*)native_write},
        {(char*)"close", (char*)"()V", (void*)native_close},
};

// 将映射关系数组注册到JVM中 java virtual machine
static int registerMethods(JNIEnv* env){
    static const char* className = "com/test/practice4_8/Screen";
    jclass clazz = env->FindClass(className);
    if(clazz == NULL){
        printf("没发现这个类\n");
        return -1;
    }
    if(env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK) return -1;
    return 0;
}

// 相当于本地的入口
jint JNI_OnLoad(JavaVM* vm, void* reserved){
    JNIEnv *env = NULL;
    jint result = -1;
    if(vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK){
        printf("获取ENV失败\n");
        goto fail;
    }
    if(env == NULL){
        goto fail;
    }
    if(registerMethods(env) != 0){
        printf("注册方法失败\n");
        goto fail;
    }
    result = JNI_VERSION_1_4;
    fail:
    	return result;
}
// java类 Screen 工作:static块中 使用System.loadLibrary(类全名);加载本地库   声明native方法  定义了一些描述这个类的属性
// 本地库  声明一个结构体描述Screen类 jclass 各种属性的id       四个native方法的实现
// 方法的注册
# Makefile编写
libscreen.so: com_test_practice4_8_ScreenTest.cpp ScreenTest.class
	g++ -I ~/aosp/jdk1.6.0_29/include -I ~/aosp/jdk1.6.0_29/include/linux/ $< -fPIC -shared -o $@
ScreenTest.class: ScreenTest.java
	javac -d ./ $<
clean:
	$(RM) ScreenTest.class Screen.class libscreen.so
# 启动脚本
#!/bin/bash
java -Djava.library.path='.' com/test/practice4_8/ScreenTest

第六章 Android的对象管理

Android的对象管理采用了智能指针,其中LightRefBase类是轻量级指针类RefBase类是重量级指针类,RefBase 类中不仅仅定义了 mStrong 强引用计数,而且还有一个 mWeak 的弱引用计数,强引用计数主要被 sp 对象管理,弱引用计数主要被 wp 对象管理。

智能指针的类型有 轻量级指针强弱指针

智能指针

C++代码中创建对象的两种方式:创建栈对象创建堆对象

class A {
    public:
    	A();
    	~A();
    private:
    	int mVar1;
    	int mVar2;
}
int main(int argc, char* argv){
    A a();	// 创建栈对象
    A* a_ptr = new A();	// 创建堆对象
}

在栈上创建对象是局部的,出了作用域就会被销毁,在堆上创建对象管理非常的复杂,必须要手动的进行销毁。实现了智能指针就如同java中的引用管理一样,能够通过引用计数智能的管理对象和引用,智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。智能指针的引用管理sp和wp使用了模板template实现了泛型,只要继承了智能指针的基类LightRefBaseRefBase就能被管理,体现了多态的思想。而且智能指针类中使用了重载运算符,重载了operator->operator*来返回原始对象指针,能够让智能指针使用起来就像原始对象指针一样。

智能指针的概念:当类中有指针成员时,一般有两种方式来管理指针成员:一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享。

轻量级指针

轻量级指针类的成员

类中定义一个mCount变量,初值为0;两个成员函数 incStrong和decStrong 来维护引用计数器的值。

轻量级指针实现的规则

(1)LightRefBase类或其子类的对象通过智能指针sp管理

(2)当使用智能指针sp指向、赋值、初始化LightRefBase对象时,该引用对象计数加1

(3)当sp指针使用完后,其指向的对象引用计数自动减1

(4)当LightRefBase对象的引用计数为0时,该对象会被删除

强引用和弱引用

强引用指针 sp:表示对一个对象的强引用关系,可以直接访问目标成员对象。

弱引用指针 wp:表示对一个对象的弱引用关系,不能像sp一样可以直接访问目标对象成员,如果想要访问的话,必须调用promote()函数由弱引用升级成强引用。

强引用增加时,强弱引用计数都增加,强引用减少时,强弱引用计数都减少

弱引用增加时,只有弱引用计数增加;弱引用减少时,只有弱引用计数减少

第七章 Binder 通信

Binder框架定义了四个角色,它们分别是:Server、Client、ServerManager (SMgr) 、Binder驱动 。其中前三者运行在用户空间,第四者运行于内核空间。

传统的Linux系统IPC通信机制:管道、消息队列、共享内存、套接字、信号量、信号


Binder通信和传统的Linux进程间通信方式相比有什么优点

(1)安全:传统的IPC(套接字、管道、消息队列)的安全机制依赖上层协议,例如:

​ a、Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志。

​ b、传统IPC的接收方无法获得对方进程可靠的UID/PID(用户ID/进程ID),从而无法鉴别对方身份。

​ c、使用传统IPC只能由用户在数据包里填入UID/PID,但这样不可靠,容易被恶意程序利用,故可靠的身份标记只有由IPC机制本身在内核中添加。

​ d、其次传统IPC访问接入点是开放的,无法建立私有通道。比如命名管道的名称,system V的键值,socket的ip地址或文件名都是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。

(2)性能高:传统的IPC(套接字、管道、消息队列)需要拷贝两次内存、Binder只需要拷贝一次内存、共享内存不需要拷贝内存。

(3)使用简单:采用了C/S架构和面向对象的调用方式,屏蔽了实现的细节,在使用Binder时,就跟调用一个本地对象实例一样。

Binder的操作步骤

(1)Server 注册服务

​ a、Server进程向Binder驱动发起注册服务请求;

​ b、Binder驱动将注册请求转发给Service Manager进程;

​ c、Service Manager进程添加该Server服务,Service Manager中保留了该Server的信息和其引用,并且Binder驱动在内核空间保存了这个Server的实体;

(2)Client 获取服务

​ a、Client向Binder驱动发起获取服务的请求,并传递要获取的服务名称;

​ b、Binder驱动将该请求转发给Service Manager进程;

​ c、Service Manager查找到Client需要的Server对应的Binder实体的Binder引用(也就是Binder实体的代理对象)并且返回给Binder驱动;

​ d、Binder驱动将Binder代理对象返回给Client(这个时候Client和Server已经建立了连接);

(3)Client 使用服务

​ a、Binder驱动为跨进程通信做准备,实现内存映射(调用了mmap系统函数);

​ b、Client进程将参数数据发送到Server进程;

​ c、Server进程根据Client进程的要求调用目标方法;

​ d、Server进程将目标方法的结果返回给Client进程;

注:Server可以通过已经建立的实名Binder连接,将创建的Binder实体传递给Client,因为这个Binder没有向Service Manager注册信息,所以别的进程就无法通过任何方式获取这个Binder的引用,这是一个匿名Binder。

第八章 Android HAL 硬件抽象层

HAL介绍

为什么要使用:Google为了使Android系统的上层应用对底层硬件屏蔽的一个软件层次。有些硬件厂商不希望自己的核心代码公开出来,所以Google加上了硬件抽象层避开GPL协议的约束。

Android HAL 的架构形式:Module架构(已经过时) 和 Stub代理架构

HAL使用方式:HAL的代码被编译生成动态模块库,可以动态加载到内核中运行,Android应用程序和框架通过JNI加载并调用HAL module 库代码,在HAL module 库再去访问设备驱动。

HAL Stub 架构

为什么弃用Module架构改用Stub架构

Module架构中,本地代码由so库实现,上层直接将so库映射进进程空间,会有代码重入以及设备多次打开的问题。而Stub架构虽然也要加载module库,但是其保存的只是底层Stub提供的操作接口,当Stub第一次使用时硬件打开,之后使用时仅仅是返回操作接口,解决了硬件重复打开问题,而多进程访问时由于返回的只是操作接口(函数指针),所以没有重入问题。

HAL Stub 架构分析

HAL Stub 架构简称 321架构。因为其只需要三个结构体、两个常量、一个函数

三个结构体

// 表示了一个硬件
struct hw_module_t{
    uint32_t tag;				// 该值必须声明为HARDWARE_MODULE_TAG
    uint16_t version_major;		 // 主版本号
    uint16_t version_minor;		 // 次版本号
    const char* id;				// 硬件id名,唯一标识module
    const char* name;			// 硬件module名字
    const char* author;			// 作者
    struct hw_module_methods_t* methods;	// 指向封装有open函数指针的结构体
    void* dso;					// 动态共享库,指向打开的so库的指针
    uint32_t reserved[32];		// 128字节补齐
};
// 封装了open函数
struct hw_module_methods_t{
    int (*open)(const struct hw_module_t* module, const char* id, struct hw_device_t** device);
};
// 表示了一个操作接口
struct hw_device_t{
    uint32_t tag;								// 必须赋值为HARDWARE_DEVICE_TAG
    uint32_t version;							// 版本号
    struct hw_module_t* module;					 // 说明这个操作接口属于哪一个硬件
    uint32_t reserved[12];						 // 字节补齐
    int (*close)(struct hw_device_t* device);		//  
}

两个常量

#define HAL_MODULE_INFO_SYM HMI				// Stub 硬件模块对象固定的名字
#define HAL_MODULE_INFO_SYM_AS_STR "HMI"	// Stub 硬件模块对象固定的字符串形式的名字

一个函数

// 第一个参数是硬件id,第二个参数是要获取的module,使用该函数初始化一个hw_module_t指针
int hw_get_module(const char* id, const struct hw_module_t** module);

HAL Stub 注册

1、需要修改一下结构体

struct led_module_t {
	struct hw_module_t common;
	// 后面可以放置扩展的属性
};

struct led_control_device_t {
	struct hw_device_t common;
	// 后面是为操作接口扩展的操作函数
	int (*getcount_led)(struct led_control_device_t *dev);
	int (*set_on)(struct led_control_device_t *dev, int arg);
	int (*set_off)(struct led_control_device_t *dev, int arg);
};

一定要注意的是common必须要做为第一个数据项。这样通过强制类型转换实现结构体的“多态”时才不会出错。

注:

c语言中结构体是没有继承关系的,其实是一种巧妙的实现。在相同的机器上,一块内存地址的大小是相同的,所以指针的大小是没有区别的,比如32位机的指针都是4个字节大小。而不同类型的指针的不同点,就是在从指针所指向的地址取值的时候,会按照这个指针所属的类型对地址内容进行解析,而不同类型的大小可能是不同的,所以以指针地址为起点解析的内存块数是不同的,解析出来的结果也是不同的(例如如果int类型大小为4个字节,float类型大小为8个字节,有一个指针指向0x1000,若这个指针是int型指针,则取值时会从0x1000,0x1001,0x1002,0x1003 中解析取出一个int类型的值,若这个指针是float类型,则会从0x1000,0x1001,0x1002,0x1003,0x1004,0x1005,0x1006,0x1007 中解析取出一个float类型的值)。我们将struct led_module_t类型的指针强制类型转换成struct hw_module_t类型的指针,就只能访问结构体头common大小的内容,后面扩展的属性就因为解析内容时范围缩小而不能访问了,所以必须要将common做为第一个数据项。

而思考一下Java或者C++中的多态,也是类似的。可以认为基类是一个结构体,里面包含了各种属性和方法,而扩展类在原来的数据项后面添加上扩展的属性和方法,而将扩展类上转型为父类,新增的属性和方法也就无法访问了,而函数和属性的重写也就是替换掉了原来的数据项的位置,所以依然可以访问,但是实现已经发生了改变,从而实现了多态。

2、实现操作接口里对硬件操作的函数

static int led_device_close(struct hw_device_t* device)
{
	struct led_control_context_t* ctx = (struct led_control_context_t*)device;
	if (ctx) {
		free(ctx);
	}
	close(fd);
	return 0; 
}
static int led_getcount(struct led_control_device_t *dev)
{
	LOGI("led_getcount");
	return 4;
}

static int led_set_on(struct led_control_device_t *dev, int arg)
{    
	LOGI("led_set_on");
	ioctl(fd,LED_ON,arg);
	return 0;
} 

static int led_set_off(struct led_control_device_t *dev, int arg)
{
	LOGI("led_set_off");
	ioctl(fd,LED_OFF,arg);
	return 0;
}

3、需要实现设备打开的函数,并将其封装到 struct hw_module_methods_t 结构体中去,将扩展的函数封装到struct led_control_device_t结构体中去

// open函数实现
static int led_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device)
{
	struct led_control_device_t* context;
	context = (struct led_control_device_t*)malloc(sizeof(*context));	// 在堆上动态开辟内存空间
	memset(context, 0, sizeof(*context));
	// 硬件抽象层必须要设置的初始化属性
	context->common.tag= HARDWARE_DEVICE_TAG;
	context->common.version = 0;
	context->common.module = module;
	context->common.close = led_device_close; 
	// 初始化扩展的控制LED函数
	context->set_on = led_set_on;
	context->set_off = led_set_off;
	context->getcount_led = led_getcount;
	*device = (struct hw_device_t*)context;
	if((fd=open("/dev/led", O_RDWR))==-1)
	{
		LOGI("open error\n");
		exit(1);
	}
    else LOGI("open ok\n");
	return 0;
}
// 将open函数封装到 struct hw_module_methods_t 结构体中去
static struct hw_module_methods_t led_module_methods = {
	open: led_device_open  
};

4、模块变量的名字必须为HMI或者是HAL_MODULE_INFO_SYM(只有是这个名字才能从上层调用时使用hw_get_module函数找到它)

const struct led_module_t HAL_MODULE_INFO_SYM = {
	common: {
		tag: HARDWARE_MODULE_TAG,
		version_major: 1,
		version_minor: 0,
		id: LED_HARDWARE_MODULE_ID,
		name: "led HAL module",
		author: "farsight",
		methods: &led_module_methods,
		}, 
		// 后面是扩展属性的初始化
};

用语言描述HAL的实现(简答题)

(1)定义结构体hw_module_t 表示硬件、hw_module_methods_t 封装open函数、hw_device_t 表示硬件操作接口。

(2) hw_module_t中的methods指向hw_module_methods_t,是hw_module_methods_t类型的指针。

(3)hw_module_methods_t中的open是指向初始化函数的函数指针。

(4)在初始化函数初始化结构hw_device_t,返回硬件操作接口。

(5)定义名字为HMI的module对象,通过hw_get_module函数获取。

第九章 HAL硬件抽象层进阶 Sensor HAL 实例

Android系统内置支持的传感器

加速度传感器、磁力传感器、方向传感器、陀螺仪、环境光照传感器、压力传感器、温度传感器、距离传感器 等。

编写一个传感器的应用程序的步骤

(1)通过调用 Context. getSystemService(SENSOR_ SERVICE)获得传感器服务,实现返回的是封装了 Sensorservice的 Sensor Manager对象。
(2)调用 Sensor Manager. get Default Sensor( SensorTYPE_ ORIENTATION)来获得指定类型的传感器对象,方便获得传感器的数据。
(3)通过 SensorManager registerlistener注册 SensorEvent listener监听器,监听获得的传感器对象,当传感器数据提交上来时,能被应用程序得到。
(4)实现监听器里传感器上报数据的具体操作。

扩展 Makefile

工作原理

1、make 会在当前目录下找名字叫 Makefile 或 makefile 的文件。

2、如果找到,它会找文件中的第一个目标文件(target),并把这个文件作为最终的目标文 件。

3、如果这个目标文件不存在,或是它所依赖的后面的 .o 文件的文件修改时间要比这个文件新,那么,他就会执行后面所定义的命令来生成这个文件。

4、如果这个文件所依赖的.o 文件也不存在,那么 make 会在当前文件中找目标为.o 文件的依赖性,如果找到则再根据那一个规则生成.o 文件。

5、当然,你的.c文件和.h文件是存在的,于是 make 会生成 .o 文 件,然后再用 .o 文件生成 make 的终极任务,也就是执行文件 edit 了。

清空目标文件的规则

clean:

​ rm edit $(objects)

更为稳健的做法是:

.PHONY : clean

clean :

​ rm edit $(objects)

注:.PHONY是伪目标的意思

make的工作方式

1、读入所有的 Makefile。

2、读入被 include 的其它 Makefile。

3、初始化文件中的变量。

4、推导隐晦规则,并分析所有规则。

5、为所有的目标文件创建依赖关系链。

6、根据依赖关系,决定哪些目标要重新生成。

7、执行生成命令。

变量

1、变量在声明时需要给予初值,而在使用时,需要给在变量名前加上“ ” 符 号 , 但 最 好 用 小 括 号 “ ( ) ” 或 是 大 括 号 “ ” 把 变 量 给 包 括 起 来 。 如 果 你 要 使 用 真 实 的 “ ” 符号,但最好用小括号“()”或是大括号“{}”把变量给包括起来。如 果你要使用真实的“ 使”字符,那么你需要用“$$”来表示

2、变量值替换格式:“ ( v a r : a = b ) ” 或 是 “ (var:a=b)”或是 “ (var:a=b){var:a=b}”

其意思是,把变量“var”中所有以“a”字串“结尾”的“a” 替换成“b”字串。

foo := a.o b.o c.o
bar := $(foo:.o=.c)

$(bar)的值就是“a.c b.c c.c”。

或者是使用

foo := a.o b.o c.o 
bar := $(foo:%.o=%.c) 

Makefile变量的赋值

立即变量:定义的时候就已经确定了该变量的值。(:=、+=)

延时变量:使用该变量的时候,才展开该变量,并确定该变量的值。(=、?=、define)

符号 含义 变量类型
= 递归赋值,将整个Makefile文件展开之后,再决定变量的值 延时变量
:= 直接为变量进行赋值 立即变量
+= 在变量后面追加值 立即变量
?= 若前面没有定义该变量,则此处赋值,如果前面已经定义了,则此处不再赋值 延时变量

自动化变量

$@:表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。

$%:仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%“就是"bar.o”,"$@“就是"foo.a”。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。

$<依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。(就是依赖中的第一个)

$?:所有比目标新的依赖目标的集合。以空格分隔。

$^所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。

$+:这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。

Makefile中的关键字

1、override

如果一个变量的值需要在编译选项中指定或由系统传入,那么makefile中可以使用override关键字来设置,使这个变量的赋值被忽略

2、define

使用define关键字可以定义多行变量

define two-lines
    echo foo
    echo $(bar)
endef

3、wildcard

作用是让通配符(Makefile中的通配符就是*, %算pattern,不是通配符)在变量或函数中展开,通常用于提取指定目录的某一类型文件。因为在Makefile的规则中,函数中的通配符是不会被展开的。

all:$(subst .c,.o,$(wildcard *.c))
%.o:%.c
    gcc -o $@ $<

4、export

将变量导出,以便于所有的子makefile都可以使用

5、include

和C语言的#include一样,将后面的文件展开到当前位置

函数

1、字符串替换函数–subst

$(subst , , )

属性 解释
名称 字符串替换函数——subst
功能 把字串中的字符串替换成
返回 函数返回被替换过后的字符串

示例:

$(subst ee,EE,feet on the street)

结果是

fEEt on the strEEt

2、模式字符串替换函数–patsubst

$(patsubst ,,)

属性 解释
名称 模式字符串替换函数——patsubst
功能 查找中的单词(单词以“空格”、“Tab”或“回车”“换行”分 隔)是否符合模式,如果匹配的话,则以替换。 这里可以包括通配符%”,表示任意长度的字串。如果中也包含%”,那么中的这个%”将是中的那个%”所代表的字串。(可以用“\”来转义,以“%”来 表示真实含义的“%”字符)
返回 函数返回被替换过后的字符串

示例:

$(patsubst *.c,*.o,foo.c bar.c)

结果是

foo.o bar.o

3、查找字符串函数–findstring

$(findstring ,)

属性 解释
名称 查找字符串函数——findstring
功能 在字串中查找字串
返回 如果找到,那么返回,否则返回空字符串

示例:

$(findstring a,a b c) 
$(findstring a,b c) 

第一个函数返回"a"字符串

第二个返回""字符串(空字符串)

4、过滤函数–filter

$(filter , )

属性 解释
名称 过滤函数——filter
功能 以模式过滤字符串中的单词,保留符合模式的单词。可以有多个模式
返回 返回符合模式的字串

示例:

sources := foo.c bar.c baz.s ugh.h 
foo: $(sources) 
	cc $(filter %.c %.s,$(sources)) -o foo 

$(filter %.c %.s,$(sources)) 返回的值是

foo.c bar.c baz.s

5、去空格函数–strip

$(strip )

属性 解释
名称 去空格函数——strip
功能 去掉字符串开头和结尾的空字符
返回 返回被去掉空格的字符串值

示例:

$(strip a b c )

结果是

a b c

6、排序函数–sort

$(sort )

属性 解释
名称 排序函数——sort
功能 给字符串中的单词排序(升序)
返回 返回排序后的字符串

示例:

$(sort foo bar lose)

结果是

bar foo lose

备注:sort函数会去掉其中重复的单词

7、取单词函数–word

$(word , )

属性 解释
名称 取单词函数——word
功能 取字符串中第个单词(从1开始)
返回 返回:返回字符串中第个单词。如果n比其中的单词数要大,返回空字符串。

示例:

$(word 2, foo bar baz)

返回值是

bar

8、去单词串函数–wordlist

$(wordlist , , )

属性 解释
名称 去单词串函数——wordlist
功能 从字符串中取从开始到的单词串。是一个数字
返回 返回字符串中从的单词串。如果中的单词数大,那么返回空字符串。如果大于的单词数,那么从开始,到结束的单词串

示例:

$(wordlist 2, 3, foo bar baz)

返回值是

bar baz

9、单词个数统计函数–words

$(words )

属性 解释
名称 单词个数统计函数——words
功能 统计中字符串中的单词个数
返回 返回中的单词数

示例:

$(words,foo bar baz)

返回值是

3

备注:如果我们要取中最后的一个单词,我们可以这样:$(word $(words ),)

10、首单词函数–firstword

$(firstword )

属性 解释
名称 首单词函数——firstword
功能 取字符串中的第一个单词
返回 返回字符串的第一个单词

示例:

$(firstword foo bar)

返回值是

foo

备注:这个函数可以用word函数来实现:$(word 1,)。

11、取目录函数–dir

$(dir )

属性 解释
名称 取目录函数——dir
功能 从文件名序列中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部分。如果没有反斜杠,那么返回"./"
返回 返回文件名序列 的目录部分

示例:

$(dir src/foo.c hacks)

返回值是

src/ ./

12、取文件函数–notdir

$(notdir )

属性 解释
名称 取文件函数——notdir
功能 从文件名序列中取出非目录部分,非目录部分是指最后一个反斜杠(“/”)之后的部分
返回 返回文件名序列的非目录部分

示例:

$(notdir src/foo.c hacks)

返回值是

foo.c hacks

13、取后缀函数–suffix

$(suffix )

属性 解释
名称 取后缀函数——suffix
功能 从文件名序列中获取后缀序列
返回 返回文件名序列的后缀序列,如果文件没有后缀则返回空字串

示例:

$(suffix src/foo.c src-1.0/bar.c hacks)

返回值是

.c .c

14、取前缀函数–basename

$(basename )

属性 解释
名称 取前缀函数——basename
功能 从文件名序列中取出各个文件名的前缀部分
返回 返回文件名序列的前缀序列,如果文件没有前缀,则返回空字串。

示例:

$(basename src/foo.c src-1.0/bar.c hacks)

返回值是

src/foo src-1.0/bar hacks

15、加后缀函数–addsuffix

$(addsuffix ,)

属性 解释
名称 加后缀函数——addsuffix
功能 把后缀加到中的每个单词后面
返回 返回加过后缀的文件名序列

示例:

$(addsuffix .c, foo bar)

返回值是

foo.c bar.c

16、加前缀函数–addprefix

$(addprefix ,)

属性 解释
名称 加前缀函数—— addprefix
功能 把前缀加到中的每个单词前面
返回 返回加过前缀的文件名序列

示例:

$(addprefix src/,foo bar)

返回值是

src/foo src/bar

17、连接函数–join

$(join , )

属性 解释
名称 连接函数——join
功能 中的单词对应地加到的单词后面。如果的单词个数要比的多,那么,中的多出来的单词将保持原样。如果的单词个数要比多,那么多出来的单词将被复制到
返回 返回连接过后的字符串

示例:

$(join aaa bbb,111 222 333)

返回值是

aaa111 bbb222 333

18、遍历函数–foreach

$(foreach , , )

把参数中的单词逐一取出放到参数所指定的变量中,然后再执行所包含的表达式。每一次会返回一个字符串,循环过程中的所返回的每个字符串会以空格分隔,最后当整个循环结束时,所返回的每个字符串所组成的整个字符串以空格分隔,将会是 foreach函数的返回值。所以,最好是一个变量名,可以是一个表达式,而中一般会使用这个参数来依次枚举中的单词。

示例:

names := a b c d 
files := $(foreach n, $(names), $(n).o)

上面的例子中, ( n a m e s ) 中 的 单 词 会 被 挨 个 取 出 , 并 存 到 变 量 “ n ” 中 , " (names)中的单词会被挨个取出,并存到变量“n”中," (names)n,"(n).o"每次根据“ ( n ) ” 计 算 出 一 个 值 , 这 些 值 以 空 格 分 隔 , 最 后 作 为 f o r e a c h 函 数 的 返 回 , 所 以 , (n)”计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以, (n)foreach(files)的值是“a.o b.o c.o d.o"注意, foreach中的参数是一个临时的局部变量, foreach函数执行完后,参数的变量将不在作用,其作用域只在 foreach函数当中。

问题(重要)

1、 Makefile文件的内容如下:

foo=$(bar)
bar=$(ugh)
ugh=Huh?
all:
  echo $(foo)

请写出执行make all后的结果。

答:

Huh?

2、Makefile文件的内容如下:

x=$(y)
y=$(z)
z=good
all:
  echo $(x)

请写出执行make all后的结果。

答:

good

3、请写出$(addprefix src/, foo bar)的返回值

​ 答:

src/foo src/bar

4、

names:=a b c d 
files:=$(foreach n ,$(names),$(n).o)

请写出$(files)的值。

答:

a.o b.o c.o d.o

5、请写出$(suffix src/foo.c src-1.0/bar.c hacks)的返回值

答:

.c .c

6、请写出$(wordlist 2, 3, foo bar baz)返回值

答:

bar baz

扩展 Shell

命令

echo命令

echo -n:不进行换行

echo -e:启用转义字符,echo默认是不会识别转义字符的。

read命令

read variable:读取变量给variable

read x y:可同时读取多个变量

read:自动读给内置的REPLY变量

read -p “please input:”:自动读给内置的REPLY变量,带有提示

注:可以看出read命令后面可以不跟变量,可以带参数,可以跟多个变量。

条件测试

Bash允许测试两种类型的条件:命令成功或失败、表达式为真或假

测试的退出状态为0表示命令成功或表达式为真,非0表示命令失败或表达式为假,状态变量**$?**中保存命令退出状态的值

表达式测试包括:字符串测试、整数测试、文件测试

内置测试命令test测试表达式的值

# 使用test命令测试
x=5;y=10
test $x -gt $y
echo $?
# test命令可以用方括号代替
x=5;y=10
[ $x -gt $y ]	# 方括号的两边必须留空格
echo $?

字符串测试

表达式 解释
[ -z str ] 字符串str长度为零,返回真
[ -n str ] 字符串str长度不为零,返回真
[ str1 = str2 ] 两个字符串相等
[ str1 != str2 ] 两个字符串不相等

整数测试

表达式 解释
[ int1 -eq int2 ] int1 等于 int2
[ int1 -ne int2 ] int1 不等于 int2
[ int1 -gt int2 ] int1 大于 int2
[ int1 -ge int2 ] int1 大于或等于 int2
[ int1 -lt int2 ] int1 小于 int2
[ int1 -le int2 ] int1 小于或等于 int2

整数测试也可以使用let命令或者双圆括号,使用 >、< 、>=、 <=、 ==、 != 操作符,例如:

x=1; let "$x == 1"; echo $?	# 使用let
x=1; (("$x == 1")); echo $?	# 使用双圆括号

逻辑测试

表达式 解释
[ expr1 -a expr2 ] 逻辑与,都为真返回真
[ expr1 -o expr2 ] 逻辑或,存在真返回真
[ ! expr ] 逻辑非

注:表达式不能随便加括号,否则不能正确解析。

文件测试

文件测试:文件是否存在、文件属性、访问权限

表达式 解释
[ -f fname ] fname 存在且是普通文件时返回真
[ -L fname ] fname 存在且是链接文件时返回真
[ -d fname ] fname 存在且是目录时返回真
[ -e fname ] fname 为文件或目录存在返回真
[ -s fname ] fname 存在且大小大于0时返回真
[ -r fname ] fname 存在且可读时返回真
[ -w fname ] fname 存在且可写时返回真
[ -x fname ] fname 存在且可执行时返回真

检查空值

[ "$name" = "" ]
[ ! "$name" ]
[ "X${name}" != "X" ]

控制语句

if条件语句

if expr1
then 
	commands1
elif expr2
then
	commands2
elif expr3
then
	:	# :表示什么都不干
else
	commnads3
fi

case选择语句

case expr in
	pattern1)
	commands1
	;;
	pattern2)
	commands2
	;;
	pattern3)
	commands3
	;;
	*)	# 以上都没有匹配到时执行
	commands3
	;;
esac

for循环语句

for variable in list	# 每一次循环将list的一个值赋给变量
do					# 循环开始
	commands		# 循环体
done				# 循环结束标志

while循环语句

while expr	# 满足条件进入循环
do
	commands
done

until循环语句

until expr	# 不满足条件进入循环
do
	commands
done

select循环语句

# 主要用于创建菜单,菜单项按数字顺序排列,等待用户输入。用户输入的值会保存在内置变量REPLY中
select variable in list
do
	commands
done

字符串操作

操作 解释
${#var} 返回字符串变量var的长度
${var:m} 返回${var}中从第m个字符到最后的部分
${var: m:len} 返回${var}中从第m个字符开始,长度为len的部分
${var#pattern} 删除${var}中开头部分与pattern匹配的最小部分
${var##pattern} 删除${var}中开头部分与pattern匹配的最大部分
${var%pattern} 删除${var}中结尾部分与pattern匹配的最小部分
${var%%pattern} 删除${var}中结尾部分与pattern匹配的最大部分
${var/old/new} 用new替换${var}中第一次出现的old
${var//old/new} 用new替换${var}中所有的old(全局替换)

位置参量

$n:n 为数字,$0代表命令本身,$1- 9 代 表 第 一 到 第 九 个 参 数 , 十 以 上 的 参 数 , 十 以 上 的 参 数 需 要 用 大 括 号 包 含 , 如 9代表第一到第九个参数,十以上的参数,十以上的参数需要用大括号包含,如 9{10}

$#:用来统计参数的个数

$@:会将命令行的所有的参数当做同一个字符串中的多个独立单词

$*:会将命令行的参数当做一个参数来保存

函数

#!/bin/bash
# 函数定义,函数定义时不能有参数,但是调用时可以传参数,并且给它传递什么参数它就接收什么参数
function hello(){
    str = "hello, world"
    echo $str
    return $str	# 可以没有返回值
}
# 函数调用,直接使用函数名即可,后面可以传递参数也可以不传递参数,参数要使用空格隔开
hello

问题(重要)

1、请写出运行下列脚本程序后的输出结果

#!/bin/sh
rm –f file_one	# 删除了文件file_one
if [ -f file_one || echo "hello" || echo "there" ];then # 此时file_one不存在,但是第二项执行成功之后判断成功不会继续执行
  echo "in if"
else
  echo "in else"
fi

答:

hello
in if

2、请写出运行下列脚本程序后的输出结果

#!/bin/sh
for foo in bar fud d3
do 
	echo $foo
done
exit

答:

bar
fud
d3

3、请写出运行下列脚本程序后的输出结果

#!/bin/sh
rm –f ext1
if [ -f ext1 ] && echo "ext2" && echo "ext3"
then
		echo "yes"
else 
		echo "no"
fi 
exit 0

答:

no

4、请写出运行下列脚本程序后的输出结果

#!/bin/sh
for word in happy birthday to you
do 
		echo $word	# 每输出一次换行
done
exit

答:

happy
birthday
to
you

5、请写出运行下列脚本程序后的输出结果

#!/bin/bash
dirname="/usr/bin/local/bin";
echo ${dirname#*bin}	# 字符串处理中判断从开始删除最小匹配项

答:

/local/bin

6、请写出运行下列脚本程序后的输出结果

#!/bin/bash
for name in Tom Dick
do
echo -n "$name"	# echo的-n选项表示输出之后不会换行
done

答:

Tom Dick

题目预测

写JNI的方法签名

JAVA方法 JNI方法签名
boolean isLedOn(); ()Z 或者 (V)Z
double Div(int x , int y); (II)D
int GetNameBy(int ModuleID); (I)I
int GetArrayElementNumber(long[] num ); ([J)I
double Sub(double x , double y); (DD)D
void setLedOn(int ledNo); (I)V
String substr(String s, int idx, int count); (Ljava/lang/String;II)Ljava/lang/String;
char fun(int n, String s, int[] count); (ILjava/lang/String;[I)C
char fun(int n, String s, int[] value); (ILjava/lang/String;[I)C
boolean showMsg(android.View v, String msg); (Landroid/View;Ljava/lang/String;)Z
int GetLedCount(); ()I 或者 (V)I
String GetMoudleNameBy(int ModuleID); (I)Ljava/lang/String;
long GetArrayElement(long[] num , int index); ([JI)J
float Add(float x , float y); (FF)F
boolean isSameName(String mName); (Ljava/lang/String;)Z
void function(); ()V 或者 (V)V

注:事实上当方法没有参数时,括号内是不需要写V的。

列举题

1、Android软件架构列举(五层)

英文名称 中文名称
Application 应用层
Application Framework 应用框架层
Android Runtime & Libraries 运行时库和本地库层
Android HAL 硬件抽象层
Linux Kernel Linux内核层

2、Android中主要的本地守护进程

Service 对应程序
ueventd /sbin/ueventd
console /system/bin/sh
adbd /sbin/adbd
servicemanager /system/bin/servicemanager
vold /system/bin/vold
netd /system/bin/netd
debugged /system/bin/debugged
ril-daemon /system/bin/rild
zygote /system/bin/app_process -Xzygote
/system/bin --zygote–start-system-server
media /system/bin/mediaserver

3、Android.mk中的include的主要预定义编译变量

编译变量 功能
BUILD_SHARED_LIBRARY 将模块编译成共享库
BUILD_STATIC_LIBRARY 将模块编译成静态库
BUILD_EXECUTABLE 将模块编译成可执行文件
BUILD_JAVA_LIBRARY 将模块编译成Java类库
BUILD_PACKAGE 将模块编译成Android应用程序包

注:上述预定义编译变量的定义在build/core/definitions.mk中。

4、JNI将传递给本地代码的对象分为哪两种

局部引用和全局引用。

5、Binder框架定义的四个角色是

Server、Client、ServerManager (SMgr) 、Binder驱动。前三者运行在用户空间,驱动运行在内核空间。

6、Binder中常用的命令

消息 含义 参数
BINDER_WRITE_READ 该命令向 Binder写入或读取数据。参数分为两段:写部分和读部分。如果 write size不为0就先将write buffer里的数据写人 Binder;如果 read size不为0再从 Binder中读取数据存人 read buffer中。write consumed和 read consumed表示操作完成时Binder驱动实际写入或读出的数据个数 struct binder_write_read{
signed long write_size;
signed long write_ consumed;
unsigned long write_buffer;
signed long read_size sIgned long read_consume; unsigned long read_ buffer;
}
BINDER_SET_MAX_THREADS 该命令告知 Binder驱动接收方(通常是 Server端)线程池中最大的线程数。由于 Client是并发向 Server端发送请求的, server端必须开辟线程池为这些并发请求提供服务。告知驱动线程池的最大值是为了让驱动在线程达到该值时不要再命令接收端启动新的线程 int max threads;
BINDER_SET_CONTEXT_MGR 将当前进程注册为SMgr。系统中同时只能存在个SMgr。只要当前的SMgr没有调用 close0关闭 Binder驱动就不能有别的进程可以成为SMgr
BINDER_THREAD_EXIT 通知 Binder驱动当前线程退出了。 Binder会为所有参与 Binder通信的线程(包括 Server线程池中的线程和 Client发出请求的线程)建立相应的数据结构。这些线程在退出时必须通知驱动释放相应的数据结构
BINDER_VERSION 获得 Binder驱动的版本号

7、HAL Stub 的框架中的三个结构体是什么

hw_module_t、hw_module_methods_t、hw_device_t

8、Android 4.0系统对传感器的支持多达13种,它们分别是

加速度传感器、磁力传感器、方向传感器、陀螺仪、环境光照传感器、压力传感器、温度传感器、距离传感器等。

简答题

1、Android源码开发环境搭建步骤

编译Android源码

(1)下载安装Vmware虚拟机,安装Ubuntu12.04系统。

(2)下载Android源码及其Linux内核。

(3)建立Linux编译环境:gcc和g++降版本、安装JDK1.6 ,配置JDK的环境变量。

(4)安装编译依赖工具包。

(5)编译Linux内核。

(6)初始化编译环境 source build/envsetup.sh。

(7)选择编译选项 lunch之后,选择目标编译项,应该选择ARM版本。

(8)编译源码 make -j4,-j之后的数字是编译使用的线程数,一般最大可以是内核数的两倍。

(9)源码编译完成,在out/target/product/generic/目录里出现三个文件:system.img、ramdisk.img、userdata.img说明编译成功。

注:选择目标编译项时(1)eng:工程版本 工程机上安装 (2)user:最终用户版本 最终用户机发行版本(3)userdebug:调试版本 (4)tests:测试版本

搭建Android SDK开发仿真环境

(1)下载安装eclipse,安装ADT插件。

(2)下载配置Android SDK工具包,搭建Android SDK平台。

(3)在Android源码目录下通过android create avd -n avd -t 1或者直接通过Android SDK Manager创建模拟器,定制Android模拟器。

(4)运行模拟器,加载编译好的linux内核和android源码镜像文件,如果成功启动说明搭建完成,可以进行仿真。

2、Android系统的启动过程

​ 1、开机上电自检后,启动BootLoader初始化堆栈和内存空间,为加载内核提供条件。

​ 2、BootLoader把Linux内核加载到内存,挂载根文件系统。

​ 3、启动init进程,init进程完成解析init.rc等初始化脚本文件,启动linux本地服务,启动Android本地守护进程。

​ 4、init进程解析init.rc启动zygote进程,zygote进程启动DVM,DVM中运行第一个java程序zygoteInit。zygote进程fork出System Server进程,在System Server进程启动和初始化时启动Android系统服务。

​ 5、Android系统服务启动完毕后,System Server进程通知开启Android桌面应用程序。

​ 6、启动完成之后可以创建Android应用程序进程,每一个应用进程都是一个独立的DVM实例,其都是客户端通过Binder机制与System Server进程进行通信,然后System Server进程再通过socket与zygote进程通信,最后由zygote进程fork出来的。这些DVM实例通过共享zygote进程预加载的一部分资源来加快应用启动速度。

3、编译一个应用程序并制作成镜像仿真的步骤

(1)打开eclipse开发环境,创建一个新的Android工程,对工程进行配置。

(2)将创建的新工程复制到源码目录中的packages/apps目录下,并且删除eclipse自动生成的文件,保留项目目录结构。

(3)修改工程的Android.mk文件:可以仿照Android自带应用程序的Android.mk文件,将Android.mk文件复制到工程目录中,然后进行修改。重点是将LOCAL_PACKAGE_NAME修改为程序的项目名,才能编译成功。

(4)回到android源码目录下,初始化编译环境。然后使用lunch选择编译目标项。

(5)编译工程:可以重新回到工程目录中去使用mm命令进行编译,可以使用mmm命令指定工程位置进行编译,也可以整个工程重新编译,不过这样太慢,没有必要。

(6)回到源码目录中,使用make snod重新生成镜像文件system.img。

(7)启动模拟器,查看应用程序运行效果。

4、局部引用和全局引用对比

局部引用:

​ (1)只在上层Java调用本地代码的函数内有效,当本地代码返回时,局部引用自动回收。

​ (2)局部引用只在创建他们的线程里有效,本地代码不能将局部引用在多线程之间传递,使用时不能在一个线程调用另一个线程创 建的局部引用,不能将一个局部引用保存在全局变量中在其他线程使用。(一定不能使用)。

​ (3)默认传递给本地代码的引用都是局部引用,所有JNI函数的返回值都是局部引用。

全局引用:

​ (1)只有显式通知VM时,全局引用才会被回收,否则一直有效,Java的GC不会释放该引用的对象。

​ (2)全局引用可以跨多个线程使用,可以将全局引用保存在全局变量中或者使用另一个线程访问。(不过不建议保存在全局变量)

​ (3)全局引用在程序员手动释放之前一直有效,使用全局引用必须手动创建销毁,可以使用局部引用创建全局引用,不使用全局引 用时必须手动释放。

5、对Android中智能指针的了解

(1)当类中有指针成员时,一般有两种方式来管理指针成员:一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享。

(2)智能指针的一种通用实现技术是使用引用计数。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。

(3)Android中的智能指针有两个基类,其中LightRefBase类是轻量级指针类RefBase类是重量级指针类,RefBase 类中不仅仅定义了 mStrong 强引用计数,而且还有一个 mWeak 的弱引用计数,强引用计数主要被 sp 对象管理,弱引用计数主要被 wp 对象管理。

(4)智能指针的类型有 轻量级指针强弱指针

(5)轻量级指针类中定义一个mCount变量,初值为0;两个成员函数 incStrongdecStrong 来维护引用计数器的值。

(6)强引用指针sp表示对一个对象的强引用关系,可以直接访问目标成员对象。弱引用指针 wp表示对一个对象的弱引用关系,不能像sp一样可以直接访问目标对象成员,如果想要访问的话,必须调用promote()函数由弱引用升级成强引用。当强引用增加时,强弱引用计数都增加,强引用减少时,强弱引用计数都减少。弱引用增加时,只有弱引用计数增加;弱引用减少时,只有弱引用计数减少。

(7)智能指针的引用管理sp和wp使用了模板template实现了泛型,只要继承了智能指针的基类LightRefBaseRefBase就能被管理,体现了多态的思想。而且智能指针类中使用了重载运算符,重载了operator->operator*来返回原始对象指针,能够让智能指针使用起来就像原始对象指针一样。

6、Binder通信和传统的Linux进程间通信方式相比有什么优点

(1)安全:传统的IPC(套接字、管道、消息队列)的安全机制依赖上层协议,例如:

​ a、Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志。

​ b、传统IPC的接收方无法获得对方进程可靠的UID/PID(用户ID/进程ID),从而无法鉴别对方身份。

​ c、使用传统IPC只能由用户在数据包里填入UID/PID,但这样不可靠,容易被恶意程序利用,故可靠的身份标记只有由IPC机制本身在内核中添加。

​ d、其次传统IPC访问接入点是开放的,无法建立私有通道。比如命名管道的名称,system V的键值,socket的ip地址或文件名都是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。

(2)性能高:传统的IPC(套接字、管道、消息队列)需要拷贝两次内存、Binder只需要拷贝一次内存、共享内存不需要拷贝内存。

(3)使用简单:采用了C/S架构和面向对象的调用方式,屏蔽了实现的细节,在使用Binder时,就跟调用一个本地对象实例一样。

7、Binder的操作步骤

(1)Server 注册服务

​ a、Server进程向Binder驱动发起注册服务请求。

​ b、Binder驱动将注册请求转发给Service Manager进程。

​ c、Service Manager进程添加该Server服务,Service Manager中保留了该Server的信息和其引用,并且Binder驱动在内核空间保存了这个Server的实体。

(2)Client 获取服务

​ a、Client向Binder驱动发起获取服务的请求,并传递要获取的服务名称。

​ b、Binder驱动将该请求转发给Service Manager进程。

​ c、Service Manager查找到Client需要的Server对应的Binder实体的Binder引用(也就是Binder实体的代理对象)并且返回给Binder驱动。

​ d、Binder驱动将Binder代理对象返回给Client(这个时候Client和Server已经建立了连接)。

(3)Client 使用服务

​ a、Binder驱动为跨进程通信做准备,实现内存映射(调用了mmap系统函数)。

​ b、Client进程将参数数据发送到Server进程。

​ c、Server进程根据Client进程的要求调用目标方法。

​ d、Server进程将目标方法的结果返回给Client进程。

注:Server可以通过已经建立的实名Binder连接,将创建的Binder实体传递给Client,因为这个Binder没有向Service Manager注册信息,所以别的进程就无法通过任何方式获取这个Binder的引用,这是一个匿名Binder。

8、对Binder的了解

(1)Binder框架定义了四个角色,它们分别是:Server、Client、ServerManager (SMgr) 、Binder驱动 。其中前三者运行在用户空间,第四者运行于内核空间。

(2)Binder框架比传统的linux IPC方式更加的安全,高效,易用。

(3)Binder框架使用了C/S架构,并且采用了面向对象的调用方式,能够更加方便的使用。

9、用语言描述HAL的实现

(1)HAL的实现遵循“321”架构,“3”是三个结构体,“2”是两个宏定义变量,“1”是一个函数。

(2)定义结构体hw_module_t 表示硬件、hw_module_methods_t 封装open函数、hw_device_t 表示硬件操作接口。

(3) hw_module_t中的methods指向hw_module_methods_t,是hw_module_methods_t类型的指针。

(4)hw_module_methods_t中的open是指向初始化函数的函数指针。

(5)在初始化函数初始化结构hw_device_t,返回硬件操作接口。

(6)定义名字为HMI的module对象,通过hw_get_module函数获取。

10、编写一个传感器的应用程序的步骤

(1)通过调用 Context. getSystemService(SENSOR_ SERVICE)获得传感器服务,实现返回的是封装了 Sensorservice的 Sensor Manager对象。
(2)调用 Sensor Manager. get Default Sensor( SensorTYPE_ ORIENTATION)来获得指定类型的传感器对象,方便获得传感器的数据。
(3)通过 SensorManager registerlistener注册 SensorEvent listener监听器,监听获得的传感器对象,当传感器数据提交上来时,能被应用程序得到。
(4)实现监听器里传感器上报数据的具体操作。

编程题

特别注意JNI中C++文件的文件名要写正确,比如java中全类名为com.test.Test,C++文件名应该为com_test_Test.cpp

1、如果只考JNI

(1)较为完整写法

下面给出java实现的类,请您完成未实现的本地代码和本地方法的注册。

// 文件名 Myclass.java
package com.soft.ex1;

class Myclass{

  static {
      System.loadLibrary("myclass");
      init();
  }
  private int buff[30];	// 用来存储添加的数据
  private int mCount;	// 数据的数目
  
  public native static void init();
  public native int add(int x);	// 把数据x添加到buff中
  public native boolean clear();	// 清空buff中的数据
  public native int[] sort();	// 对buff中的数据从小到大进行排序
  public native int sum(int x);	// 对buff中的前x个数据作求和运算
    
}

要点:

写出函数向量表给5分

写出注册函数给5分

四个方法每实现一个给2分,共8分

文件名写对给2分

// 文件名 com_soft_ex1_Myclass.cpp
#include 
#include 
#include 
#include 
#include 

// 属性ID或方法ID缓存结构体,封装起来的目的是做为缓存便于之后操作,如果需要获取Java层的属性的话可以写。
typedef struct _myclass{
    jclass clazz;
    jfieldID id_buff;
    jfieldID id_mCount;
} myclass, *myclass_ptr;

myclass_ptr cp;

// 初始化信息,封装属性id
static void native_id_init(JNIEnv *env, jclass clazz) {
    cp = (myclass_ptr)malloc(sizeof(myclass));
    jclass _clazz = env->FindClass("com/soft/ex1/Myclass");
    cp->clazz = _clazz;
    cp->id_buff = env->GetFieldID(_clazz, "buff", "[I");
    cp->id_mCount = env->GetFieldID(_clazz, "mCount", "I");
}

// 把数据x添加到buff中
static jint native_add(JNIEnv* env, jobject obj, jint x){
    jintArray array = (jintArray)(env->GetObjectField(obj, cp->id_buff));
    jint mCount = env->GetIntField(obj, cp->id_mCount);
    jint* buff = env->getIntArrayElements(array, JNI_TRUE);
    if (mCount >= 30) return -1;
    buff[mCount++] = x;
    env->ReleaseIntArrayElements(array, buff, 0);
    env->SetIntField(obj, cp->id_mCount, mCount);
    return 0;
}

// 清空buff中的数据
static jboolean native_clear(JNIEnv* env, jobject obj){
    jintArray array = (jintArray)(env->GetObjectField(obj, cp->id_buff));
    jint mCount = env->GetIntField(obj, cp->id_mCount);
    jint* buff = env->getIntArrayElements(array, JNI_TRUE);
    memset(buff, 0, mCount);
    mCount = 0;
    env->ReleaseIntArrayElements(array, buff, 0);
    env->SetIntField(obj, cp->id_mCount, mCount);
    return JNI_TRUE;
}

// 对buff中的数据从小到大进行排序
static jintArray native_sort(JNIEnv* env, jobject obj){
    jintArray array = (jintArray)(env->GetObjectField(obj, cp->id_buff));
    jint mCount = env->GetIntField(obj, cp->id_mCount);
    jint* buff = env->getIntArrayElements(array, JNI_TRUE);
    for(int i = 0; i < mCount - 1; i++){
        for(int j = 0; j < mCount - i - 1; j++){
            if(buff[j] > buff[j+1]){
                buff[j] = buff[j] ^ buff[j+1];
                buff[j+1] = buff[j] ^ buff[j+1];
                buff[j] = buff[j] ^ buff[j+1];
            }
        }
    }
    env->ReleaseIntArrayElements(array, buff, 0);
    return array;
}

// 对buff中的前x个数据作求和运算
static jint native_sum(JNIEnv* env, jobject obj, jint x){
    jintArray array = (jintArray)(env->GetObjectField(obj, cp->id_buff));
    jint mCount = env->GetIntField(obj, cp->id_mCount);
    jint* buff = env->getIntArrayElements(array, JNI_TRUE);
    jint sum = 0;
    for(int i = 0; i < x; i++){
        sum += buff[i];
    }
    env->ReleaseIntArrayElements(array, buff, 0);
    return sum;
}

// 该数组描述了一个 java方法 -> 本地函数的映射关系
static const JNINativeMethod gMethods[] = {
   		{(char*)"init", (char*)"()V", (void*)native_id_init},
        {(char*)"add", (char*)"(I)I", (void*)native_add},
        {(char*)"clear", (char*)"()Z", (void*)native_clear},
        {(char*)"sort", (char*)"()[I", (void*)native_sort},
        {(char*)"sum", (char*)"(I)I", (void*)native_sum},
};

// 将映射关系数组注册到JVM中
static int registerMethods(JNIEnv* env){
    static const char* className = "com/soft/ex1/Myclass";
    jclass clazz = env->FindClass(className);
    if(clazz == NULL){
        printf("没发现这个类\n");
        return -1;
    }
    if(env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK) return -1;
    return 0;
}

// 相当于本地的入口
jint JNI_OnLoad(JavaVM* vm, void* reserved){
    JNIEnv *env = NULL;
    jint result = -1;
    if(vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK){
        printf("获取ENV失败\n");
        goto fail;
    }
    if(env == NULL){
        goto fail;
    }
    if(registerMethods(env) != 0){
        printf("注册方法失败\n");
        goto fail;
    }
    result = JNI_VERSION_1_4;
    fail:
    	return result;
}

(2)简化写法

下面给出java 实现的类,请您完成未实现的本地代码和本地方法的注册。

// 文件名 test.java
package com.test.ex1;
class Test {
  static {
      System.loadLibrary("test");
  }
  private int x;
  private int y;
  public native int add(int x, int y);  		// 返回两参数的和
  public native boolean isBigNumber(int x); 	// 判断参数是不是大数,标准大于100为大数
  public native double div(int x, int y);		// 求商
}
// 测试代码
public class TestTest {
    public static void main(String[] args) {
        Test test = new Test();
        int i = test.add(1, 2);
		System.out.println("i=" + i);
		if(test.isBigNumber(101)) System.out.println("x is BigNumber");	
        double d = test.div(40, 20);
		System.out.println("d=" + d);
    }
}

要点:

写出函数向量表给5分

写出注册函数给5分

三个方法每实现一个给3分,共9分

文件名写对给1分

// 文件名 com_test_ex1_Test.cpp   注意要写
#include 
#include 
#include 
#include 
#include 		// 头文件可以不写

// Java方法的本地实现,一定要写
// 返回两参数的和
static jint native_add(JNIEnv* env, jobject obj, jint x, jint y){
    return x + y;
}

// 判断参数是不是大数,标准大于100为大数
static jboolean native_isBigNumber(JNIEnv* env, jobject obj, jint x){
    return x > 100;
}

// 求商
static jdouble native_div(JNIEnv* env, jobject obj, jint x, jint y){
    return x / y;
}

// 该数组描述了一个 java方法 -> 本地函数的映射关系,一定要写
static const JNINativeMethod gMethods[] = {
        {(char*)"add", (char*)"(II)I", (void*)native_add},
        {(char*)"isBigNumber", (char*)"(I)Z", (void*)native_isBigNumber},
        {(char*)"div", (char*)"(II)D", (void*)native_div}
};

// 将映射关系数组注册到JVM中
static int registerMethods(JNIEnv* env) {
    jclass clazz = env->FindClass("com/test/ex1/Test"); // 通过全类名找到jclass对象
    return env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod)); // 注册本地方法
}

// 相当于本地的入口,必须要写,不需要修改
jint JNI_OnLoad(JavaVM* vm, void* reserved){
    JNIEnv* env = NULL;
    jint version = JNI_VERSION_1_4;
    if(vm->GetEnv((void**) &env, version) != JNI_OK) return -1;
    if(registerMethods(env) != JNI_OK) return -1;
    return version;
}

2、如果结合HAL硬件抽象层

// java的service代码
package com.farsight.service;
import  android.util.Log;

public class LedService {

    static {
       System.loadLibrary ( "led_runtime" );
    }

    public LedService() {
        String icount;
        _init ();
    }
    public String set_on(int arg) {
       _set_on (arg);
        return "led " + String.valueOf(arg) + "on";
    }
    public String set_off(int arg) {
        _set_off (arg);
        return "led " + String.valueOf(arg) + "off" ;
    }

    private static native boolean _init();
    private static native int _set_on(int arg);
    private static native int _set_off(int arg);
    private static native int _get_count();
}
// JNI代码
// 如果给出HAL要求写出JNI的话,首先按照JNI的基本框架写出,在JNI的初始化函数或打开函数中使用hw_get_module函数找到
// hw_module_t类型的对象,然后通过其中的open函数返回硬件操作接口,硬件操作接口可以作为一个全局变量。
// 通过硬件操作接口进行操作
// 使用完毕之后将接口关闭
#define LOG_TAG "LedService"
#include "utils/Log.h"
#include 
#include 
#include 
#include 
#include 
#include "../../led_stub/include/led.h"

static led_control_device_t* sLedDevice = 0;
static led_module_t* sLedModule = 0;

static jint get_count(void)
{
    sLedDevice->getcount_led(sLedDevice);
	return 0;
}

static jint led_setOn(JNIEnv* env, jobject thiz, jint arg) {
    sLedDevice->set_on(sLedDevice, (int)arg);
	return 0;
}

static jint led_setOff(JNIEnv* env, jobject thiz, jint arg) {
	sLedDevice->set_off(sLedDevice, (int)arg);
	return 0;
}

static inline int led_control_open(const struct hw_module_t* module, struct led_control_device_t** device) {
	return module->methods->open(module, LED_HARDWARE_MODULE_ID, (struct hw_device_t**)device);
}

static jint led_init(JNIEnv *env, jclass clazz)
{
	led_module_t const *module;
	if (hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0) {     
		sLedModule = (led_module_t*) module;
		if (led_control_open(&module->common, &sLedDevice)) return -1;
	}
	return 0;
}

static const JNINativeMethod gMethods[] = {
	{ (char*)"_init", (char*)"()Z", (void*)led_init },
	{ (char*)"_set_on", (char*)"(I)I", (void*)led_setOn },
	{ (char*)"_set_off", (char*)"(I)I", (void*)led_setOff },
	{ (char*)"_get_count", (char*)"()I", (void*)get_count },
};


// 将映射关系数组注册到JVM中
static int registerMethods(JNIEnv* env){
    jclass clazz = env->FindClass("com/farsight/service/LedService"); // 通过全类名找到jclass对象
    return env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod)); // 注册本地方法
}

jint JNI_OnLoad(JavaVM* vm, void* reserved){
    JNIEnv* env = NULL;
    jint version = JNI_VERSION_1_4;
    if(vm->GetEnv((void**) &env, version) != JNI_OK) return -1;
    if(registerMethods(env) != JNI_OK) return -1;
    return version;
}

// HAL硬件抽象层代码
// 实现步骤:
// 如果给出JNI代码要求写出HAL代码的话,注意实现JNI中调用的HAL函数和JNI中体现出的hw_module_t和hw_device_t的子结构体。
// 实现open函数和close函数。在open函数中将hw_device_t的子结构体初始化后返回。
// 将open函数封装到hw_module_methods_t结构体中去,然后初始化hw_module_t的子结构体,并将其结构体变量的名字命名为HMI
#define LED_HARDWARE_MODULE_ID "led"

// 封装 hw_module_t 结构体
struct led_module_t {
	struct hw_module_t common;
};
// 封装 hw_device_t 结构体
struct led_control_device_t {
	struct hw_device_t common;
	int (*getcount_led)(struct led_control_device_t* dev);
	int (*set_on)(struct led_control_device_t* dev, int arg);
	int (*set_off)(struct led_control_device_t* dev, int arg);
};

int fd;

static int led_device_close(struct hw_device_t* device)
{
	struct led_control_context_t* ctx = (struct led_control_context_t*)device;
	if (ctx) {
		free(ctx);
	}
	close(fd);
	return 0;
}

static int led_getcount(struct led_control_device_t* dev)
{
	return 4;
}

static int led_set_on(struct led_control_device_t* dev, int arg)
{    
	ioctl(fd,LED_ON,arg);
	return 0;
} 

static int led_set_off(struct led_control_device_t* dev, int arg)
{
	ioctl(fd,LED_OFF,arg);
	return 0;
}

static int led_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device)
{
	struct led_control_context_t* context = (struct led_control_context_t*)malloc(sizeof(*context));
	memset(context, 0, sizeof(*context));
    // 初始化操作接口基本属性
	context->common.tag = HARDWARE_DEVICE_TAG;
	context->common.version = 0;
	context->common.module = module;
	context->common.close = led_device_close; 
	// 初始化操作接口扩展函数
	context->set_on = led_set_on;
	context->set_off = led_set_off;
	context->getcount_led = led_getcount;
	*device = (struct hw_device_t*) context;
    // 打开设备文件返回文件描述符
    fd = open("/dev/led",O_RDWR); 
    return fd;
}
// 封装打开设备函数
static struct hw_module_methods_t led_module_methods = {
	open: led_device_open
};
// 定义名字为 HMI 的硬件结构体对象
const struct led_module_t HAL_MODULE_INFO_SYM = {
	common: {
    	tag: HARDWARE_MODULE_TAG,
    	version_major: 1,
    	version_minor: 0,
    	id: LED_HARDWARE_MODULE_ID,
    	name: "led module",
    	author: "NAPO",
    	methods: &led_module_methods,
	}
};

你可能感兴趣的:(android)