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

  • 相关阅读:
    HTTP缓存——协商缓存(缓存验证)
    谷粒商城学习——P119-121映射
    powershell download, 兼容低版本 powershell
    InstallShield 2020 R3 破解补丁 支持 VS2019
    解决react使用antd table组件固定表头后,表头和表体列不对齐以及配置fixed固定左右侧后行高度不对齐
    numFormat 用于千分位的操作
    Jquery+NProgress实现网页进度条显示
    js变量前的+是什么意思
    有没有人遇到过用charles做js文件map时,文件若比较大,会被截掉一些(即映射到的文件不完整)的问题?
    如何写代码
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/10468616.html
Copyright © 2011-2022 走看看