Android中JNI编程的那些事儿

首先说明,Android系统不允许一个纯粹使用C/C++的程序出现,它要求必须是通过Java代码嵌入Native C/C++——即通过JNI的方式来使用本地(Native)代码。因此JNI对Android底层开发人员非常重要。

如何将.so文件打包到.APK

让我们 先 从最简单的情况开始,假如已有一个JNI实现——libxxx.so文件,那么如何在APK中使用它呢?

在我最初写类似程序的时候,我会将libxxx.so文件push到/system/lib/目录下,然后在Java代码中执行 System.loadLibrary(xxx),这是个可行的做法,但需要取得/system/lib 目录 的写权限(模拟器通过adb remount取得该权限)。但模拟器 重启之 后libxxx.so文件会消失。现在 我找到了更好的方法,把.so文件打包到apk中分发给最终用 户,不管是模拟器 或者 真机 ,都不再需要system分区的写权限。实现步骤如下:

1、在你的项目根目录下建立libs/armeabi目录;

2、将libxxx.so文件copy到 libs/armeabi/下;

3、此时ADT插件自动编译输出的.apk文件中已经包括.so文件了;

4、安装APK文件,即可直接使用JNI中的方法;

我想还需要简单说明一下libxxx.so的命名规则,沿袭Linux传统,lib<something>.so是类库文件名称的格 式,但在Java的System.loadLibrary(“ something ”)方法中指定库名称时,不能包括 前缀—— lib,以及后缀—— .so。

准备编写自己的JNI模块

你一定想知道如何编写自己的xxx.so,不过这涉及了太多有关JNI的知识。简单的说:JNI是Java平台定义的用于和宿主平台上的本地代码进 行交互的“Java标准”,它通常有两个使用场景:1.使用(之前使用c/c++、delphi开发的)遗留代码;2.为了更好、更直接地与硬件交互 并 获得更高性能 。你可以通过以下链接了解JNI的更多资料:

JNI之Hello World

1、首先创建含有native方法的Java类:

  1. package com.okwap.testjni;
  2. public final class MyJNI {
  3. //native方法,
  4. public static native String sayHello(String name);
  5. }
  1. package com.okwap.testjni;
  2. public final class MyJNI {
  3. //native方法,
  4. public static native String sayHello(String name);
  5. }

2、通过javah命令生成.h文件,内容如下(com_okwap_testjni.h文件):

  1. /* DO NOT EDIT THIS FILE - it is machine generated */
  2. #include <JNI.H>
  3. /* Header for class com_okwap_testjni_MyJNI */
  4. #ifndef _Included_com_okwap_testjni_MyJNI
  5. #define _Included_com_okwap_testjni_MyJNI
  6. #ifdef __cplusplus
  7. extern "C" {
  8. #endif
  9. /*
  10. * Class:     com_okwap_testjni_MyJNI
  11. * Method:    sayHello
  12. * Signature: (Ljava/lang/String;)Ljava/lang/String;
  13. */
  14. JNIEXPORT jstring JNICALL Java_com_okwap_testjni_MyJNI_sayHello
  15. (JNIEnv *, jclass, jstring);
  16. #ifdef __cplusplus
  17. }
  18. #endif
  19. #endif
  1. /* DO NOT EDIT THIS FILE - it is machine generated */
  2. #include <jni.h>
  3. /* Header for class com_okwap_testjni_MyJNI */
  4. #ifndef _Included_com_okwap_testjni_MyJNI
  5. #define _Included_com_okwap_testjni_MyJNI
  6. #ifdef __cplusplus
  7. extern "C" {
  8. #endif
  9. /*
  10. * Class:     com_okwap_testjni_MyJNI
  11. * Method:    sayHello
  12. * Signature: (Ljava/lang/String;)Ljava/lang/String;
  13. */
  14. JNIEXPORT jstring JNICALL Java_com_okwap_testjni_MyJNI_sayHello
  15. (JNIEnv *, jclass, jstring);
  16. #ifdef __cplusplus
  17. }
  18. #endif
  19. #endif
  20. </jni.h>

这是一个标准的C语言头文件,其中的JNIEXPORT、JNICALL是JNI关键字(事实上它是没有任何内容的宏,仅用于指示性说明),而 jint、jstring是JNI环境下对int及java.lang.String类型的映射。这些关键字的定义都可以在jni.h中看到。
3、在 com_okwap_testjni.c文件中实现以上方法:

  1. #include <STRING.H>
  2. #include <JNI.H>
  3. #include "com_okwap_testjni.h"
  4. JNIEXPORT jstring JNICALL Java_com_okwap_testjni_MyJNI_sayHello(JNIEnv* env, jclass, jstring str){
  5. //从jstring类型取得c语言环境下的char*类型
  6. const char* name = (*env)->GetStringUTFChars(env, str, 0);
  7. //本地常量字符串
  8. char* hello = "你好,";
  9. //动态分配目标字符串空间
  10. char* result = malloc((strlen(name) + strlen(hello) + 1)*sizeof(char));
  11. memset(result,0,sizeof(result));
  12. //字符串链接
  13. strcat(result,hello);
  14. strcat(result,name);
  15. //释放jni分配的内存
  16. (*env)->ReleaseStringUTFChars(env,str,name);
  17. //生成返回值对象
  18. str = (*env)->NewStringUTF(env, "你好 JNI~!");
  19. //释放动态分配的内存
  20. free(result);
  21. //
  22. return str;
  23. }
  1. #include <string.h>
  2. #include <jni.h>
  3. #include "com_okwap_testjni.h"
  4. JNIEXPORT jstring JNICALL Java_com_okwap_testjni_MyJNI_sayHello(JNIEnv* env, jclass, jstring str){
  5. //从jstring类型取得c语言环境下的char*类型
  6. const char* name = (*env)->GetStringUTFChars(env, str, 0);
  7. //本地常量字符串
  8. char* hello = "你好,";
  9. //动态分配目标字符串空间
  10. char* result = malloc((strlen(name) + strlen(hello) + 1)*sizeof(char));
  11. memset(result,0,sizeof(result));
  12. //字符串链接
  13. strcat(result,hello);
  14. strcat(result,name);
  15. //释放jni分配的内存
  16. (*env)->ReleaseStringUTFChars(env,str,name);
  17. //生成返回值对象
  18. str = (*env)->NewStringUTF(env, "你好 JNI~!");
  19. //释放动态分配的内存
  20. free(result);
  21. //
  22. return str;
  23. }
  24. </jni.h></string.h>

4、编译——两种不同的编译环境
以上的C语言代码要编译成最终.so动态库文件,有两种途径:
Android NDK :全称是Native Developer
Kit,是用于编译本地JNI源码的工具,为开发人员将本地方法整合到Android应用中提供了方便。事实上NDK和完整源码编译环境一样,都使用
Android的编译系统——即通过Android.mk文件控制编译。NDK可以运行在Linux、Mac、Window(+cygwin)三个平台
上。有关NDK的使用方法及更多细节请参考以下资料:
eoe特刊第七期《NDK总结》http://blog.eoemobile.com/?p=27
http://androidappdocs.appspot.com/sdk/ndk/index.html ;
完整源码编译环境
:Android平台提供有基于make的编译系统,为App编写正确的Android.mk文件就可使用该编译系统。该环境需要通过git从官方网站获
取完整源码副本并成功编译,更多细节请参考:http://source.android.com/index.html
不管你选择以上两种方法的哪一个,都必须编写自己的Android.mk文件,有关该文件的编写请参考相关文档。
JNI组件的入口函数——JNI_OnLoad()、JNI_OnUnload()
JNI组件被成功加载和卸载时,会进行函数回调,当VM执行到System.loadLibrary(xxx)函数时,首先会去执行JNI组件中的JNI_OnLoad()函数,而当VM释放该组件时会呼叫JNI_OnUnload()函数。先看示例代码:

  1. //onLoad方法,在System.loadLibrary()执行时被调用
  2. jint JNI_OnLoad(JavaVM* vm, void* reserved){
  3. LOGI("JNI_OnLoad startup~~!");
  4. return JNI_VERSION_1_4;
  5. }
  6. //onUnLoad方法,在JNI组件被释放时调用
  7. void JNI_OnUnload(JavaVM* vm, void* reserved){
  8. LOGE("call JNI_OnUnload ~~!!");
  9. }
  1. //onLoad方法,在System.loadLibrary()执行时被调用
  2. jint JNI_OnLoad(JavaVM* vm, void* reserved){
  3. LOGI("JNI_OnLoad startup~~!");
  4. return JNI_VERSION_1_4;
  5. }
  6. //onUnLoad方法,在JNI组件被释放时调用
  7. void JNI_OnUnload(JavaVM* vm, void* reserved){
  8. LOGE("call JNI_OnUnload ~~!!");
  9. }

JNI_OnLoad()有两个重要的作用
指定JNI版本:告诉VM该组件使用那一个JNI版本(若未提供JNI_OnLoad()函数,VM会默认该使用最老的JNI
1.1版),如果要使用新版本的JNI,例如JNI
1.4版,则必须由JNI_OnLoad()函数返回常量JNI_VERSION_1_4(该常量定义在jni.h中) 来告知VM。
初始化设定,当VM执行到System.loadLibrary()函数时,会立即先呼叫JNI_OnLoad()方法,因此在该方法中进行各种资源的初始化操作最为恰当。
JNI_OnUnload()的作用与JNI_OnLoad()对应,当VM释放JNI组件时会呼叫它,因此在该方法中进行善后清理,资源释放的动作最为合适。
使用registerNativeMethods方法
对Java程序员来说,可能我们总是会遵循:1.编写带有native方法的Java类;—>2.使用javah命令生成.h头文件;—
>3.编写代码实现头文件中的方法,这样的“官方”
流程,但也许有人无法忍受那“丑陋”的方法名称,RegisterNatives方法能帮助你把c/c++中的方法隐射到Java中的native方法,
而无需遵循特定的方法命名格式。来看一段示例代码吧:

  1. //定义目标类名称
  2. static const char *className = "com/okwap/testjni/MyJNI";
  3. //定义方法隐射关系
  4. static JNINativeMethod methods[] = {
  5. {"sayHello", "(Ljava/lang/String;)Ljava/lang/String;", (void*)sayHello},
  6. };
  7. jint JNI_OnLoad(JavaVM* vm, void* reserved){
  8. //声明变量
  9. jint result = JNI_ERR;
  10. JNIEnv* env = NULL;
  11. jclass clazz;
  12. int methodsLenght;
  13. //获取JNI环境对象
  14. if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
  15. LOGE("ERROR: GetEnv failed\n");
  16. return JNI_ERR;
  17. }
  18. assert(env != NULL);
  19. //注册本地方法.Load 目标类
  20. clazz = (*env)->FindClass(env,className);
  21. if (clazz == NULL) {
  22. LOGE("Native registration unable to find class '%s'", className);
  23. return JNI_ERR;
  24. }
  25. //建立方法隐射关系
  26. //取得方法长度
  27. methodsLenght = sizeof(methods) / sizeof(methods[0]);
  28. if ((*env)->RegisterNatives(env,clazz, methods, methodsLenght) < 0) {
  29. LOGE("RegisterNatives failed for '%s'", className);
  30. return JNI_ERR;
  31. }
  32. //
  33. result = JNI_VERSION_1_4;
  34. return result;
  35. }
  1. //定义目标类名称
  2. static const char *className = "com/okwap/testjni/MyJNI";
  3. //定义方法隐射关系
  4. static JNINativeMethod methods[] = {
  5. {"sayHello", "(Ljava/lang/String;)Ljava/lang/String;", (void*)sayHello},
  6. };
  7. jint JNI_OnLoad(JavaVM* vm, void* reserved){
  8. //声明变量
  9. jint result = JNI_ERR;
  10. JNIEnv* env = NULL;
  11. jclass clazz;
  12. int methodsLenght;
  13. //获取JNI环境对象
  14. if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
  15. LOGE("ERROR: GetEnv failed\n");
  16. return JNI_ERR;
  17. }
  18. assert(env != NULL);
  19. //注册本地方法.Load 目标类
  20. clazz = (*env)->FindClass(env,className);
  21. if (clazz == NULL) {
  22. LOGE("Native registration unable to find class '%s'", className);
  23. return JNI_ERR;
  24. }
  25. //建立方法隐射关系
  26. //取得方法长度
  27. methodsLenght = sizeof(methods) / sizeof(methods[0]);
  28. if ((*env)->RegisterNatives(env,clazz, methods, methodsLenght) < 0) {
  29. LOGE("RegisterNatives failed for '%s'", className);
  30. return JNI_ERR;
  31. }
  32. //
  33. result = JNI_VERSION_1_4;
  34. return result;
  35. }

建立c/c++方法和Java方法之间映射关系的关键是 JNINativeMethod 结构,该结构定义在jni.h中,具体定义如下:

  1. typedef struct {
  2. const char* name;//java方法名称
  3. const char* signature; //java方法签名
  4. void*       fnPtr;//c/c++的函数指针
  5. } JNINativeMethod;
  1. typedef struct {
  2. const char* name;//java方法名称
  3. const char* signature; //java方法签名
  4. void*       fnPtr;//c/c++的函数指针
  5. } JNINativeMethod;

参照上文示例中初始化该结构的代码:

  1. //定义方法隐射关系
  2. static JNINativeMethod methods[] = {
  3. {"sayHello", "(Ljava/lang/String;)Ljava/lang/String;", (void*)sayHello},
  4. };
  1. //定义方法隐射关系
  2. static JNINativeMethod methods[] = {
  3. {"sayHello", "(Ljava/lang/String;)Ljava/lang/String;", (void*)sayHello},
  4. };

其中比较难以理解的是第二个参数——signature字段的取值,实际上这些字符与函数的参数类型/返回类型一一对应,其中"()"
中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void func(),"(II)V" 表示 void func(int,
int),具体的每一个字符的对应关系如下:
字符 Java类型 C/C++类型
V void void
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short
数组则以"["开始,用两个字符表示:
字符 java类型 c/c++类型
[Z jbooleanArray boolean[]
[I jintArray int[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
[D jdoubleArray double[]
[J jlongArray long[]
上面的都是基本类型,如果参数是Java类,则以"L"开头,以";"结尾,中间是用"/"隔开包及类名,而其对应的C函数的参数则为jobject,一
个例外是String类,它对应C类型jstring,例如:Ljava/lang /String; 、Ljava/net/Socket;
等,如果JAVA函数位于一个嵌入类(也被称为内部类),则用$作为类名间的分隔符,例如:"Landroid/os
/FileUtils$FileStatus;"。
使用registerNativeMethods方法不仅仅是为了改变那丑陋的长方法名,最重要的是可以提高效率,因为当Java类别透过VM呼叫到本地
函数时,通常是依靠VM去动态寻找.so中的本地函数(因此它们才需要特定规则的命名格式),如果某方法需要连续呼叫很多次,则每次都要寻找一遍,所以使
用RegisterNatives将本地函数向VM进行登记,可以让其更有效率的找到函数。
registerNativeMethods方法的另一个重要用途是,运行时动态调整本地函数与Java函数值之间的映射关系,只需要多次调用registerNativeMethods()方法,并传入不同的映射表参数即可。
JNI中的日志输出
你一定非常熟悉在Java代码中使用Log.x(TAG,“message”)系列方法,在c/c++代码中也一样,不过首先你要include相关头文件。遗憾的是你使用不同的编译环境( 请参考上文中两种编译环境的介绍) ,对应的头文件略有不同。。
如果是在完整源码编译环境下,只要include 头文件,就可以使用对应的LOGI、LOGD等方法了,同时请定义LOG_TAG,LOG_NDEBUG等宏值,示例代码如下:

  1. #define LOG_TAG "HelloJni"
  2. #define LOG_NDEBUG 0
  3. #define LOG_NIDEBUG 0
  4. #define LOG_NDDEBUG 0
  5. #include <STRING.H>
  6. #include <JNI.H>
  7. #include <UTILS Log.h>
  8. jstring Java_com_inc_android_ime_HelloJni_stringFromJNI(JNIEnv* env,jobject thiz){
  9. LOGI("Call stringFromJNI!\n");
  10. return (*env)->NewStringUTF(env, "Hello from JNI (中文)!");
  11. }
  1. #define LOG_TAG "HelloJni"
  2. #define LOG_NDEBUG 0
  3. #define LOG_NIDEBUG 0
  4. #define LOG_NDDEBUG 0
  5. #include <string.h>
  6. #include <jni.h>
  7. #include <utils log="">
  8. jstring Java_com_inc_android_ime_HelloJni_stringFromJNI(JNIEnv* env,jobject thiz){
  9. LOGI("Call stringFromJNI!\n");
  10. return (*env)->NewStringUTF(env, "Hello from JNI (中文)!");
  11. }
  12. </utils></jni.h></string.h>

与日志相关的.h头文件,在以下源码路径:
myeclair\frameworks\base\include\utils\Log.h
myeclair\system\core\include\cutils\log.h
如果你是在NDK环境下编译,则需要#include ,示例代码如下:

  1. #define LOG_TAG "HelloJni"
  2. #include <STRING.H>
  3. #include <JNI.H>
  4. #include <UTILS Log.h>
  5. jstring Java_com_inc_android_ime_HelloJni_stringFromJNI(JNIEnv* env,jobject thiz){
  6. __android_log_print(ANDROID_LOG_INFO,LOG_TAG,"Call stringFromJNI!\n");
  7. return (*env)->NewStringUTF(env, "Hello from JNI (中文)!");
  8. }
  1. #define LOG_TAG "HelloJni"
  2. #include <string.h>
  3. #include <jni.h>
  4. #include <utils log="">
  5. jstring Java_com_inc_android_ime_HelloJni_stringFromJNI(JNIEnv* env,jobject thiz){
  6. __android_log_print(ANDROID_LOG_INFO,LOG_TAG,"Call stringFromJNI!\n");
  7. return (*env)->NewStringUTF(env, "Hello from JNI (中文)!");
  8. }
  9. </utils></jni.h></string.h>

很可惜,其中用于日志输出的方法是: __android_log_print(....) , 并不是我们熟悉的LOG.x(...)系列方法。不过好的一点是android/log.h文件在完整源码环境下也是可用的,因此,可以用一下的头文件来统两种环境下的差异:

  1. /*
  2. * jnilogger.h
  3. *
  4. *  Created on: 2010-11-15
  5. *      Author: INC062805
  6. */
  7. #ifndef __JNILOGGER_H_
  8. #define __JNILOGGER_H_
  9. #include <ANDROID log.h>
  10. #ifdef _cplusplus
  11. extern "C" {
  12. #endif
  13. #ifndef LOG_TAG
  14. #define LOG_TAG    "MY_LOG_TAG"
  15. #endif
  16. #define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
  17. #define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
  18. #define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
  19. #define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
  20. #define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG_TAG,__VA_ARGS__)
  21. #ifdef __cplusplus
  22. }
  23. #endif
  24. #endif /* __JNILOGGER_H_ */
  1. /*
  2. * jnilogger.h
  3. *
  4. *  Created on: 2010-11-15
  5. *      Author: INC062805
  6. */
  7. #ifndef __JNILOGGER_H_
  8. #define __JNILOGGER_H_
  9. #include <android log="">
  10. #ifdef _cplusplus
  11. extern "C" {
  12. #endif
  13. #ifndef LOG_TAG
  14. #define LOG_TAG    "MY_LOG_TAG"
  15. #endif
  16. #define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
  17. #define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
  18. #define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
  19. #define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
  20. #define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG_TAG,__VA_ARGS__)
  21. #ifdef __cplusplus
  22. }
  23. #endif
  24. #endif /* __JNILOGGER_H_ */
  25. </android>

你可以下载以上头文件,来统一两种不同环境下的使用差异。另外,不要忘了在你的Android.mk文件中加入对类库的应用,两种环境下分别是:

  1. ifeq ($(HOST_OS),windows)
  2. #NDK环境下
  3. LOCAL_LDLIBS := -llog
  4. else
  5. #完整源码环境下
  6. LOCAL_SHARED_LIBRARIES := libutils
  7. endif
  1. ifeq ($(HOST_OS),windows)
  2. #NDK环境下
  3. LOCAL_LDLIBS := -llog
  4. else
  5. #完整源码环境下
  6. LOCAL_SHARED_LIBRARIES := libutils
  7. endif

Android为JNI提供的助手方法
myeclair\dalvik\libnativehelper\include\nativehelper
在完整源码编译环境下,Android在myeclair\dalvik\libnativehelper\include\nativehelper
\JNIHelp.h头文件中 提供了助手函数 ,用于本地方法注册、异常处理等任务,还有一个用于计算方法隐射表长度的宏定义:

  1. #ifndef NELEM
  2. # define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
  3. #endif
  4. //有了以上宏定义后,注册方法可以按如下写,该宏定义可以直接copy到NDK环境下使用:
  5. (*env)->RegisterNatives(env,clazz, methods,NELEM(methods));
    1. #ifndef NELEM
    2. # define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
    3. #endif
    4. //有了以上宏定义后,注册方法可以按如下写,该宏定义可以直接copy到NDK环境下使用:
    5. (*env)->RegisterNatives(env,clazz, methods,NELEM(methods));

JNI-入门之二的更多相关文章

  1. 二、Android NDK编程预备之Java jni入门Hello World

    转自:  http://www.eoeandroid.com/forum.php?mod=viewthread&tid=264543&fromuid=588695 昨天已经简要介绍了J ...

  2. 三、Android NDK编程预备之Java jni入门创建C/C++共享库

    转自: http://www.eoeandroid.com/thread-264971-1-1.html 应网友回复,答应在两天前要出一篇创建C/C++共享库的,但由于清明节假期,跟朋友出去游玩,丢手 ...

  3. 无废话ExtJs 入门教程二十一[继承:Extend]

    无废话ExtJs 入门教程二十一[继承:Extend] extjs技术交流,欢迎加群(201926085) 在开发中,我们在使用视图组件时,经常要设置宽度,高度,标题等属性.而这些属性可以通过“继承” ...

  4. 无废话ExtJs 入门教程二十[数据交互:AJAX]

    无废话ExtJs 入门教程二十[数据交互:AJAX] extjs技术交流,欢迎加群(521711109) 1.代码如下: 1 <!DOCTYPE html PUBLIC "-//W3C ...

  5. Bootstrap入门(二)栅格

    Bootstrap入门(二)栅格 Bootstrap入门(二)栅格 全局CSS样式--栅格 先引入本地的CSS文件(根据自己的文件夹,有不同的引入地址,我是放在一个新建的名为css的文件夹中) con ...

  6. Android工程师入门(二)——不忙不累怎么睡。。

    安卓开发迫在眉睫,这周入个门吧! Android工程师入门(二) 四.在界面中显示图片 ImageView 是显示图片的一个控件. --属性 src——内容图片: background——背景图片/背 ...

  7. Java入门(二)——果然断更的都是要受惩罚的。。。

    断更了一个多月,阅读量立马从100+跌落至10-,虽说不是很看重这个,毕竟只是当这个是自己的学习笔记,但有人看,有人评论,有人认同和批评的感觉还是很巴适的,尤其以前有过却又被剥夺的,惨兮兮的. 好好写 ...

  8. 无废话ExtJs 入门教程二[Hello World]

    无废话ExtJs 入门教程二[Hello World] extjs技术交流,欢迎加群(201926085) 我们在学校里学习任何一门语言都是从"Hello World"开始,这里我 ...

  9. Maven入门系列(二)--设置中央仓库的方法

    原文地址:http://www.codeweblog.com/maven入门系列-二-设置中央仓库的方法/ Maven仓库放在我的文档里好吗?当然不好,重装一次电脑,意味着一切jar都要重新下载和发布 ...

  10. Java NIO入门(二):缓冲区内部细节

    Java NIO 入门(二)缓冲区内部细节 概述 本文将介绍 NIO 中两个重要的缓冲区组件:状态变量和访问方法 (accessor). 状态变量是前一文中提到的"内部统计机制"的 ...

随机推荐

  1. 剑指Offer37 二叉树深度与平衡二叉树判断

    /************************************************************************* > File Name: 37_TreeDe ...

  2. IOS iphone 4inch上应用没有全屏,上下有黑边(转)

    在编写IOS应用程序的过程中,我一直都是使用iPhone Retina(3.5-inch)模拟器测试的,一切显示正常,切图如下: 我在应用开发中,采用的是纯代码实现.公司提供了一部iPhone4s,我 ...

  3. 1166 矩阵取数游戏[区间dp+高精度]

    1166 矩阵取数游戏 2007年NOIP全国联赛提高组  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解       题目描述 Description [ ...

  4. TransmitFile下载文件(部分转载)

    例子代码: public void Down() { TransmitFile(@"/File/KBPub.zip"); } public void TransmitFile(st ...

  5. ASP.NET MVC 4 SimpleMembership Provider (1)

    新的ASP.NET MVC 4.0 提供了一个新的Membership Provider,叫SimpleMembership. 首先,我们建立一个新的solution 首先我们先看一下web.conf ...

  6. Part 18 Indexes in sql server

    Indexes in sql server Clustered and nonclustered indexes in sql server Unique and Non Unique Indexes ...

  7. c++错误修复 数据库无法打开 无法右击 run outtiime

    先前有安装vs2015失败的前提 现象:1.无法右击  显示 explorer.exe   c++        run outtiime  这些字样 2. 安装有数据库的软件都不能用.显示数据库连接 ...

  8. ASP.NET 窗体间传值实现方法详解

    假设ParentForm.aspx 页面上有TextBox1文本框和Open按钮点击Open按钮弹出SubForm.aspx,SubForm.aspx页面上有TextBox1文本框和Close按钮点击 ...

  9. js 用window.open(参数) 打开新窗口,在新窗口怎么获取传过来的参数

    unction openwin(taskno){window.open ('playIt.jsp?taskno='+taskno,'play','height=100,width=400,toolba ...

  10. git merge 到 非当前 branch

    1. Add a remote alias for your local repository, ex: git remote add self file:///path/to/your/reposi ...