Skip to main content
 首页 » 编程设计

Android 硬件访问服务学习笔记_WDS

2022年07月19日49落叶

1.Android驱动框架
App1 App2 App3 App4
-------------------
硬件访问服务
-------------------
JNI
-------------------
C库
-------------------
Linux内核驱动

也就是说Android驱动 = Linux驱动 + 封装。
重点在与硬件访问服务,不同的硬件需要不同的硬件访问服务。

2.需要根据“韦东山Android系统视频使用手册20160303.pdf”的第六章安装Android Studio.
Me: 另外还需要计算机->属性->高级系统设置->环境变量 将jdk和jre的bin目录导入到path环境变量中。

3.app/src/main/res/layout/activity_main.xml文件对应于手机屏上看到的界面。

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="www.100ask.net"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.176"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.241" />

a. TextView首字母大写就说明他是一个类对象,在其上面ctrl+h键可以查看这个类的继承关系。
发现其为view类的子类,双击TextView类就可以打开TextView.java源文件。
b. android:layout_width表示Text文本框的高度,赋值"wrap_content"表示其宽度/高度由内容决定。
若为"fill_parent"表示宽度要占满整个界面。

c.复选框是CheckBox

4.实验
(1)要求:
a. 点击ALL ON,变为ALL OFF,同时勾选4个LED
b. 点击ALL OFF,变为ALL ON,同时4个LED都取消勾选
c. 可以单独选择某一个LED。

(2)按钮按下时函数怎么写
双击.xml文件中的Button再按住Shift+F1会打开一个介绍Wegit中Button怎么使用的文档(但是3.31的AS中无效)
这个文档是sdk/docs/reference/android/widget下的Button.html,它里面介绍了如何如何在
点击Button的时候关联到函数。
里面有button.setOnClickListener()设置button的监听器,当buton被按下的时候这个函数就会被调用


button = () 鼠标定位在()内部,按ctrl+shift+空格 会自动帮我们强制类型转换 button = (Button)

button = (Button) findViewById(R.id.BUTTON); 双击BUTTON,然后按ctrl+B可以跳转到BUTTON定义的地方。
选中上面的BUTTON,然后Fn+Alt+F7跳转到引用这个Button的地方。

Build工程可以让AS根据xml生成一些源代码。

class MyButtonListener implements View.OnClickListener {
鼠标定位在这里,按ctrl+i, 双击弹出来的onClick()就表示要override
这个onClick(),代码会自动补全。
}

要想按下ALL ON时4个按键都被选中,首先要获取这四个按键的实例化对象。

MainActivity.java中要想根据id来找到控件就需要在activity_main.xml中定义这些控件的id.

android:onClick="onCheckboxClicked" 表明当CheckBox被点击的时候方法onCheckboxClicked就会被调用

双击选中一个函数,然后按ctrl+b就会跳转到函数定义的地方去。


4.activity_main.xml中添加一个Button
<Button Tab键,会补全基本内容

5.这里写的app程序可以在tiny4412开发板或在Android手机上运行,前提是手机打开了
开发者选项的USB调试。在tiny4412上运行的话使用mini USB 口语PC相连。此时再点击AS
的运行三角符号,就会弹出一个模拟器和一个正式的开发板。
===>此时会显示4412开发板的序列号,可以看是不是同一个开发板。

2.TODO:第一小节中提到的 Android_app视频_Mars 在哪???

JDK(Java 开发包), JRE(Java 运行环境)


disable.android.first.run=true

在JNI的C文件中使用这个函数可以吧log打印在串口上,printf好像不可以
__android_log_print()

TODO:
1.我的实验中模拟器(虚拟手机)上面什么都不显示,为什么 ?
2.查MainActivity.java中使用的toast类的成员函数的使用。

注App所在版本库:https://github.com/weidongshan/APP_0001_LEDDemo.git


=====================================================================
第1课第2节_让Android应用程序访问C库_P.mp4
-------------------------------------------
1.JNI编译生成的.so文件,根据报错信息,可以打包到java应用程序中,也可以
放在/vendor/lib或/system/lib下。
放到应用程序中的方法:
a. 在app/libs/下创建armeabi子目录,.so文件放在这里面。
b. 修改build.gradle文件,其实有2个build.grale文件,一个在AS工程根目录下,另一个在app目录下,
要修改的是app下面的build.gradle文件。
android {
...
sourceSets { //增加这一项
main {
jniLibs.srcDirs = ['libs'] //表示JNI库文件方法app/libs目录下
}
}
...
}

注意:
(1)若使用交叉编译器编译添加了JNI动态库文件就不能再使用模拟器运行了,因为模拟器是运行在x86上的。
(2)若在开发板上运行包找不到JNI动态库,排查:app\build\outputs\apk下面有生成.apk文件,可以使用2345好压打开它,看看app/libs里面是否有
动态库文件。对比名字是否写错了等。
(3)libhardcontrol.so依赖于libc.so.6这个库,但是找不到它的解决方法:
# ls /vendor/lib
# ls /system/lib/libc.so* ==>有libc.so但是没有libc.so.6
若重新编译开发板上的Android系统可以吧libc.so.6放进去,也可以指定就使用libc.so 修改编译成动态库的
编译选项,加上-nostdlib来指定链接哪个动态库,表示生成libhardcontrol.so的时候不会自动使用标准的libc库。
我们可以指定使用哪个libc库:
# cd work/android5.0.2/
# find -name libc.so -r ./ 发现有很多libc库,我们可以找一个arm平台的比较新的libc库,在-nostdlib后需要
加上这个库的路径名。
然后再从新编译这个app。

2.JNI文件中的JNI_OnLoad()中(*env)->FindClass(env, "com/thisway/hardlibrary/HardControl");
HardControl类位于包com.thisway.hardlibrary中,这里要给全路径,‘.’还要换成‘/’。

3.将JNI文件HardControl.c编译成C库:
arm-linux-gcc -fPIC -shared HardControl.c -o libHardControl.so -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/
这样的话交叉编译工具链直接使用的就是x86 PC机的头文件了,这样合适吗? 会不会有些头文件有出入。

4.可以在手机/开发板上运行app程序,程序运行状态可以打印在AS中,可以用AS在线调试程序。 ##########

5.LED_Demo1\app\build\outputs\apk下面有生成.apk文件,可以使用2345好压打开它,看看app/libs里面是否有
动态库文件

6.此应用程序的入口是onCreate()方法,在里面调用JNI提供的open方法。

7.在JNI文件中加打印
虽然JNI文件是C编写的,但是也不能使用printf(),因为它打印的我们在AS里面看不见,这里想在AS的logcat窗
口打印这些信息需要使用Android提供的liblog库,用法:
#include <android/log.h>
__android_log_print(ANDROID_LOG_DEBUG, "JNIDemo", "native add ...");
eg:
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledOpen ...");
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledCtrl: %d, %d", which, status);
以后写的C库比较复杂的话也可以使用这种方法加log帮助调试。


编译JNI成动态库的时候报找不到log.h,解决方法:
在Android源码包(work/android5.2.0/)下使用find查找log.h文件,选择一个最高版本的Android并且选择arm结构的log.h
即可。
app在开发板上执行的时候报找不到__android_log_print,解决方法:
在Android源码包(work/android5.2.0/)下使用find查找liblog*文件,选择一个arm平台的罪行的liblog.so文件,
在编译libhardcontrol.so的时候要加liblog.so的路径,链接到这个文件,然后在开发板上运行就没有这个错了。
TODO:编译成的动态库中使用的其它库的函数时,会把其它库的函数代码拷贝到此库中吗? 若不然为什么在运行时
没有再报找不到__android_log_print方法了!


========================================================
第1课第3节_Android程序操作LED_P.mp4
---------------------------------------
1.这一节主要就是写一个LED的驱动程序,19min处有使用工具烧录内核

2.驱动版本库:git clone https://github.com/weidongshan/DRV_0001_LEDs.git

========================================================
第1课第4.1节_Android硬件访问服务框架_P.mp4
------------------------------------------
1.硬件访问服务:只让一个程序访问硬件,叫做serverm,其它进程将访问请求发
给这个server.
流程:
a. Java通过JNI访问到C库,在C库中的JNI_OnLoad()中注册本地方法给Java使用,
在Java的静态代码块中调用loadlibrary()加载库。
b. SystemServer中将针对每个硬件的服务添加到系统中。
c. App先获得服务,getService,然后使用服务执行service方法。


注意:
1.server(服务器) service(服务), 服务器提供服务,首先SystemServer先对每一个硬件
构造service,然后再addService。

2.在SystemServer中加载C库来初始化本地服务,使用System.loadLibrary("android_servers"); libandroid_servers.so
对应的文件是Onload.cpp,它里面的JNI_OnLoad()分别调用各个硬件模块的函数注册
本地方法。
注意:JNI_OnLoad()里面注册函数的名字与文件对应是有规律的,例如 register_android_server_UsbDeviceManager,
其本地方法定义是在com_android_server_UsbDeviceManager.cpp中。它们称为JN文件,如果对硬件的操作比较简可以直接
在JNI文件中调用read/write/ioctl来访问硬件。但是如果对硬件的操作比较复杂,建议把它写在HAL文件中(也是在用户空间的),好处有两个:
(1)如果修改了硬件可以直接只修改编译HAL文件,然后把HAL文件对应的库放到系统中即可。但是如果在JNI文件中操作硬件的话,一旦修改
就需要重新编译烧写整个系统。
(2)有些硬件厂商不愿意公开其硬件操作文件,直接给出.so文件,此时直接使用JNI文件加载这个.so文件
就可以使用了。

3.硬件service和JNI文件和HAL文件还是两个不同的东西。SystemServer中startOtherServices()方法中
启动了很多硬件模块的service服务。

4.在SystemServer(.cpp)运行的进程中通过ServiceManager.addService()将一个服务告诉
系统,系统是Service_manager.c,它管理着系统中的所有服务,例如SerialService/VibratorService/LEDDemoService。
如果一个service想能被应用程序使用必须得把它注册进Service_manager.c中去。################
之后应用程序就可以向Service_manager.c查询获得service。
eg:
vibrator = new VibratorService(mSystemContext);
/*把这个vibrator服务告诉系统*/
ServiceManager.addService("vibrator", vibrator);

5.注意课件上的一个差别:当addService的时候是LedService.java,但是当应用程序getService
的时候却是ILedService.java,表明应用程序获得的是一个接口。应用程序通过ILedService这个接口将
服务请求发给LedService,然后由LedService实现对硬件的操作。

6.整个过程涉及三个进程
(1)SystemServer.java向service_manager.c注册添加各种service的进程
(2) 应用程序实际上就是一个客户端,它首先向service_manager.c查询
获得某一个服务,最后把这个服务请求发送给SystemServer.java
(3)

这些进程间的通信是通过内核的一个进程间通信机制binder机制实现的,比如SystemServer.java在
addService()的时候会调用到这个binder驱动。应用程序这个客户端在getService()的时候也会
涉及到binder驱动。应用程序这个客户端在向SystemServer.java发送服务请求的时候也会涉及
到binder驱动程序。

7.binder驱动并不是内核自带的,它是google公司对Linux内核做的一个修改,添加的一个驱动程序,它
可以实现更加高效的进程间通信。

8.硬件访问服务框架与实现
(1)实现JNI和HAL文件,JNI文件作用是注册JNI native方法和加载HAL库。HAL文件作用是调用open()/read()/write()实现硬件操作。
例如LEDDemo中实现JNI文件com_android_server_LedService.cpp,作用是注册JNI本地方法和加载hal_led.c编译出来的.so文件。
hal_led.c中实现对对硬件的操作。

(2) 修改onload.cpp文件,向Java层注册JNI文件中实现的native接口
例如调用com_android_server_LedService.cpp中实现的注册JNI native的方法。

(3) 修改SystemServer.java文件,new一个service对象然后添加此service。
例如new LedService, 然后add此service

(4)实现LedService.java 用于调用本地函数,提供硬件服务

(5)实现ILedService.java 给App向service_manager.c查询service使用

-----------------------------------------------------------

第1课第4.1节_Android硬件访问服务框架_P.mp4

1.server 提供service

2.system server框架
(1) 加载C库:使用loadLibrary()
(2) 注册本地方法:在JNI_Onload()中分别为每一个硬件注册本地方法(eg:led、振动器、串口等)
(3) system server为每一个硬件构造service,并注册service
(4) App使用系统服务
① 获取服务:getService()
② 使用服务:执行service的方法。
(5)

要想一个service能被应用进程使用,就必须把它注册到service_manager进程中去。

3.在JNI文件中操作硬件的话,一旦修改就要重新编译整个系统,若JNI文件中
对硬件操作比较多,建议写成HAL文件。
很多公司并不愿意开放硬件操作文件,若写在HAL 文件中,直接加载库就能使用了。

4.Client App在getService的时候调用ILedService.java(这里前面带一个'I'
表明它是一个接口)将对硬件的操作请求发送给
LedService.java,由LedService.java实现对硬件的操作。(笔记中有图)

整个过程涉及了3个进程,SystemServer(SystemServer.java)进程
向service_manager(service_manager.c)注册添加注册各种service。然后是作为
客户端的App向service_manager查询获得某一个服务,最后把获取服务请
求发送给SystemServer??!!

这些进程之间的通信是通过Linux内核中的一个binder驱动来实现的。
比如SystemService在addService的时候会涉及到binder驱动。
client App在调用getService的时候也会涉及到binder驱动。
client向SystemService发送服务请求的时候也会涉及到binder驱动。

binder驱动并不是内核自带的,而是google修改内核添加的一个驱动程序。它可以实现更加高效的
进程间通信。

5.怎么实现硬件访问服务
(1) 要实现JNI和HAL文件
实现com_android_server_LedService()用于注册JNI本地方法。
如果对硬件的操作非常简单,可以直接在JNI文件中调用open()/read()/write(),不适用HAL文件
(2) 修改onload.cpp
(3) 修改SystemService.java,先new一个LedService,然后再add这个LedService
(4) 既然上一步要new一个LedService,那么就必须先有一个LedService.java去调用那些本地方法
实现硬件操作。
(5) 作为client的应用程序还要获取这些操作硬件的接口,因此还要写一个ILedService.java(应该是主要用于binder通信吧)给App使用。

========================-===============================================
第1课第4.2节_Android硬件访问服务编写系统代码_P.mp4

-----------------------------------------------------------------------

1.实现ILedService.java
它非常简单,我们并不需要实现ILedService.java文件,而是需要实现一个AIDL(Android Interface Definition Language)文件,
有这个AIDL文件之后使用Android中的编译命令自动生成java文件
参考1:https://www.cnblogs.com/zhujiabin/p/6080806.html

参考2:IVibratorService.aidl
package android.os;
import android.os.VibrationEffect;

/** {@hide} */
interface IVibratorService
{
boolean hasVibrator();
boolean hasAmplitudeControl();
void vibrate(int uid, String opPkg, in VibrationEffect effect, int usageHint, IBinder token);
void cancelVibrate(IBinder token);
}

对照着实现自己的:
此例中对于一个应用程序,它只需要点亮和熄灭LED,不需要open和close,因此就只需要
在这个接口中声明一个control接口即可(也就是说可以从AIDL文件中看出普通的应用程序可
以使用哪些接口),如下:
interface ILedService
{
int ledCtrl(int which, int status);
}

注意:在编译这个AIDL文件之前,整个Android工程需要是已经编译过了的,把它放到Android源码工程中进行编译。

问题1:应该放在哪个目录下
模仿IVibratorService.aidl这个文件,就放在它所在的位置:frameworks/base/core/java/android/os

修改Android.mk文件(注意本层目录下没有Android.mk就到上层目录去寻找,最终在frameworks/base/core下找到Android.mk文件)
找到参考的IVibratorService.aidl:core/java/android/os/IVibratorService.aidl
仿照它在它下面添加:core/java/android/os/ILedService.aidl
然后在当前目录下执行mmm命令编译,它会帮我们生成ILedService.java文件:frameworks/base/core $ mmm .

mmm命令的使用:mmm <dir> 会更加<dir>下的Android.mk文件来编译,使用mmm的前提是Android内核工程已经编译过了。编译出的结果会放在
编译工程的out目录下,可以在out目录下find ./ -name ILedService.java 查找。打开生成的ILedService.java,发现它里面还是挺复杂的。


2. 应用程序怎么使用ILedService.java接口文件
IVibratorService.aidl文件最后也会生成一个IVibratorService.java的接口文件,参考它是怎么被使用的。在framework目录中检索IVibratorService。
在SystemVibrator类中:
public class SystemVibrator extends Vibrator {
private static final String TAG = "Vibrator";
private final IVibratorService mService; //定义一个IVibratorService对象

public SystemVibrator() {
//实例化对象
mService = IVibratorService.Stub.asInterface( //然后把它转成一个接口赋给mService对象
ServiceManager.getService("vibrator")); //首先getService()获得这个service
}
}
SystemVibrator.java(与ISystemVibrator.aidl ISystemVibrator.java对应)中通过mService这个对象使用了AIDL文件
中interface IVibratorService里面列出的所有函数。 ###################

3.所以使用ILedService.java的方法:
在LedSerivice.java中:
private final ILedService iLedService; //定义一个ILedService对象
iLedService = ILedService.Stub.asInterface(ServiceManager.getService("led")); //“led”是传入的参数 为什么传“led”作为参数?
之后就可以使用iLedService.ledCtrl(which, status)来控制led的亮灭了。
但是iLedService.ledCtrl(which, status)并不会直接调用系统调用函数来控制LED,它会把这个服务请求发给LedService.java,在LedService.java
中调用本地方法来操作LED,所以接下来就应该实现LedService.java

4.实现LedService.java
参考VibratorService.java
public class VibratorService extends IVibratorService.Stub { } //VibratorService继承了IVibratorService.Stub这个父类
对应的本例:
public class LedService extends ILedService.Stub { }

在生成的ILedService.java中可以看出Stub类是个abstract类,继承了ILedService这个类,而ILedService这个接口中定义了lecCtrl()方法,
根据抽象类的继承规则,LedService类中必须实现lecCtrl()方法。把lecCtrl()的签名直接从生成的ILedService.java中拷贝过来使用,因为签名中
throw了一些异常,保持一致。在lecCtrl()方法中直接调用native方法即可。
eg:
public class LedService extends ILedService.Stub {
private static final String TAG = "LedService"; //只有打印log才会使用这个信息

public int ledCtrl(int which, int status) throw xxxx { //函数签名直接从ILedService.aidl生成的ILedService.java中拷贝过来即可。
return native_ledCtrl(which, status); //调用native方法
}

public LedService() {
return native_ledOpen();
}

public static native int native_ledCtrl(which, status); //上面调用了native_lecCtrl,这里需要声明一下。
public static native int native_ledOpen(); //注意native需要在返回值前面
public static native void native_ledClose();
}

由于所有的硬件操作都要由LedService.java来执行,因此它需要open() ioctl() close()。可以在LedService类的构造函数中执行open()操作。
如上,LedService.java就实现完了。


5.LedService.java需要通过调用addService来告诉service_manager.c进程,因此需要修改SystemServer.java文件
依然仿照Vibrator的代码:
Slog.i(TAG, "Vibrator Service");
vibrator = new VibratorService(mSystemContext);
/*把这个vibrator服务告诉系统*/
ServiceManager.addService("vibrator", vibrator);
添加Led的:
Slog.i(TAG, "Led Service");
led = new LedService(); //LedService的构造函数中我们没有指定参数
/*把这个Led服务告诉系统*/
ServiceManager.addService("led", led); //注意这个“led”就是在LedService中调用getService()时传入的参数。
只有应用程序就可以调用ServiceManager.getService("led");来操作LED了。
LedService.java中调用了很多native方法,这些native方法需要通过JNI文件实现,下一步就需要实现com_android_server_LedService.cpp这个JNI文件


6.实现JNI文件com_android_server_LedService.cpp
同样也是参考vibrator的com_android_server_VibratorService.cpp文件:
实现LED的如下:
namespace android {
int register_android_server_VibratorService(JNIEnv *env)
{
/*把本地方法methods注册到LedService类中*/
return jniRegisterNativeMethods(env, "com/android/server/LedService", methods, NELEM(method_table));
}
}

然后构造本地方法methods:
static const JNINativeMethod methods[] = {
{"native_ledOpen", "()I", (void*)ledOpen},
{"native_ledClose", "()V", (void*)ledClose},
{"native_ledCtrl", "(II)I", (void*)ledCtrl},
};

然后再分别实现ledOpen、ledClose、ledCtrl 本地函数:

static jint fd;

jint ledOpen(JNIEnv *env, jobject cls) {
fd = open("/dev/leds", O_RDWR);
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native: ledOpen");
if (fd < 0) {
return 0;
} else {
return -1;
}
}


void ledOpen(JNIEnv *env, jobject cls) {
close(fd);
}

jint ledCtrl(JNIEnv *env, jobject cls, jint which, jint status) {
int ret = ioctl(fd, status, which);
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native: ledCtrl which=%d status=%d\n", which, status); /*这个主要是为了在Android Studio软件中打印,不用管*/
return ret;
}

参考Vibrator的打印替换打印函数:
将__android_log_print换为ALOGW()/ALOGE()等可以打印到串口的函数。

然后参考Vibrator来修改OnLoad.cpp来调用register_android_server_VibratorService()来注册这个JNI文件

注意:目前是在JNI文件中直接操作了硬件,以后会把对硬件的操作剥离出来放在HAL文件中。

7.执行流程再次回顾
到目前为止,除了Android应用程序的代码还没有修改,其它代码都写完了。
SystemServer.java中会先调用loadLibrary("android_servers")来加载一个C库,这个"android_servers"C库
对应的文件就是Onload.cpp和实现的一大堆JNI文件。loadLibrary会促使Onload.cpp中的JNI_OnLoad()方法被调用,
在此方法中给各个对应的service类注册本地方法。

SystemServer.java中加载完这个C库会调用startOtherServices()来启动各个service,通过
SystemServiceManager.startService(LedService.class)来把LedService注册到系统中,也就是告诉
service_manager.c进程。
此时App就可以知道有LedService存在并可以getService, 然后调用LedService中提供的接口来控制LED了。

8.修改Android.mk来编译JNI文件
JNI文件位置:
frameworks/base/services/core/jni/onload.cpp
frameworks/base/services/core/jni/com_android_server_LedService.cpp
修改frameworks/base/services/core/jni/Android.mk:
在VibratorService.cpp下面加:
$(LOCAL_REL_DIR)/com_android_server_LedService.cpp \

编译:
$ mmm frameworks/base/services
$ make snod //用于生成印象文件system.img
$ ./gen-img.sh //除了生成system.img之外,还会生成用户data等,此例不执行它也行。

说明:在frameworks/base/services目录下执行mmm命令的原因是修改的LedService.java和SystemServer.java所
依赖的Android.mk文件是在frameworks/base/services/core/Android.mk目录下。还修改了onload.cpp和com_android_server_LedService.cpp,
它两个对应的Android.mk在frameworks/base/services/core/jni/Android.mk目录下。一般来说上层目录的Android.mk文件会把它
子目录的Android.mk文件包含进来,但是frameworks/base/services/core/Android.mk中没有把jni文件夹下的Android.mk包含进来,
所以如果直接在core目录下mmm将导致JNI文件得不到编译。在向上一层目录,frameworks/base/services/Android.mk
中使用include $(wildcard $(LOCAL_PATH)/*/jni/Android.mk)把所有jni目录下的Android.mk都会编译进来,这样可以确保我们JNI部分
的修改可以被编译进来。frameworks/base/services/Android.mk里面同样使用
include $(patsubst %,$(LOCAL_PATH)/%/Android.mk,$(services))把本目录下的所有目录的Android.mk包含进来。

TODO:查Makefile中的wildcard和patsubst函数作用?

注意看frameworks/base/services/Android.mk,里面指定的LOCAL_MODULE := libandroid_services(就是SystemService.java中加载的),
这个库对应的源程序文件就是上面include $(wildcard $(LOCAL_PATH)/*/jni/Android.mk)里面编译的所有源文件。

9.烧写演示
PC上启动mini_tool, 然后把开发板设置为从SD卡启动,选中左栏的Android,此例中我们只烧写
system.img所以只选中“Android Rootfs/System Image”并提供image文件(生成路径:out/target/product/tiny4412/system.img)点击烧写即可。烧写完再设置
为从nandflash启动。

10.代码位置:https://github.com/weidongshan/SYS_0001_LEDDemo.git

=================================================================================
第1课第4.3节_Android硬件访问服务编写APP代码
----------------------------------------------
1.删除tag v6中的hardwcontrol目录,之前是以这个目录中的打印开演示操作硬件的。

2.使用一个类需要先import它。ILedService.java中package android.os, 所以App中的MainActivity.java中import android.os.ILedService;(同时也删除之前的import con.thisway.hardware.*)

3.App使用JNI文件
在MainActivity类中:
private ILedService iLedService = null;
然后再onCreate()中给它赋值:
iLedService = ILedService.Stub.asInterface(ServiceManager.getService("led")); //传参会生成一个临时对象,
getService("led")的名字要与addService("led")时的一样。
然后就可以直接iLedService.ledCtrl(which, status);来控制LED的状态了。

4.Android Studio中以红色高亮显示的代表找不到符号。其中
import android.os.ILedService;显示红色
ILedService.ledCtrl 显示红色
由于ILedService是我们自己定义的,所以Android Stdio肯定没有这个类,因此
我们要包含一些头文件。
(1)包含什么
ILedService来源于一个AIDL文件,我们可以假装修改这个AIDL文件,然后重新编译Android系统,看看
这个AIDL文件最终生成什么文件。
假装修改一下AIDL文件,然后编译, 看看它最终会涉及哪些文件:
$ mmm frameworks/base show commands > log.txt 2>&1 //TODO:查这个命令作用?
最后生成了framework.jar,是需要修改它吗?
Baidu搜索一大堆没有任何用处,之后用GoogleFQ搜索"android studio framework.jar" 第二篇文章就是我们想要的。

我们的问题的本质是:应用程序想去引用Android的hidden或内部API接口。
在AIDL文件生成的ILedService.java中的ILedService接口描述前面加了/**{@hide}*/l来注释,
就说明接口ILedService是Android的隐藏类。它并不导出来给应用程序使用,那我们怎么才是使用这个隐藏类呢:
copy out out/target/common/obj/JAVA_LIBRARIES/frameworks_intermediates/class.jar(better to rename it
as something like framework_all.jar)

是class.jar而不是frameworks.jar的原因:
frameworks.jar是dex格式的,而Android中运行的程序并不是原原本本的java程序,它是它java源代码和目标文件又转换为dex格式,
dex格式包含了Android对Java可执行文件做了一些优化。我们在编译源代码的时候需要原生态的Java目标文件。因此需要包含的是:
out/target/common/obj/JAVA_LIBRARIES/frameworks_intermediates/class.jar

(2)怎么包含
使用Google搜索"android studio jar", 第一篇文章:
https://stackoverflow.com/questions/16608135/android-studio-add-jar-as-library打开16位置处的超链接“Intellij's help”打开:
http://www.jetbrains.com/help/idea/creating-and-managing-modules.html里面告诉了我们操作方法。
方法:
a. 拷贝出来out/target/common/obj/JAVA_LIBRARIES/frameworks_intermediates/class.jar
b. 在AS中 file --> Project Structure --> 点击左上角的“+” --> 选中Import JAR/AAR Packages -->找到刚才拷贝出来的class.jar后添加。
在左边栏中可以看出来Modules下classes和app是并列的,待会app要求引用classes.
c.添加app对classes的依赖:点击app --> Dependencies --> "+" --> ModuleDependency --> ok --> ok.

此时已经解决了ILedService符号找不到的问题了。

5.然后重新编译,此时仍然报“找不到符号,符号:变量ServiceManager,位置:类MainActivity”
需要import ServiceManager,在ServiceManager.java中有“package android.os;”,因此import android.os.ServiceManager;
重新build。

6.然后又报了很多错误:
MainActivity.java中:
“错误:未报告的异常错误RemoteException;必须对其捕获或声明以便抛出”
选中报错部分的代码,对每一个报错的位置都按快捷键Ctrl+ALT+T会自动给你加上try{}catch{},选中的代码在try语句块中。

重新编译,已经都能编译成功。

7.点击AS中的运行,然后产生一个错误,点击AS右下角的“Gradle Console”打开这个窗口,它里面会有
更加详细的错误信息。
错误信息是:"Android Studio java.lang.OutOfMemoryError: GC overhead limit execteed", 在google上搜索这个错误:
搜索到的解决方法是:在app/src/build.gradle里面(sourceSets{}上面)加上:
dexOption {
javaMaxHeapSize "4g"
}
一旦更改了build.gradle文件AS提示应该先同步一下它,于是我们就点击标题栏下的sync。
然后再编译。

8.但是不幸的是又引入了新的错误,在AS的右下角GradleConsole窗口中看第一个报错信息
是“too many methods references: 82068” 在Google上搜索这个信息。
https://developer.android.com/tools/building/multidex#mdex-gradle
找到右边栏“Apps Over 65K Methods”对应的“Configuring Your App for Multidex with Gradle”标题对应的内容
要引用很多方法的话必须要使用多重的dex.
修正:
a.在app/src/build.gradle中的defaultConfig{}函数体的最后加上: multiDexEnabled ture
b.在dependencies{}函数体的最后加上: compile “com.android.support:multidex:1.0.0”
c.需要在AndroidManifest.xml的<application>语句段内加上:android:name="android.support.multidex.MultiDexApplication"
点击"sync now" 然后再执行编译,显示编译成功。

选中tiny4412单板,测试控制LED是没问题的。


9.列出此过程中参考的网址
How do I build the Android SDK with hidden and internal APIs available?
http://stackoverflow.com/questions/7888191/how-do-i-build-the-android-sdk-with-hidden-and-internal-apis-available

Creating a module library and adding it to module dependencies
https://www.jetbrains.com/idea/help/configuring-module-dependencies-and-libraries.html

a. java.lang.OutOfMemoryError: GC overhead limit exceeded
Android Studio Google jar causing GC overhead limit exceeded error
http://stackoverflow.com/questions/25013638/android-studio-google-jar-causing-gc-overhead-limit-exceeded-error

b. Too many field references
Building Apps with Over 65K Methods
https://developer.android.com/tools/building/multidex.html

10.代码位置
https://github.com/weidongshan/APP_0001_LEDDemo.git tag: v7

=================================================================================
第1课第4.4节_Android硬件访问服务编写HAL代码_P.mp4
-----------------------------------------------------
JNI文件访问HAL文件其实就是怎么使用dlopen()去打开HAL文件编译成的库和怎么调用它里面的函数。

1.应用程序是不会直接访问硬件的,而是由SystemServer来直接访问硬件,应用程序将访问硬件
的请求发给SystemServer,进行间接硬件访问。SystemServer访问硬件的实质就是Java语言调用C
函数。

2.对于那些操作比较复杂的硬件,建议把对硬件的操作写在一个HAL文件中,这个JNI文件除了向上注册
本地函数之外,还要去加载这个HAL文件,然后调用这个HAL文件里面的函数。使用一个单独的HAL文件来
操作硬件有2个好处:
① 容易修改,一旦发现需要修改硬件的操作代码,只需要修改这个HAL文件,然后把它编译成一个.so文件,
放入系统里面去就可以了。如果把对硬件的操作放在JNI文件中,需要修改硬件就需要重新编译系统重新烧
写系统。

② 可以起到保密作用,Linux内核遵循GPL协议的,也就意味着一旦使用Linux内核就需要公开Linux内核的源代码,
如果在Linux的驱动程序中实现这些硬件操作的话,那么就需要把这些硬件操作的所有源代码公开出去,而很多硬
件厂商并不想公开这些硬件操作的细节,于是他写一个最简单的Linux驱动程序,只用来操作某些寄存器,而硬件内
部的复杂操作放在这个HAL文件中,然后只提供HAL文件编译成的.so文件给你。这样就避开了这个GPL协议。Android遵循
的是Apache协议,你可以去修改Android的代码而不公开你的修改。

3.Android中对硬件的操作一般分为两个文件,一个是JNI文件,另一个是HAL文件。HAL文件负责具体的硬件操作,JNI负
责向Java注册本地函数,和加载这个HAL文件然后调用它提供的函数。JNI文件和HAL文件都是使用C来写的,JNI加载HAL的
实质就是怎么使用dlopen()来加载动态库。Android对它进行了封装,变为hw_get_module()

external\chromium_org\third_party\hwcplus\src\hardware.c
hw_get_module(“led”)
1.模块名==>动态库文件名
hw_get_module_by_class("led", NULL)
property_get() //“subname”这里面查找的属性的值
hw_module_exit() //用于判断“name”.“subname”.so是否存在,会在下面三个位置寻找。

2.加载动态库
load()
dlopen(file_path_name)
dlsym("HMI") //从.so文件中获得名为HMI的hw_module_t结构体
strcmp(id, hmi->id) 判断名字是否一致(hmi->id, "led") 若一致就表示找到了这个模块。

(1) hw_module_exit()查找动态库的位置
a. HAL_LIBRARY_PATH 环境变量 在串口上#echo $HAL_LIBRARY_PATH 没有,说明没有设置。 ##############
b. /vender/lib/hw
c. /system/lib/hw

(2) property_get() 涉及到Android的一个重要系统,叫属性系统。
属性就是键值对(name=value),可以根据name获得一个value. 此例中获得的属性值赋值为
库名中的subname.
led.XXX.so XXX就是来源于此属性
a. ro.hardware.led.so //name led放在后面了,可以使用# getprop ro.hardware.led来检查是否有这个属性。
发现没有这个属性,就会去variant_keys[]指针数组中找属性:
ro.hardware //同样可以使用# getprop ro.hardware来查看属性是否存在。显示ro.hardware=tiny4412
ro.product.board //为tiny4412
ro.board.platform //为exynos4
ro.arch //为空
=====>因此就在上面abc三个目录下查找一下有没有led.tiny4412.so或led.exynos4.so这个文件存在。

如果都没有找到,就会执行hw_module_exists("default")来查找led.default.so,如果找到了这个.so文件,其
路径就会包存在hw_module_exists的第一个参数里面。

总结:
1.JNI怎么使用HAL
a. 调用hw_get_module 获得一个hw_module_t的结构体
b. 调用module->methods->open(module, name(这是device的name), &device) 获得一个hw_device_t结构体。
将hw_device_t结构转换为自己定义的结构体。

2.HAL怎么写
a. 与上面a对应,应该实现一个名为HMI的hw_module_t结构体
b. 与上面b对应,应该实现一个open(),它会根据name返回一个设备自定义结构体,但是这个结构体的第一个成员需要是
hw_device_t结构体(前面的强制类型转换决定的),其它成员任意。
注意实现代码中只有一个设备,就没有去判断名字了,实际上应该根据HAL文件的open()中的id来返回对应的设备结构体。


led_hal.h参考的是lights.h

参考1:com_android_server_lights_LightsService.cpp
它里面调用一次hw_get_module()获得模块后调用多次get_device(),也就说明了一个module(一个.so)
文件里面可以支持多个设备。所有我们还需要通过get_device()从这个module里面获得某个设备。

参考2:vibrator.c //hardware/libhardware/modules/vibrator/vibrator.c
实现的led_hal.h头文件存放的位置可以参考vibrator.h文件。

参考3:Android.mk也灿开vibrator.c的
LOCAL_MODULE := led.default //最后生成led.default.so文件(上面有提到它)
LOCAL_MODULE_RELATIVE_PATH := hw //编译出的文件存放在/system/lib/hw下
LOCAL_C_INCLUDES := hardware/libhardware // 头文件的存放位置为此目录下的include目录下
LOCAL_SHARED_LIBRARIES := liblog //用来打印信息的函数使用了这个库
LOCAL_MODULE_TAGS := eng //开发版本,因为编译这个系统的时候选择的是tiny4412_eng版本。

=====>怎么搭建环境参考哪个pdf文档。

4.翻译 man dlopen
5.HAL文件中使用的ALOGI()打印函数需要包含头文件utils/log.h, 此函数中需要一个Tag, 需要自己指定
到时可以根据Tag信息把打印筛选出来。

6.编译
$ mmm framework/base/services //编译JNI
$ mmm hardware/libhardware/modules/led //编译C库
$ make snod //干嘛的?
$ ./gen-img.sh //生成映像文件

7.烧录
参数手册有介绍烧录方法

8.Android系统中打印方法介绍
a.有三类打印信息:app、system、radio
对应程序中的打印函数(宏):ALOGx、SLOGx、RLOGx来打印。
b.x表示6中打印级别,有:
V Verbose
D Debug
I Info
W Warn
E Error
F Fatal
S Silent 所有log都不会打印
eg:
#define LOG_TAG "LedHal"
ALOGI("led_open: %d", fd);

c.打印出来的格式为:
I/LedHal (1987): led_open: 65
(级别)/LOG_TAG (进程号) 打印信息

d.使用logcat命令查看App打印出来的log
logcat LedHal:I *:S 使用logcat的过滤规则,-h可以查看,为<tag>[:priority],第一种过滤规则:第二种过滤规则
logcat LedHal:I *:S 打印tag为LedHal的且优先级高于I的log,过滤掉的信息会流到下一个规则 *:S, *表示所有的tag,S表示这个信息不想被打印。此条命令作用:只打印tag为LedHal且优先级大于等于I的log.
logcat既可以在串口上执行,也可以使用adb远程执行。
检索信息:
logcat | grep LedHal

过滤规则可以有很多,但是其只有七层V D 到S,

9.试验代码下载
$ git clone https://github.com/weidongshan/APP_0001_LEDDemo.git //但是需要用户名和密码,目前没有,===>需要问WDS要,看看前几节视频有没有讲?
更新同步:git pull origin
取出指定版本:git checkout v1 //有JNI没有HAL
取出指定版本:git checkout v2 //有JNI有HAL

TODO:查Android各个目录的作用 ?

===========================================================================================
第1课第4.5节_Android硬件访问服务使用反射_P
------------------------------------------
这是对上一节的补充:
(1)之前编译这个App程序的时候由于需要使用到class.jar里面的函数,因此就把它编译进了我们的App里面,导致一个简单
的控制LED的App程序竟然有6M之大。这里我们使用class.jar中的函数并且不包含class.jar文件到编译输出的apk(app/build/outputs/apk/app-debug.apk)包中。

(2)修改编译选项,不包含进去class.dex(和class2.dex)
AS中file --> Project Structure --> app --> Dependencies -->classes的Scope栏中修改为"Provided"
Provided表示只会使用class.jar来编译,但是并不会把它打包到我们的apk里。
重新编译,然后发现app/build/outputs/apk/app-debug.apk就变为1M多了。


1.使用反射
a.先注释掉MainActivity.java中的:
import android.os.ILedService;
import android.os.ServiceManager;
b.也注释掉:
iLedService = ILedService.Stub.asImterface(ServiceManager.getService("led"));
待会要使用反射来实现这句话

2.注意到ServiceManager是个隐藏的类(因为其前面有/**@hide*/注释),ServiceManager的
getService()是一个静态方法,参数为string类类型。
因此将注释掉的b替换为:
/*获取ServiceManager类的getService方法,由于getService()的参数是String类类型,因此参数2也指定为类类型*/
Method getService = Class.forName("android.os.ServiceManager").getMethod("getService", String class);
/*
* arg1是一个对象,但是由于getService方法是个静态方法,不需要实例化对象,因此传null
* 返回是一个IBinder对象(因为ServiceManager的getService返回的是一个IBinder对象,所以这里也返回的是IBinder对象)
* IBinder对象并不是一个隐藏接口。
*/
IBinder ledService = getService.invoke(null, "led");

到这里已经实现了b中注释掉的“ServiceManager.getService("led")”部分

3.ILedService.Stub是ILedService的一个内部类,首先要获得这个内部类。
/*
* 这个由AIDL文件生成的接口文件ILedService.java中Stub内部类的asInterface()定义为:
* static android.os.ILedService asInterface(android.os.IBinder obj);
* 参数是IBinder对象,所以上面getMethod的第二个参数是IBinder类型。
*/
Method asInterface = Class.forName("android.os.ILedService$Stub").getMethod("asImterface", IBinder.class); //内部类使用”$“符号进行引用。

/*
* 注意ILedService.java中的asInterface()会返回一个ILEDService.stub.Proxy()的对象,但是我们没有
* 直接把ILedService import进来,所以我们不能直接使用ILEDService这个类型。可以使用期父类object。
* asInterface也是一个static方法,所以arg1可以写为null。arg2是一个Ibinder的对象(见ILedService.java
* 中的asInterface()的arg2),就是前面得到的ILedService。
*/
Object proxy = asInterface.invoke(null, ILedService);

之后就可以使用这个proxy对象里面的ledCtrl()方法来操作我们的LED了。

因为在ILedService.java中的class proxy类中有一个
@Override
public int(int which, int status) {
  android.os.Parcel _data = android.os.Parcel.obtain();
  android.os.Parcel _reply = android.os.Parcel.obtain();
  int result;
  try {
    /*
     * 利用binder驱动把请求发给硬件访问服务,
     * 由硬件访问服务来操作硬件。
     */
    _data.writeInterfaceToken(DESCRIPTOR);
    _data.witeInt(which);
    _data.witeInt(status);
    mRemote.transact(Stub.TRANSACTION_ledCtrl, _data, _reply, 0);
    _reply.readException();
    _result = _replay.readInt();
  } finally {
    _reply.recycle();
    .recycle();
  }
  return _result;
}

MainActivity.java中:
Method ledCtrl = Class.forName("android.os.ILedService$Stub$Proxy").getMethod("ledCtrl", int.class, int.class);
之后就可以通过ledCtrl()来操作LED了。

5.使用ledCtrl控制LED
在ILedService.java看出ledCtrl()并不是static的,因此引用它需要实例化一个对象。这个实例化对象应该是proxy的实例化对象。
eg: 第0个LED点亮
ledCtrl.invoke(proxy, 0 1);

6.编译遇到的问题:
a.AS中编译过程中报IBinder找不到符号
解决:解决方法是需要包含某些包。在java源码frameworks目录下的IBinder.java中package android.os, 所以AS中import android.os.IBinder;

b.接着编译报错“Object无法转换成IBinder”
解决:将IBinder ledService = getService.invoke(null, "led");改为Object ledService = getService.invoke(null, "led");
报错分析:getService在ServiceManager.java中的定义是public static IBinder getService(String name);返回的明明是IBinder类型,为什么这里不是使用IBinder类型接收getService.invoke()的返回参数呢:
注意这里的这个getService是通过getMethod得到的。在AS中双击选中getMethod然后按“Shift+F1”发现列出多个选项不好找。然后选中其前面的class然后按“Shift+F1”打开Class.html文件,在此网页上搜索getMethod, 点击进入其invoke方法中,发现返回的是个Object类型。
也就是说getService虽然返回的是IBinder对象,但是在调用getService.invoke时就会向上转换为Object对象,所以返回的是Object对象。
假若就想使用IBinder,让其再向下转换的写法:
IBinder ledService = (IBinder)getService.invoke(null, "led"); //加上强制类型转换

c.接着编译报错“在相应的try语句主体中不能抛出异常错误RemoteException”
解决:
之前的try{...}catch(RemoteException e){...}就不能使用了,需要改为其它异常,之前使用的是iLedService.ledCtrl(i, 1)。删除掉它,然后按Ctrl+Alt+T,选中try...catch..会自动补上要捕获的异常。

7.总结
使用反射的话就可以不使用Android隐藏的ILedService接口,也就不需要花费很多精力去包含class.jar依赖。但是使用反射看起来很别扭,还是建议使用把class.jar导入到工程里的方法。

8.代码路径:https://github.com/weidongshan/APP_0001_LEDDemo.git

9.本节涉及的AS的快捷键使用方法
a.在AS中ctrl+b跳到变量定义的地方。
b.可以在AS左下角logcat窗口中右击设置logcat的过滤规则。
c.Shift+F1打开帮助文档。


本文参考链接:https://www.cnblogs.com/hellokitty2/p/10468616.html