作者:唐老师,华清远见嵌入式学院讲师。

通过前两节HAL框架分析和JNI概述,我们对Android提供的Stub HAL有了比较详细的了解了,下面我们来看下led的实例,写驱动点亮led灯,就如同写程序,学语言打印HelloWorld一样,如果说打印HelloWorld是一门新语言使用的第一声吆喝,那么点亮led灯就是我们学习HAL的一座灯塔,指挥我们在后面的复杂的HAL代码里准确找到方向。

LedHAL实例架构

上图描述了我们Led实例的框架层次:

l LedDemo.java:是我们写的Android应用程序

l LedService.java:是根据Led HAL封装的Java框架层的API,主要用于向应用层提供框架层API,它属于Android的框架层

l libled_runtime.so:由于Java代码不能访问HAL层,该库是LedService.java对应的本地代码部分

l led.default.so:针对led硬件的HAL代码

LedDemo通过LedService提供的框架层API访问Led设备,LedService对于LedDemo应用程序而言是Led设备的服务提供者,LedService运行在Dalvik中没有办法直接访问Led硬件设备,它只能将具体的Led操作交给本地代码来实现,通过JNI来调用Led硬件操作的封装库libled_runtime.so,由HAL Stub框架可知,在libled_runtime.so中首先查找注册为led的硬件设备module,找到之后保存其操作接口指针在本地库中等待框架层LedService调用。led.default.so是HAL层代码,它是上层操作的具体实施者,它并不是一个动态库(也就是说它并没有被任何进程加载并链接),它只是在本地代码查找硬件设备module时通过ldopen”杀鸡取卵”找module,返回该硬件module对应的device操作结构体中封装的函数指针。

其调用时序如下:

Led HAL实例代码分析

我们来看下led实例的目录结构:

主要文件如下:

com.hello.LedService.cpp:它在frameworks/services/jni目录下,是的Led本地服务代码
led.c:HAL代码
led.h:HAL代码头文件
LedDemo.java:应用程序代码
LedService.java:Led框架层服务代码

在Android的源码目录下,框架层服务代码应该放在frameworks/services/java/包名/目录下,由Android的编译系统统一编译生成system/framework/services.jar文件,由于我们的测试代码属于厂商定制代码,尽量不要放到frameworks的源码树里,我将其和LedDemo应用程序放在一起了,虽然这种方式从Android框架层次上不标准。

另外,本地服务代码的文件名要和对应的框架层Java代码的名字匹配(包名+类文件名,包目录用“_“代替)。有源码目录里都有对应的一个Android.mk文件,它是Android编译系统的指导文件,用来编译目标module。

1) Android.mk文件分析

先来看下led源码中①号Android.mk:

[plain] view plaincopyprint?

1. include $(call all-subdir-makefiles)

代码很简单,表示包含当前目录下所有的Android.mk文件

先来看下led_app目录下的③号Android.mk:

[plain] view plaincopyprint?

              1. # 调用宏my-dir,这个宏返回当前Android.mk文件所在的路径
            2. LOCAL_PATH:= $(call my-dir)
            3.
            4. # 包含CLEAR_VARS变量指向的mk文件build/core/clear_vars.mk,它主要用来清除编译时依赖的编译变量
            5. include $(CLEAR_VARS)
            6.
            7. # 指定当前目标的TAG标签,关于其作用见前面Android编译系统章节
            8. LOCAL_MODULE_TAGS := user
            9.
            10. # 当前mk文件的编译目标模块
            11. LOCAL_PACKAGE_NAME := LedDemo
            12.
            13. # 编译目标时依赖的源码,它调用了一个宏all-java-files-under,该宏在build/core/definitions.mk中定义
            14. # 表示在当前目录下查找所有的java文件,将查找到的java文件返回
            15. LOCAL_SRC_FILES := $(callall-java-files-under, src)
            16.
            17. # 在编译Android应用程序时都要指定API level,也就是当前程序的编译平台版本
            18. # 这里表示使用当前源码的版本
            19. LOCAL_SDK_VERSION := current
            20.
            21. # 最重要的就是这句代码,它包含了一个文件build/core/package.mk,根据前面设置的编译变量,编译生成Android包文件,即:apk文件
            22. include $(BUILD_PACKAGE)

上述代码中都加了注释,基本上每一个编译目标都有类似上述的编译变量的声明:

LOCAL_MODULE_TAGS
LOCAL_PACKAGE_NAME
LOCAL_SRC_FILES

由于所有的Android.mk最终被编译系统包含,所以在编译每个目标模块时,都要通过LOCAL_PATH:= $(call my-dir)指定当前目标的目录,然后调用include $(CLEAR_VARS)先清除编译系统依赖的重要的编译变量,再生成新的编译变量。

让我们来看看LedDemo目标对应的源码吧。

2) LedDemo代码分析

学习过Android应用的同学对其目录结构很熟悉,LedDemo的源码在src目录下。

@ led_app/src/com/farsight/LedDemo.java:

[java] view plaincopyprint?

            1. package com.hello;
            2.
            3. import com.hello.LedService;
            4.
            5. import com.hello.R;
            6.
            7. importandroid.app.Activity;
            8.
            9. importandroid.os.Bundle;
            10.
            11. importandroid.util.Log;
            12.
            13. importandroid.view.View;
            14.
            15. import android.view.View.OnClickListener;
            16.
            17. importandroid.widget.Button;
            18.
            19.
            20.
            21. public classLedDemo extends Activity {
            22.   privateLedService led_svc;
            23.   private Buttonbtn;
            24.   private booleaniflag = false;
            25.   private Stringtitle;
            26.
            27.   /** Calledwhen the activity is first created. */
            28.   @Override
            29.   public void onCreate(Bundle savedInstanceState) {
            30.    super.onCreate(savedInstanceState);
            31.    setContentView(R.layout.main);
            32.
            33.    Log.i("Java App", "OnCreate");
            34.    led_svc =new LedService();
            35.    btn =(Button) this.findViewById(R.id.Button01);
            36.    this.btn.setOnClickListener(new OnClickListener() {
            37.     public void onClick(View v) {
            38.      Log.i("Java App", "btnOnClicked");
            39.      if (iflag) {
            40.       title = led_svc.set_off();
            41.       btn.setText("Turn On");
            42.       setTitle(title);
            43.       iflag = false;
            44.      } else {
            45.       title = led_svc.set_on();
            46.       btn.setText("Turn Off");
            47.       setTitle(title);
            48.       iflag = true;
            49.      }
            50.     }
            51.    });
            52.   }
            53.  }

代码很简单,Activity上有一个按钮,当Activity初始化时创建LedService对象,按钮按下时通过LedService对象调用其方法set_on()和set_off()。

3) LedService代码分析

我们来看下LedService的代码:

@led_app/src/com/farsight/LedService.java:

[java] view plaincopyprint?

            1. package com.hello;
            2. import android.util.Log;
            3.
            4. public class LedService {
            5.
            6.  /*
            7.  * loadnative service.
            8.  */
            9.  static { // 静态初始化语言块,仅在类被加载时被执行一次,通常用来加载库
            10.   Log.i ("Java Service" , "Load Native Serivce LIB" );
            11.   System.loadLibrary ( "led_runtime" );
            12.  }
            13.
            14.  // 构造方法
            15.  public LedService() {
            16.   int icount ;
            17.
            18.   Log.i ("Java Service" , "do init Native Call" );
            19.   _init ();
            20.   icount =_get_count ();
            21.   Log.d ("Java Service" , "led count = " + icount );
            22.   Log.d ("Java Service" , "Init OK " );
            23.  }
            24.
            25.  /*
            26.  * LED nativemethods.
            27.  */
            28.  public Stringset_on() {
            29.   Log.i ("com.hello.LedService" , "LED On" );
            30.   _set_on();
            31.   return"led on" ;
            32.  }
            33.
            34.  public String set_off() {
            35.   Log.i ("com.hello.LedService" , "LED Off" );
            36.   _set_off();
            37.   return"led off" ;
            38.  }
            39.
            40.  /*
            41.  * declare all the native interface.
            42.  */
            43.  private static native boolean _init();
            44.  private static native int _set_on();
            45.  private static native int _set_off();
            46.  private static native int _get_count();
            47.
            48. }

通过分析上面代码可知LedService的工作:

l 加载本地服务的库代码

l 在构造方法里调用_init本地代码,对Led进行初始化,并调用get_count得到Led灯的个数

l 为LedDemo应用程序提供两个API:set_on和set_off,这两个API方法实际上也是交给了本地服务代码来操作的

由于Java代码无法直接操作底层硬件,通过JNI方法将具体的操作交给本地底层代码实现,自己只是一个API Provider,即:服务提供者。

让我们来到底层本地代码,先看下底层代码的Android.mk文件:

@ frameworks/Android.mk:

[plain] view plaincopyprint?

            1. LOCAL_PATH:= $(call my-dir)
            2. include $(CLEAR_VARS)
            3.
            4. LOCAL_MODULE_TAGS := eng
            5. LOCAL_MODULE:= libled_runtime          # 编译目标模块
            6. LOCAL_SRC_FILES:= \
            7. services/jni/com_farsight_LedService.cpp
            8.
            9.
            10. LOCAL_SHARED_LIBRARIES := \          # 编译时依赖的动态库
            11. libandroid_runtime \
            12. libnativehelper \
            13. libcutils \
            14. libutils \
            15. libhardware
            16.
            17. LOCAL_C_INCLUDES += \            #编译时用到的头文件目录
            18. $(JNI_H_INCLUDE)
            19.
            20. LOCAL_PRELINK_MODULE := false       # 本目标为非预链接模块
            21. include $(BUILD_SHARED_LIBRARY)      # 编译生成共享动态库

结合前面分析的Android.mk不难看懂这个mk文件。之前的mk文件是编译成Android apk文件,这儿编译成so共享库,所以LOCAL_MODULE和include $(BUILD_SHARED_LIBRARY)与前面mk文件不同,关于Android.mk文件里的变量作用,请查看Android编译系统章节。

总而言之,本地代码编译生成的目标是libled_runtime.so文件。

4) Led本地服务代码分析

我们来看下本地服务的源码:
@ frameworks/services/jni/com_hello_LedService.cpp:

[cpp] view plaincopyprint?

            1. #define LOG_TAG "LedService"
            2. #include "utils/Log.h"
            3. #include <stdlib.h>
            4. #include <string.h>
            5. #include <unistd.h>
            6. #include <assert.h>
            7. #include <jni.h>
            8. #include "../../../hardware/led.h"
            9.
            10. static led_control_device_t *sLedDevice = 0;
            11.  static led_module_t* sLedModule=0;
            12.
            13.  static jint get_count(void)
            14.  {
            15.  LOGI("%sE", __func__);
            16.  if(sLedDevice)
            17.   returnsLedDevice->get_led_count(sLedDevice);
            18.  else
            19.   LOGI("sLedDevice is null");
            20.  return 0;
            21.  }
            22.
            23.
            24.  static jint led_setOn(JNIEnv* env, jobject thiz) {
            25.  LOGI("%sE", __func__);
            26.  if(sLedDevice) {
            27.   sLedDevice->set_on(sLedDevice);
            28.  }else{
            29.   LOGI("sLedDevice is null");
            30.  }
            31.  return 0;
            32.   }
            33.
            34.   static jint led_setOff(JNIEnv* env, jobject thiz) {
            35.  LOGI("%s E", __func__);
            36.  if(sLedDevice) {
            37.   sLedDevice->set_off(sLedDevice);
            38.  }else{
            39.   LOGI("sLedDevice is null");
            40.  }
            41.  return 0;
            42.  }
            43.
            44.  static inline int led_control_open(const structhw_module_t* module,
            45.   structled_control_device_t** device) {
            46.  LOGI("%s E ", __func__);
            47.  returnmodule->methods->open(module,
            48.  LED_HARDWARE_MODULE_ID, (struct hw_device_t**)device);
            49.  }
            50.
            51.  static jint led_init(JNIEnv *env, jclass clazz)
            52.  {
            53.  led_module_tconst * module;
            54.  LOGI("%s E ", __func__);
            55.  if(hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0){
            56.   LOGI("get Module OK");
            57.   sLedModule = (led_module_t *) module;
            58.   if(led_control_open(&module->common, &sLedDevice) != 0) {
            59.    LOGI("led_init error");
            60.    return-1;
            61.   }
            62.  }
            63.
            64.  LOGI("led_init success");
            65.  return 0;
            66 . }
            67.
            68.
            69.
            70. /*
            71. *
            72. * Array ofmethods.
            73. * Each entryhas three fields: the name of the method, the method
            74. * signature,and a pointer to the native implementation.
            75. */
            76.  static const JNINativeMethod gMethods[] = {
            77.  {"_init", "()Z",(void*)led_init},
            78.  {"_set_on", "()I",(void*)led_setOn },
            79.  {"_set_off", "()I",(void*)led_setOff },
            80.  {"_get_count", "()I",(void*)get_count },
            81.  };
            82.
            83. static int registerMethods(JNIEnv* env) {
            84.  static constchar* const kClassName = "com/hello/LedService";
            85.  jclass clazz;
            86.  /* look upthe class */
            87.  clazz =env->FindClass(kClassName);
            88.  if (clazz ==NULL) {
            89.   LOGE("Can't find class %s\n", kClassName);
            90.   return-1;
            91.  }
            92.
            93.  /* registerall the methods */
            94.  if(env->RegisterNatives(clazz, gMethods,
            95.   sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)
            96.  {
            97.   LOGE("Failed registering methods for %s\n", kClassName);
            98.   return -1;
            99.  }
            100.  /* fill outthe rest of the ID cache */
            101.  return 0;
            102. }
            103.
            104.
            105.
            106. /*
            107. * This iscalled by the VM when the shared library is first loaded.
            108. */
            109. jint JNI_OnLoad(JavaVM* vm, void* reserved) {
            110.  JNIEnv* env= NULL;
            111.  jint result= -1;
            112.  LOGI("JNI_OnLoad");
            113.  if(vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
            114.   LOGE("ERROR: GetEnv failed\n");
            115.   gotofail;
            116.  }
            117.
            118.  assert(env!= NULL);
            119.  if(registerMethods(env) != 0) {
            120.   LOGE("ERROR: PlatformLibrary nativeregistration failed\n");
            121.   gotofail;
            122.  }
            123.  /* success-- return valid version number */
            124.  result =JNI_VERSION_1_4;
            125.
            126.  fail:
            127.  return result;
            128. }

这儿的代码不太容易读,因为里面是JNI的类型和JNI特性的代码,看代码先找入口。LedService.java框架代码一加载就调用静态初始化语句块里的System.loadLibrary ( "led_runtime" ),加载libled_runtime.so,该库刚好是前面Android.mk文件的目标文件,也就是说LedService加载的库就是由上面的本地代码生成的。当一个动态库被Dalvik加载时,首先在Dalvik会回调该库代码里的JNI_OnLoad函数。也就是说JNI_OnLoad就是本地服务代码的入口函数。

JNI_OnLoad的代码一般来说是死的,使用的时候直接拷贝过来即可,vm->GetEnv会返回JNIEnv指针,而这个指针其实就是Java虚拟机的环境变量,我们可以通过该指针去调用JNI提供的方法,如FindClass等,调用registerMethods方法,在方法里通过JNIEnv的FindClass查找LedService类的引用,然后在该类中注册本地方法与Java方法的映射关系,上层Java代码可以通过这个映射关系调用到本地代码的实现。

RegisterNatives方法接收三个参数:

l 第一个参数jclass:要注册哪个类里的本地方法映射关系

l 第二个参数JNINativeMethod*:这是一个本地方法与Java方法映射数组,JNINativeMethod是个结构体,每个元素是一个Java方法到本地方法的映射。

[cpp] view plaincopyprint?

            1. typedef struct {
            2.  constchar* name;
            3.  constchar* signature;
            4.  void*fnPtr;
            5. } JNINativeMethod;

name:表示Java方法名
signature:表示方法的签名
fnPtr:Java方法对应的本地方法指针

l 第三个参数size:映射关系个数

由代码可知,Java方法与本地方法的映射关系如下:

Java方法 本地方法
void _init() jint led_init(JNIEnv *env, jclass clazz)
int _set_on() jint led_setOn(JNIEnv* env, jobject thiz)
int _set_off() jint led_setOff(JNIEnv* env, jobject thiz)
int _get_count() jint get_count(void)

通过上表可知,本地方法参数中默认会有两个参数:JNIEnv* env, jobject thiz,分别表示JNI环境和调用当前方法的对象引用,当然你也可以不设置这两个参数,在这种情况下你就不能访问Java环境中的成员。本地方法与Java方法的签名必须一致,返回值不一致不会造成错误。

现在我们再来回顾下我们的调用调用流程:

l LedDemo创建了LedService对象

l LedService类加载时加载了对应的本地服务库,在本地服务库里Dalvik自动调用JNI_OnLoad函数,注册Java方法和本地方法映射关系。

根据Java语言特点,当LedDemo对象创建时会调用其构造方法LedService()。

[cpp] view plaincopyprint?

            1. // 构造方法
            2. public LedService() {
            3.  int icount ;
            4.  Log.i ("Java Service" , "do init Native Call" );
            5.  _init ();
            6.  icount =_get_count ();
            7.  Log.d ("Java Service" , "led count = " + icount );
            8.  Log.d ("Java Service" , "Init OK " );
            9. }

在LedService构造方法里直接调用了本地方法_init和_get_count(通过native保留字声明),也就是说调用了本地服务代码里的jint led_init(JNIEnv *env, jclass clazz)和jintget_count(void)。

在led_init方法里的内容就是我们前面分析HAL框架代码的使用规则了。

l 通过hw_get_module方法查到到注册为LED_HARDWARE_MODULE_ID,即:”led”的module模块。

l 通过与led_module关联的open函数指针打开led设备,返回其device_t结构体,保存在本地代码中,有的朋友可能会问,不是本地方法不能持续保存一个引用吗?由于device_t结构是在open设备时通过malloc分配的,只要当前进程不死,该指针一直可用,在这儿本地代码并没有保存Dalvik里的引用,保存的是mallco的分配空间地址,但是在关闭设备时记得要将该地址空间free了,否则就内存泄漏了。

l 拿到了led设备的device_t结构之后,当LedDemo上的按钮按下时调用LedService对象的set_on和set_off方法,这两个LedService方法直接调用了本地服务代码的对应映射方法,本地方法直接调用使用device_t指向的函数来间接调用驱动操作代码。

好吧,让我们再来看一个详细的时序图:

不用多解释了。

最后一个文件,HAL对应的Android.mk文件:

@ hardware/Android.mk:

[plain] view plaincopyprint?

            1. LOCAL_PATH := $(call my-dir)
            2. include $(CLEAR_VARS)
            3.
            4. LOCAL_C_INCLUDES += \
            5. include/
            6.
            7. LOCAL_PRELINK_MODULE := false
            8. LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
            9. LOCAL_SHARED_LIBRARIES := liblog
            10. LOCAL_SRC_FILES := led.c
            11. LOCAL_MODULE := led.default
            12. include $(BUILD_SHARED_LIBRARY)

注:LOCAL_PRELINK_MODULE:= false要加上,否则编译出错
指定目标名为:led.default
目标输入目录LOCAL_MODULE_PATH为:/system/lib/hw/,不指定会默认输出到/system/lib目录下。

根据前面HAL框架分析可知,HAL Stub库默认加载地址为:/vendor/lib/hw/或/system/lib/hw/,在这两个目录查找:硬件id名.default.so,所以我们这儿指定了HAL Stub的编译目标名为led.default,编译成动态库,输出目录为:$(TARGET_OUT_SHARED_LIBRARIES)/hw,TARGET_OUT_SHARED_LIBRARIES指/system/lib/目录。

5) 深入理解

我们从进程空间的概念来分析下我们上面写的代码。

我们前面的示例代码中,将LedDemo.java和LedService.java都放在了一个APK文件里,这也就意味着这个应用程序编译完之后,它会运行在一个Dalvik虚拟机实例中,即:一个进程里,在LedService.java中加载了libled_runtime.so库,通过JNI调用了本地代码,根据动态库的运行原理,我们知道,libled_runtime.so在第一次引用时会被加载到内存中并映射到引用库的进程空间中,我们可以简单理解为引用库的程序和被引用的库在一个进程中,而在libled_runtime.so库中,又通过dlopen打开了库文件led.default.so(该库并没有被库加载器加载,而是被当成一个文件打开的),同样我们可以理解为led.default.so和libled_runtime.so在同一个进程中。

由此可见,上面示例的Led HAL代码全部都在一个进程中实现,在该示例中的LedService功能比较多余,基本上不能算是一个服务。如果LedDemo运行在两个进程中,就意味着两个进程里的LedService不能复用,通常我们所谓的Service服务一般向客户端提供服务并且同时可以为多个客户端服务(如下图),所以我们的示例Led HAL代码不是完美的HAL模型,我们后面章节会再实现一个比较完美的HAL架构。

文章来源:华清远见嵌入式学院原文地址:http://www.embedu.org/Column/Column757.htm

更多相关嵌入式免费资料查看华清远见讲师博文>>

深入浅出 - Android系统移植与平台开发(十) - led HAL简单设计案例分析的更多相关文章

  1. 深入浅出 - Android系统移植与平台开发(一)

    深入浅出 - Android系统移植与平台开发(一) 分类: Android移植2012-09-05 14:16 16173人阅读 评论(12) 收藏 举报 androidgitgooglejdkub ...

  2. 深入浅出 - Android系统移植与平台开发(十)- Android编译系统与定制Android平台系统(瘋耔修改篇二)

    第四章.Android编译系统与定制Android平台系统 4.1Android编译系统 Android的源码由几十万个文件构成,这些文件之间有的相互依赖,有的又相互独立,它们按功能或类型又被放到不同 ...

  3. 深入浅出 - Android系统移植与平台开发(十二)- Android JNI机制

    第五章.JNI机制 4.1 JNI概述 由前面基础知识可知,Android的应用层由Java语言编写,Framework框架层则是由Java代码与C/C++语言实现,之所以由两种不同的语言组合开发框架 ...

  4. 深入浅出 - Android系统移植与平台开发(三)- 编译并运行Android4.0模拟器

    作者:唐老师,华清远见嵌入式学院讲师. 1.   编译Android模拟器 在Ubuntu下,我们可以在源码里编译出自己的模拟器及SDK等编译工具,当然这个和在windows里下载的看起来没有什么区别 ...

  5. 深入浅出 - Android系统移植与平台开发(五)- 编译Android源码(转)

    2.3编译Android源码 Android源码体积非常庞大,由Dalvik虚拟机.Linux内核.编译系统.框架代码.Android定制C库.测试套件.系统应用程序等部分组成,在编译Android源 ...

  6. 深入浅出 - Android系统移植与平台开发(十一)- Android系统的定制(瘋耔修改篇一)

    首先非常感谢原文作者为我们提供的知识库,因为有你们的贡献,我们的开发难度更显简单 原文 :   http://blog.csdn.net/mr_raptor/article/details/30113 ...

  7. 深入浅出 - Android系统移植与平台开发(二) - 准备Android开发环境

    作者:唐老师,华清远见嵌入式学院讲师. 编译Android源码 关于android系统的编译,Android的官方网站上也给出了详细的说明.http://source.android.com/sour ...

  8. 深入浅出 - Android系统移植与平台开发(六)- 为Android启动加速

    作者:唐老师,华清远见嵌入式学院讲师. Android的启动速度一直以来是他的诟病,虽然现在Android设备的硬件速度越来越快,但是随着新 版本的出现,其启动速度一直都比较慢,当然,作为程序员,我们 ...

  9. 深入浅出 - Android系统移植与平台开发(七)- 初识HAL

    作者:唐老师,华清远见嵌入式学院讲师. 1. HAL的module与stub HAL(Hardware AbstractLayer)硬件抽象层是Google开发的Android系统里上层应用对底层硬件 ...

随机推荐

  1. Oracle 备份与恢复介绍

    一.Oracle备份方式分类:Oracle有两类备份方式:(1)物理备份:是将实际组成数据库的操作系统文件从一处拷贝到另一处的备份过程,通常是从磁盘到磁带.物理备份又分为冷备份.热备份:   (2)逻 ...

  2. How to use the Visual Studio

    推荐一个提供VS配色方案的一个网站:StudioStyles,域名和网站同名:http://studiostyl.es/ 2. 整行剪切:Ctrl + X.光标不要选中任何文字,然后按这个快捷键就可以 ...

  3. loj 1036(dp)

    题目链接:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=25913 思路:易证存在一条从左上角到右下角的折线,沿着格子边缘的. ...

  4. cocos2dx游戏开发——微信打飞机学习笔记(三)——WelcomeScene的搭建

    一.场景与层的关系: cocos2dx的框架可以说主要由导演,场景,层,精灵来构成: 1.其中导演,意如其名,就是操控整个游戏的一个单例,管理着整个游戏. 2.场景就像电影的一幕剧情,所以说,懂得如何 ...

  5. felx项目属性(二)

    order flex-grow flex-shrink flex-basis flex align-self 1.1 order css order属性规定了弹性容器中的可伸缩项目在布局时的顺序.元素 ...

  6. How Kafka’s Storage Internals Work

    In this post I'm going to help you understand how Kafka stores its data. I've found understanding th ...

  7. css精灵动画

    精灵动画的实现 CSS Sprites在国内很多人叫CSS精灵,其实这个技术不新鲜,原理就是:靠不断的切换图片让人感觉视觉上不断在变化,例如gif动画之类的效果 那么前端如何实现精灵效果? 传统的就是 ...

  8. MFC 打开文件对话框 打开单个文件

    CFileDialog的语法: CFileDialog(BOOL bOpenFileDialog,LPCTSTR lpszDefExt=NULL,LPCTSTR lpszFileName=NULL,D ...

  9. HTML5 postMessage 和 onmessage API 详细应用

    随着 HTML5 的发展,了解并熟悉 HTML5 的 API 接口是非常重要的.postMessage(send) 和 onmessage 此组 API 在 HTML5 中有着广泛的应用,比如 Web ...

  10. JavaScript定时器

    定时器 开启定时器 Setinterval间隔型    每隔一段时间重复的执行 SetTimeout延时型   只执行一次 两种定时器的区别 <!DOCTYPE html> <htm ...