安卓软件的架构分层
| 英文 | 中文 |
|---|---|
| Application | 应用层 |
| Application Framework | 应用框架层 |
| Android Runtime&Libraries | 运行时库和本地库 |
| Linux Kernel | 内核层 |
| Android HAL | 硬件抽象层 |
应用层
Application Framework应用框架层
运行时库和本地库层
内核层
硬件抽象层
子系统的介绍
| 系统名 | 介绍 |
|---|---|
| Android RIL子系统 | 子系统即无线电接口系统用于管理用户的电话、短信、数据通信等相关功能,它是每个移动通信设备必备的系统。 |
| 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连接子系统是智能设备的重要组成部分,它除了一般网络连接,如以太网、Wi-Fi外,还包含蓝牙连接、GPS定位连接、NFC等。 |
Android 应用程序开发过程(2种形式的开发,开发过程)
Android 4.0开发环境搭建及源码编译
安装VMware Workstation pro
安装ubuntu-12.04
gcc、g++降版本
sudo apt-get install gcc-4.4 g++-4.4
gcc降级:
sudo rm -rf /usr/bin/gcc
sudo ln -s /usr/bin/gcc-4.4 /usr/bin/gcc
gcc -v
g++降级
sudo rm -rf /usr/bin/g++
sudo ln -s /usr/bin/g++-4.4 /usr/bin/g++
g++ -v
把源码aosp源码拷贝到目录下
安装JDK1.6,修改 /etc/profile并且追加JDK的配置信息到文件的最后,通过 source /etc/profile使得更改生效
安装编译依赖工具包
解压源码、内核、sdk、eclipse压缩包
进入内核目录运行脚本build_kernel.sh进行编译linux内核
初始化编译环境 source build/envsetup.sh。
选择编译选项 lunch之后,选择目标编译项,应该选择ARM版本。
编译源码 make -j4,-j之后的数字是编译使用的线程数,一般最大可以是内核数的两倍。
源码编译完成,在out/target/product/generic/目录里出现三个文件:system.img、ramdisk.img、userdata.img说明编译成功。
创建模拟器
运行run_emulator.sh脚本
版本变量名由以下几个部分组成
源码编译生成:
搭建android sdk开发环境
android create avd -n avd -t 1或者直接通过Android SDK Manager创建模拟器,定制Android模拟器。编译命令
Android系统的启动过程
简答版本
init进程启动
Android初始化语言四种类型的声明
Actions(行为)
Commands(命令)
后面是一系列命令
常用的Commands
| 命令 | 说明 |
|---|---|
| exec
| 创建和执行一个程序(
|
| export | 在全局环境变量中设置环境变量为 |
| ifup | 启动网络接口 |
| hostname | 设置主机名 |
| chmod
| 更改文件访问权限 |
| chown
| 更改文件的所有者和组 |
| class_start | 启动所有指定服务类别下的未运行的服务 |
| class_stop | 停止指定服务类下的所有已运行的服务 |
| insmod
| 加载
|
| mkdir
| 创建一个目录
|
| mount
| 在目录挂载指定的设备 |
| setprop | 设置系统属性name为value |
| start/stop | 启动/停止指定服务 |
services(服务)
options(选项)
是一个服务的属性,决定了service的运行机制、状态和功能,他们影响service在何时,并以何种方式运行
| options | 说明 |
|---|---|
| critical | 对于设备关键的服务,如果其4min内退出大于四次,系统将会重启进入recovery模式 |
| disabled | 这个服务不会与同一个触发器下的服务自动启动 |
| setenv | 在进程启动时将环境变量<name<设置为 |
| socket [ [ ] ] | 创建套接字,并传递它的文件描述符给已经启动的进程。 |
Android中的本地守护进程
Home桌面的启动
Android.mk必须完成的操作
安卓主要预定义编译变量
| 编译变量 | 功能 |
|---|---|
| BUILD_SHARED_LIBRARY | 将模块编译成共享库 |
| BUILD_STATIC_LIBRARY | 将模块编译成静态库 |
| BUILD_EXECUTABLE | 将模块编译成可执行文件 |
| BUILD_JAVA_LIBRARY | 将模块编译成Java类库 |
| BUILD_PACKAGE | 将模块编译成Android应用程序包 |
主要编译变量
| 编译变量 | 功能 |
|---|---|
| local_path | 编译路径 |
| local_module | 编译模块名 |
| local_src_files | 编译源码列表 |
| local_shared_libraries | 使用的C/C++共享库列表 |
| local_static_libraries | 使用的C/C++静态库列表 |
| local_static_java_libraries | 使用的Java库列表 |
| local_cflags | 编译器参数 |
| local_c_includes | C/C++头文件路径 |
| local_package_name | Android应用程序名 |
| local_certificate | 签名认证 |
| local_java_libraries | Java库列表 |
编译可执行程序
LOCAL_PATH:=$(my-dir)//获取当前目录
# Test Exe
include $(CLEAR_VARS)//清除许多local_XXX变量
LOCAL_MODULE_TAGS:=eng//编译版本
LOCAL_SRC_FILES:=\ //源文件
main.c
LOCAL_MODULE=test_exe //生成可执行程序
LOCAL_C_INCLUDES:= //包含的头文件
LOCAL_STATIC_LIBARIES:= //需要的静态库
LOCAL_SHARED_LIBARIES:=libc //需要的动态库
include $(BUILD_EXECUTABLE)//编译后生成文件路径,指定编译方式,编译成可执行程序
编译静态库的模板
# Test Static Lib
LOCAL_PATH:=$(call my-dir)//获取当前目录
include$(CLEAR_VARS):=/
helloworld.c
LOCAL_MODULE:=libtest_static
#LOCAL_C_INCLUDES :=//包含的头文件
#LOCAL_STATIC_LIBRARIES :=//需要的静态库
#LOCAL_SHARED_LIBRARIES :=//需要的动态库
include $(BUILD_STATIC_LIBRARY)
一般的和上面相似,BUILD_STATIC_LIBRARY表示编译一个静态库。
编译生成的是以“lib<module_name>.so”的文件,这个就是共享库了,这是系统会自动加上“头”和“尾”。 libtest_static.a
编译动态库的模板
#Test Shared Lib
LOCAL_PATH:=$(call my-dir)//获取当前目录
include $(CLEAR_VARS)//清除许多local_XX变量
LOCAL_SRC_FILES:=/
helloworld.c
LOCAL_MODULE:=libtest_shared
TARGET_PRELINK_MODULES:=false
#LOCAL_C_INCLUDES :=//包含的头文件
#LOCAL_STATIC_LIBRARIES :=//需要的静态库
#LOCAL_SHARED_LIBRARIES :=//需要的动态库
include $(BUILD_SHARED_LIBRARY)
LOCAL_PRELINK_MODULE := false
Prelink利用事先链接代替运行时链接的方法来加速共享库的加载,它不仅可以加快起动速度,还可以减少部分内存开销。
编译一个简单的APK
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=$(call all-subdir-java-files)//指定编译源码列表
LOCAL_PACKAGE_NAME:=LocalPackage//指定android应用程序名
include $(BUILD_PACKAGE)
BUILD_PACKAGE表示编译一个apk程序,而call all-subdir-java-files表示自动查找当前目录下的所有java文件进行编译。
在源码中编译和SDK环境下编译有所不同,涉及的目录主要有两个。
Out/target/common/obj/apps—通用java字节码目录
out/target/product/<TARGET_PRODUCT>/apps---android应用包目录
编译Helloworld应用程序的步骤
打开eclipse开发环境,新建一个Android应用程序:HelloWorld
cd /home/fyj/aosp/eclipse
./eclipse
将新创建的HelloWorld工程复制到源码目录下的packages/apps目录下
cp -rf HelloWorld /home/fyj/aosp/aosp/packages/apps
编译HelloWorld工程的Android.mk文件,仿照Android自带应用程序的Android.mk文件,例如 Camera工程中的Android.mk,因此将其复制到HelloWorld中
cp ../Camera/Android.mk ./
Android.mk
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := HelloWorld
#LOCAL_SDK_VERSION := current
include $(BUILD_PACKAGE)
•eng: 工程机,user:最终用户机 tests:测试机 optional:指该模块在所有版本下都编译
编译Helloworld工程
切换到Android源码目录下
cd /home/fyj/aosp/aosp
加载编译函数
source build/envsetup.sh
选择编译目标项
lunch 1
通过mmm命令编译HelloWorld工程
mmm packages/apps/HelloWorld/
编译生成模拟映像system.img
make snod
定制开机界面
生成图片数组
设置logo.jpg输出文件属性
保存图片数据为c语言头文件test.h
将test.h图片数据文件拷贝到Linux内核framebuffer驱动目录下
cp test.h /home/fyj/aosp/goldfish_2.6.29/drivers/video
修改framebuffer驱动文件goldfishfb.c
然后修改初始化工作,在prob函数返回之前,将图片数据写入到Framebuffer显存中。
memcpy(fb->fb.screen_base,gImage_test,320*480*2);
return 0;
重新编译Linux内核
cd /home/fyj/aosp/goldfish_2.6.29
./build_kenel.sh
启动Android模拟器运行实训结果
开机动画【蒙版图片】
使用Photoshop 等图像处理软件制作一张背景为黑色,中间镂空的 PNG:格式的图片,命名为:android-logo-mask.png
将android-logo-mask.png 复制到 frameworks/base/core/res/assets/images/目录下替换Android 默认的图片,为了防止源码不编译图片资源,将图片时间戳更新一下。
运行命令把时间戳进行更新:
touch /home/fyj/aosp/aosp/frameworks/base/core/res/assets/images/android-logo-mask.png
重新编译Android的系统资源包framework-res.apk,以及运行make snod并且生成新的system.img
source build/envsetup.sh
lunch 1
mmm frameworks/base/core/res/
make snod
运行脚本查看
开机动画【逐帧动画】
在/data/local/或/system/media/目录创建 bootanimation.zip 文件
bootanimation.zip文件打包前的结构如表所示。
| 文件 | 说明 |
|---|---|
| desc.txt | 动画属性描述文件 |
| part0/ | 第一阶段动画图片的目录 |
| part1/ | 第二阶段动画图片的目录 |
理解:
320 480 5
p 1 0 part0
p 0 0 part1
| 图片属性 | 320(图片宽) | 320(图片高) | 15(每秒显示帧数) | 无 |
|---|---|---|---|---|
| 第一阶段动画属性 | P(默认标志符) | 1(循环次数为1) | 0(进入该时间段的间隔时间) | part0(该阶段图片存放目录) |
| 第二阶段动画属性 | P(默认标志符) | 0(无限循环) | 0(进入该时间段的间隔时间) | part1(该阶段图片存放目录) |
如果bootanimation.zip放到/system/media/目录下,则重新编译生成system.img
source build/envsetup.sh
lunch 1
make snod
启动模拟器
使用JNI通常的场景
JNIEnv是当前Java线程的执行环境,一个JVM对应一个JavaVM结构,而一个JVM中可能创建多个Java线程,每个线程对应一个JNIEnv结构,他们保存在线程本地存储中。因此,不同的线程的JNIEnv是不同,也不能相互共享调用。
JNI的数据类型和Java数据类型以及数据类型签名
| 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 | [ |
| 二维数组类型 | [[ |
JNI方法签名相关
| JAVA方法 | JNI方法签名 |
|---|---|
| boolean isLedOn(); | ()Z或者(V)Z |
| double Div(int x,int y); | (II)D |
| int GetNameBy(int ModubleID); | (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) | (I;Ljava/lang/String;[I)C |
| boolean showMsg(android.View v, String msg); | (Landroid/lang/String;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 |
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类型 |
Java访问本地方法
编写Java代码,在Java代码中加载本地代码库
在Java中声明本地native方法
调用本地native方法
public class HelloJNI{
static {
System.loadLibrary("hellojni");
}
private static native String getJNIHello();
public static void main(String args[]){
System.out.println(HelloJNI.getJNIHello());
}
}
JNI访问Java成员
jclass FindClass(const char *name);
在JNI中Java对象一般都是作为参数传递给本地方法的
class MyClass{
private int mNumber;
private static Sring mName;
public MyClass(){
}
public void printNum(){
System.out.println("Number:"+mNumber);
}
public static void printNm(){
System.out.println("Number"+mNumber);
}
}
class PassJavaObj{
static{
System.loadLibrary("native_method");
}
private native static void passObj(String str);
public static void main(){
passObj("Hello World");
}
}
本地代码
void Java_com_test_exam1_PassJavaObj_passObj
(JNIEnv *env,jclassthiz,jobject str)
{
...
}
env:当前Java代码的运行环境
thiz:表示调用当前本地方法的对象
str:就是我们传递过来的字符串
获得Java属性ID和方法ID
jfieldID和jmethodID用来标识java对象的属性和方法。需要获得ID才能对属性和方法进行操作。而由于java中的重载机制,所以需 要使用签名确定属性和方法。
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
jclass clazz:要取得成员对应的类
const char *name:代表要取得的方法名和属性名
const char *sig:代表要取得的方法或属性的签名
案例
class MyClass{
private int mNumber;
private static Sring mName="Michael";
public MyClass(){
mNumber=1000;
}
public void printNum(){
System.out.println("Number:"+mNumber);
}
public static void printNm(){
System.out.println("Number"+mNumber);
}
}
class NativeCallJava{
static{
System.loadLibrary("native_callback");
}
private native static void callNative(MyClass cls);
public static void main(){
callNative(new MyClass());
}
}
void Java_com_test_exam2_NativeCallJava_callNative(JNIEnv * env,jobject obj)
{
jclass myCls = env->GetObjectClass(obj);
jfieldID mNumFieldID=env->GetFieldID(myCls,"Number","I");
jfieldID mNameFieldID=env->GetStaticFieldID(myCls,"mName","java/lang/String");
jmethodID printNumMethodID =env->GetMethodID(myCls,"printNum"."(V)V");
jmethodID printNumMethodID =env->GetStaticMethodID(myCls,"printNm"."(V)V");
}
JNI操作Java属性和方法
// 获取java属性
j<类型> Get<类型>Field(jobject obj, jfieldID fieldID); // 通过fieldID从一个object中获取属性
j<类型> Get Static<类型>Field(jobject obj, jfieldID fieldID); // 通过fieldID从一个object中获取静态属性
//设置java属性
void Set<类型>Field(jobject obj, jfieldID fieldID, j<类型> val); // 向一个对象的一个属性设置值
void GetStatic<类型>Field(jobject obj, jfieldID fieldID, j<类型> val); // 向一个对象的一个静态属性设置值
例如
jint GetIntField(jobejct obj,jfield fieldID);
//获得obj对象中,整形属性ID为fieldID的值
void SetObjectField(jobjectobj,jfieldID fielded,jobejct val);
//设置obj对象中,属性ID为fieldID,属性值为val
void SetStaticCharField(jclass clazz,jfieldID fieldID,jchar value)
//设置clazz类中,静态属性ID为fieldID的属性值为value
例子
//对上一节本地的代码进行修改
void Java_com_test_exam2_NativeCallJava_callNative(JNIEnv * env,jclassthiz,jobject obj)
{
jclass myCls = env->GetObjectClass(obj);
jfieldID mNumFieldID=env->GetFieldID(myCls,"Number","I");
jfieldID mNameFieldID=env->GetStaticFieldID(myCls,"mName","java/lang/String");
//获得、设置Java成员的属性值
jint mNum=env->GetIntField(obj,mNumFieldID);
env->SetIntField(obj,mNumFieldID,mNum+100);
//获得、设置静态属性的值
jstring mNum=(jstring)(env->GetStaticObjectField(myCls,mNameFieldID));
printf("%s\n",mNm);
jstring newStr=env->NewStringUTF("hello from Native");
env->SetStaticObejctField(myCls,mNumFieldID,newStr);
}
JNI调用Java中的方法
// 调用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);
例子
//对上一节本地的代码进行修改
void Java_com_test_exam2_NativeCallJava_callNative(JNIEnv * env,jclassthiz,jobject obj)
{
jclass myCls = env->GetObjectClass(obj);
jfieldID mNumFieldID=env->GetFieldID(myCls,"Number","I");
jfieldID mNameFieldID=env->GetStaticFieldID(myCls,"mName","java/lang/String");
//获得、设置Java成员的属性值
jint mNum=env->GetIntField(obj,mNumFieldID);
env->SetIntField(obj,mNumFieldID,mNum+100);
//获得、设置静态属性的值
jstring mNum=(jstring)(env->GetStaticObjectField(myCls,mNameFieldID));
printf("%s\n",mNm);
jstring newStr=env->NewStringUTF("hello from Native");
env->SetStaticObejctField(myCls,mNumFieldID,newStr);
//取得Java方法ID
jmethodID printNumMethodID=env->GetMethodID(myCls,"printNum","(V)V");
jmethodID printMmMethodID=env->GetStaticMethodID(myCls,"printNm","(V)V");
//调用Myclass对象中的printNm方法
CallStaticVoidMethod(myCls,printNmMethodID)
}
在本地代码中创建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);
clazz:要创建的对象的类
jmethodID:创建对象对应的构造方法ID
参数列表:,表示是变长参数,以V结束的方法名表示向量表表示参数列表,以A结束的方法名表示以jvalue数组提供参数列表
在本地代码中创建Java对象
void Java_com_test_exam2_NativeCallJava_callNative(JNIEnv * env,jclassthiz,jobject obj)
{
jclass myCls=env->GetObjectClass(obj);
//当然可以像下面这样
//jclass myCls = env->FindClass("com/test/exam2/MyClass");
//取得MyClass的构造方法ID
jmethodID myClassMethodID=env->GetMethodID(myCls,"MyClass","(V)V");
//创建MyClass新对象
jobject newObj = NewObject(myCls,myClassMethodID);
}
本地代码操作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);
len:新创建对象数组长度
clazz:对象数组元素类型
init:对象数组元素的初始值
array:要操作的数组
index:要操作数组元素的下标索引
val:要设置的数组元素的值
例子
class ArrayTest{
static{
System.loadLibrary("native_array");
}
privateint [] arrays=new int[]{1,2,3,4,5};
publicnative void show();
publicstatic void main(String[] args){
new ArrayTest().show();
}
}
本地代码的编写:
void Java_com_test_exam4_5_ArrayTest_ show(JNIEnv * env,jobject obj){
jfieldID id_arrsys=env->GetFieldID(env->GetObjectClass(obj),"arrays","[I");
jintArrayarr=(jintArray)(env->GetObjectField(obj,id_arrays));
jint * int_arr=env->GetIntArrayElements(arr,NULL);
jsizelen = env->GetArrayLength(arr);
for(int i=0;i<len;i++)
cout<<int_arr[i]<<endl;
env->ReleaseIntArrayElements(arr,int_arr,JNI_ABORT);
}
局部引用与全局引用
局部引用:
(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不会回收该对象
局部引用需要手动释放的情况
全局引用
- 本地方法被多次调用时,可以使用一个全局引用跨越他们,一个全局引用可以跨越多个线程,并且在程序员手动释放之前,一直有效。
stringClass=env->NewGlobalRef(localRefCls);
//创建全局引用并指向局部引用
//删除局部应用
env->DeleteLocalRef(localRefCls);
//判断全局引用是否创建成功
if(stringClass==NULL)
return NULL;
在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;
JNI_OnLoad方法
当Java代码中通过System.loadLibrary方法加载本地库的时候,本地代码库被JVM加载,JVM自动调用本地代码中的JNI_OnLoad函数。这个函数实现了本地方法注册的流程
jint JNI_OnLoad(JavaVM* vm, void* reserved); // 第一个参数表示了一个JVM实例,第二个参数是保留的
vm:代表了JVM实例,其实是和JVM相关的一些操作函数指针
reserved:保留
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 <unistd.h>
#include <stdlib.h>
#include <malloc.h>
#include <jni.h>
#include <jni_md.h>
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的对象管理采用了智能指针,其中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实现了泛型,只要继承了智能指针的基类LightRefBase或RefBase就能被管理,体现了多态的思想。而且智能指针类中使用了重载运算符,重载了operator->和operator*来返回原始对象指针,能够让智能指针使用起来就像原始对象指针一样。
智能指针的概念
- 当类中有指针成员时,一般有两种方式来管理指针成员:一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享
轻量级指针
- 当使用智能指针时,只要继承LightRefBase类,那么子类对象就具有智能管理功能了。
- 类中定义一个mCount变量,初值为0;两个成员函数 incStrong和decStrong 来维护引用计数器的值。
- 需要注意的是,在desStrong函数中,如果当前引用计数值为1,那么当减1后就会变成0,于是就会delete这个对象。
轻量级指针实现有以下规定
(1)LightRefBase类的对象可以通过智能指针sp进行管理
(2)当使用智能指针sp指向、赋值、初始化LightRefBase对象时,该对象引用计数加1
(3)当sp指针使用完后,其指向的对象引用计数自动减1
(4)当LightRefBase对象的引用计数为0时,该对象会被删除
强引用和弱引用
Binder框架定义了四个角色,它们分别是:Server、Client、ServerManager (SMgr) 、Binder驱动 。其中前三者运行在用户空间,第四者运行于内核空间。
传统的Linux系统IPC通信机制:管道、消息队列、共享内存、套接字、信号量、信号
Binder的小知识
Android进程空间与Binder机制
Binder通信和传统的Linux进程间通信方式相比的优点
Binder的操作步骤

Server注册服务
Client获取服务
Client使用服务
Server可以通过已经建立的实名Binder连接,将创建的Binder实体传递给Client,因为这个Binder没有向Service Manager注册信息,所以别的进程就无法通过任何方式获取这个Binder的引用,这是一个匿名Binder。
RPC通信机制
IPC和RPC通信的区别
Inter Process Communication )跨进程通信
Reomote Procedure Call) 远程过程调用
匿名和实名Binder的区别与联系
理解:
其相互配合完成一次Binder通信的大概过程如下:
首先在系统开机启动时,一个进程通过Binder驱动将自己注册成ServiceManager,其地址固定为0;
Server通过驱动向ServiceManager中注册Binder(Server中的Binder实体),表明可以对外提供服务。驱动为这个Binder创建位于内核中的实体节点以及ServiceManager对实体的引用,将名字以及新建的引用打包传给ServiceManager,ServiceManger将其填入查找表;
Client通过名字,在Binder驱动的帮助下从ServiceManager中获取到对Binder实体的引用,通过这个引用就能实现和Server进程的通信。

HAL:硬件设备抽象
- linux的设备驱动:
- Kernel中使用GPL协议开源一些非核心的接口访问代码
- 在Kernel上的应用层使用Apache协议,主要是硬件厂商不希望开源的逻辑代码,仅提供二进制代码
HAL存在的原因
HAL的两种框架形式
HAL使用方式
HAL Stub 架构简称 321架构。因为其只需要三个结构体、两个常量、一个函数。
HAL Stub架构分析
三个结构体
// 表示了一个硬件
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 注册
led_moudle_t
继承led_moudle_t并且修改结构体
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必须要做为第一个数据项。这样通过强制类型转换实现结构体的“多态”时才不会出错。
实现操作接口里对硬件操作的函数
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;
}
led_device_t和父结构体hw_device_t的关系
struct led_device_t
{
struct hw_device_t common;
int (*getcount_led)(struct led_device_t *dev);
int (*set_on)(struct led_device_t *dev);
int (*set_off)(struct led_device_t *dev);
}
需要实现设备打开的函数,并将其封装到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
};
模块变量的名字必须为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的实现(简答题)
Android系统内置支持的传感器
- 加速度传感器、磁力传感器、方向传感器、陀螺仪、环境光照传感器、压力传感器、温度传感器、距离传感器 等。
编写一个传感器的应用程序的步骤
ff = 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
};
```
模块变量的名字必须为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的实现(简答题)
Android系统内置支持的传感器
- 加速度传感器、磁力传感器、方向传感器、陀螺仪、环境光照传感器、压力传感器、温度传感器、距离传感器 等。
编写一个传感器的应用程序的步骤