zoukankan      html  css  js  c++  java
  • 硬件访问服务学习笔记_WDS

    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动态库,排查:appuildoutputsapk下面有生成.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_Demo1appuildoutputsapk下面有生成.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 ", 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搜索一大堆没有任何用处,之后用Google翻墙搜索"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()

    externalchromium_org hird_partyhwcplussrchardware.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打开帮助文档。

  • 相关阅读:
    Atitit. visual studio vs2003 vs2005 vs2008  VS2010 vs2012 vs2015新特性 新功能.doc
    Atitit. C#.net clr 2.0  4.0新特性
    Atitit. C#.net clr 2.0  4.0新特性
    Atitit.通过null 参数 反射  动态反推方法调用
    Atitit.通过null 参数 反射  动态反推方法调用
    Atitit..net clr il指令集 以及指令分类  与指令详细说明
    Atitit..net clr il指令集 以及指令分类  与指令详细说明
    Atitit.变量的定义 获取 储存 物理结构 基本类型简化 隐式转换 类型推导 与底层原理 attilaxDSL
    Atitit.变量的定义 获取 储存 物理结构 基本类型简化 隐式转换 类型推导 与底层原理 attilaxDSL
    Atitit.跨语言反射api 兼容性提升与增强 java c#。Net  php  js
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/10468616.html
Copyright © 2011-2022 走看看