android NDK 的进阶实验

本文将为各位详细介绍Android NDK的安装、使用和实战。我下载的是Android Native Developer Kit (NDK) R4版本,当前的最新版。

强烈建议在看这篇博客的时候先看上一篇博客,,,,以对NDK的基础规则和架构有基本的认识,,,这篇文章将不再对这些内容赘述


Android NDK

我下面讲的都是以linux环境为准,因为我的系统是linux的。windows下可以弄个Cygwin,模拟linux环境。
首先进入NDK目录,有个README文件,里面提到了API的文档在docs/STABLE-APIS.TXT里面,如何安装NDK,参考docs/INSTALL.TXT,还有如何使用NDK,参考docs/HOWTO.TXT。建议这些文档都看一遍,有个大概了解。
安装:
INSTALL.TXT里面讲的是如何安装,安装NDK,就需要一个可以make环境,linux自带了,所以不用关心。还有一点,以前的版本都需要运行build/host-setup.sh来进行初始化,这个版本把它删除了,这样更方便。
然后就是配置环境变量。
在~/.bashrc文件里面,添加Shell代码
NDK_ROOT=~/android-ndk-r4b #后面的路径是NDK所在的目录,根据自己的目录修改  export NDK_ROOT
然后保存,重新打开bash。
使用:
先拿sample试刀吧,刚开始什么都不知道,只有运行出一个例子,才能增加信心。
编译的两种方法:
1.进入要目标工程目录,比如$NDK_ROOT/samples/hello-jni,然后执行$NDK_ROOT/ndk-build
2.在任何地方,执行$NDK_ROOT/ndk-build -C $NDK_ROOT/samples/hello-jni.
如果成功的话,会生成obj和libs两个目录。
选择一种方法,编译这个例子。然后打开eclipse,把hello-jni这个工程导入,运行,ok,就能看到效果了。
进阶:
docs下的STABLE-APIS.TXT里面讲了系统API的用法。我以1.5为例。进入$NDK_ROOT/build/platforms/android-3/arch-arm/usr/include,里面有很多.h文件,这些都是可以在NDK里面调用的,除了linux和asm目录下的。
一般来说,主要用到的是jni.h,里面提供了很多对类和对象的操作。
另外,1.5提供了log的API,在android/log.h里面,使用的时候,在c文件中#include <android/log.h>,然后在Android.mk里面加上LOCAL_LDLIBS := -llog,就可以了。
1.6到2.01提供了openGL ES 1.x的API,2.1提供了openGL ES 2.0的API,2.2提供了graphics的处理接口。使用方法同log。
实例:
给出两个点的坐标,求它们的距离。
首先,创建一个Point对象,
Java代码

  • public class Point {   
  •     float x;   
        float y;   
  • }   
       

然后在c文件中定义一个函数  
C代码
  • jfloat Java_chroya_demo_ndk_Main_distance(JNIEnv* env, jobject thiz, jobject a,jobject b){}   
返回值是float,在jni中定义的是jfloat。
函数名规则: Java开头,接着是包名的每一段,然后是类名,最后是Java中调用的方法名,中间都用下划线隔开。第一个参数JNIEnv* env和第二个参数jobject thiz都是必须的,后面的才是Java中传递进来的参数。这里是两个Point对象。
首先确定要做的步骤:
◆找到这个Point类
◆找到类中的域x和y的域id
◆根据ID取出x和y的值
◆计算结果并返回
那么代码如下:
Java代码

  1. #include <jni.h>
  2. #include <math.h>
  3. #include <android/log.h>
  4. Java_com_example_hellojni_HelloJni_InitGraphics( JNIEnv* env,jobject thiz ,jobject job )//这个就是函数所在的对象
  5. {
  6. //步骤1
  7. jclass point_class = (*env)->FindClass(env, "chroya/demo/ndk/Point");
  8. if(point_class == NULL) {
  9. //printf("class not found");
  10. __android_log_write(ANDROID_LOG_INFO, "MyNdkDemo", "class Point not found");
  11. return 0;
  12. } else {
  13. __android_log_write(ANDROID_LOG_INFO, "MyNdkDemo", "found class Point");
  14. }
  15. //步骤2
  16. jfieldID field_x = (*env)->GetFieldID(env, point_class, "x", "F");
  17. jfieldID field_y = (*env)->GetFieldID(env, point_class, "y", "F");
  18. //步骤3
  19. jfloat ax = (*env)->GetFloatField(env, a, field_x);
  20. jfloat ay = (*env)->GetFloatField(env, a, field_y);
  21. jfloat bx = (*env)->GetFloatField(env, b, field_x);
  22. jfloat by = (*env)->GetFloatField(env, b, field_y);
  23. //步骤4
  24. return sqrtf(powf(bx-ax, 2) + powf(by-ay, 2));
  25. }
复制代码


然后在Java里面调用:
Java代码

  1. public class Main extends Activity {

  2. @Override
  3. public void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. TextView tv = new TextView(getApplicationContext());
  6. Point a = new Point();
  7. a.x = 3;
  8. a.y = 3;

  9. Point b = new Point();
  10. b.x = 5;
  11. b.y = 5;

  12. float d = distance(a,b);
  13. tv.setText("distance(a,b):"+d);
  14. setContentView(tv);
  15. }

  16. public native float distance(Point a, Point b);

  17. static {
  18. System.loadLibrary("demo");
  19. }
  20. }
复制代码

运行,得到结果distance(a,b):2.828427

这是源码http://download.csdn.net/detail/ccccdddxxx/3858863

¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥

先面是这个重要的文件的框架

├── jni
│   ├── Android.mk
│   └── demo.c
── src
    └── chroya
        └── demo
            └── ndk
                ├── Main.java
                └── Point.java

上一篇已经对NDK编程的基本框架有讲解:以下对文件解读:

getApplicationContext()

public class MyActivity extends Activity {
    public void method() {
       mContext = this;    // since Activity extends Context
       mContext = getApplicationContext();
       mContext = getBaseContext();
    }
 }

this 是因为Activity 扩展了Context,其生命周期是Activity 创建到销毁;

getApplicationContext 取得的Context是和Application关联的生命周期是应用的创建到销.

搞清楚了生命周期就不会在使用过程中犯错误.
比如有一个全局的数据操作类,用到了context, 这个时候就要用到getApplicationContext , 而不是用ACtivity, 这就保证了,数据库的操作与activity无关.

那么context是什么??????????????????????????????

Context字面意思是上下文,位于framework package的android.content.Context中,其实该类为LONG型,类似Win32中的Handle句柄。很多方法需要通过 Context才能识别调用者的实例:比如说Toast的第一个参数就是Context,一般在Activity中我们直接用this代替,代表调用者的实例为Activity,而到了一个button的onClick(View view)等方法时,我们用this时就会报错,所以我们可能使用ActivityName.this来解决,主要原因是因为实现Context的类主要有Android特有的几个模型,Activity以及Service。

Context提供了关于应用环境全局信息的接口。它是一个抽象类,它的执行被Android系统所提供。它允许获取以应用为特征的资源和类型。同时启动应用级的操作,如启动Activity,broadcasting和接收intents。

&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&7

在android中context可以作很多操作,但是最主要的功能加载和访问资源。在android中有两种context,一种是 application context,一种是activity context,通常我们在各种类和方法间传递的是activity context。
比如一个activity的onCreate:
protected void onCreate(Bundle state) {
        super.onCreate(state);

        TextView label = new TextView(this); //传递context给view control
        label.setText("Leaks are bad");

        setContentView(label);
}
把activity context传递给view,意味着view拥有一个指向activity的引用,进而引用activity占有的资源:view hierachy, resource等。
这样如果context发生内存泄露的话,就会泄露很多内存。
这里泄露的意思是gc没有办法回收activity的内存。

Leaking an entire activity是很容易的一件事。

屏幕旋转的时候,系统会销毁当前的activity,保存状态信息,再创建一个新的。

比如我们写了一个应用程序,它需要加载一个很大的图片,我们不希望每次旋转屏 幕的时候都销毁这个图片,重新加载。实现这个要求的简单想法就是定义一个静态的Drawable,这样Activity 类创建销毁它始终保存在内存中。
实现类似:
public class myactivity extends Activity {
        private static Drawable sBackground;
        protected void onCreate(Bundle state) {
                super.onCreate(state);

                TextView label = new TextView(this);
                label.setText("Leaks are bad");

                if (sBackground == null) {
                        sBackground = getDrawable(R.drawable.large_bitmap);
                }
        label.setBackgroundDrawable(sBackground);//drawable attached to a view

        setContentView(label);
        }
}
这段程序看起来很简单,但是却问题很大。当屏幕旋转的时候会有leak(即gc没法销毁activity)。
我们刚才说过,屏幕旋转的时候系统会销毁当前的activity。但是当drawable和view关联后,drawable保存了view的 reference,即sBackground保存了label的引用,而label保存了activity的引用。既然drawable不能销毁,它所 引用和间接引用的都不能销毁,这样系统就没有办法销毁当前的activity,于是造成了内存泄露。gc对这种类型的内存泄露是无能为力的。

避免这种内存泄露的方法是避免activity中的任何对象的生命周期长过activity,避免由于对象对 activity的引用导致activity不能正常被销毁。我们可以使用application context。application context伴随application的一生,与activity的生命周期无关。application context可以通过Context.getApplicationContext或者Activity.getApplication方法获取

避免context相关的内存泄露,记住以下几点:
1. 不要让生命周期长的对象引用activity context,即保证引用activity的对象要与activity本身生命周期是一样的
2. 对于生命周期长的对象,可以使用application context
3. 避免非静态的内部类,尽量使用静态类,避免生命周期问题,注意内部类对外部对象引用导致的生命周期变化

&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

在android中context可以作很多操作,但是最主要的功能是加载和访问资源 。在android中有两种context,一种是 applicationcontext,一种是activity context,通常我们在各种类和方法间传递的是activity context。
 

Context是一个抽象对象,它的子类比较多,Activity就是它的子类,Service也是。

如果想要弹出一个AlertDialog,要写如下的代码

AlertDialog.Builder alert =new AlertDialog.Builder(this);


alert.setTitle("Warning");


alert.setMessage("Wrong time!");


alert.show(); 

这里构造方法的原型是AlertDialog.Builder(Context arg)需要一个Context类的对象作为参数,一般我们都在Activity里写,所以用this,表示在当前的会话中弹出AlertDialog。

在我的一个程序里,我自定义了一个接口Public interfaceCustomPickerListener,在实现这个接口的方法时我需要弹出来一个AlertDialog,这里,参数表里填写this的话会提示错误:The constructor AlertDialog.Builder(new CustomPickerListener(){})is undefined.


其实这个地方写this很明显是错误的,但是要写什么才能达成目的呢?


官方文档上对context的解释是Interface to global information about anapplication environment. This is an abstract class whoseimplementation is provided by the Android system. It allows accessto application-specific resources and classes, as well as up-callsfor application-level operations such as launching activities,broadcasting and receiving intents, etc.


于是我就试着写上了我程序的 包.目标类.this ,如下

AlertDialog.Builder alert =new AlertDialog.Builder(com.android.alcoholtest.AlcoholTest.this);

然后就成功了

Java_com_example_hellojni_HelloJni_InitGraphics( JNIEnv* env,jobject thiz ,jobject job )//这个就是函数所在的对象


因为jni函数有一个函数名规范。打开test-jni.c 会看的
Java_com_example_hellojni_HelloJni_stringFromJNI 这个命名是有详细规范的,Java_ 开头 下面是你在java哪个类里面要使用,比如我要在com\example\hellojni下面的testjni类中使用,那就要命名为Java_com_example_hellojni_testjni_stringFromJNI。
然后是参数JNIEnv* env,jobject thiz ,这两个是默认的参数,第一个是java虚拟机,必须有,第二个可以不用,相当于当前类的对象的指针,也就是this。
   里面内容的写法也有很多,无非是 一些标准C的处理了。这里要注意的是java那边的 数组类型在这边对应的都是一个类的指针,比如byte[],在c这边是JbyteArray,
要获取数组的值要用
int len =(*env)->GetArrayLength(env,msg);  
    jbyte* elems =(*env)-> GetByteArrayElements(env,msg, 0);  
(*env)->ReleaseByteArrayElements(env,msg, elems, 0);
三、重点:C调用java。
  先讲一下我理解的调用过程,c要先找到这个类,然后要有一个对象,然后在这个类的对象基础上调用这个函数。
因此调用java函数首先要有类,然后函数ID,然后对象
cls = (*env)->FindClass(env,"com/example/hellojni/Ext_Graphics");
JNIEnv *env本地空间的资源,jobject thiz是 java 空间资源

jclass point_class = (*env)->FindClass(env, "chroya/demo/ndk/Point");


如果是c程序,要用 (*env)->

如果是C++要用 env->

ps:在linux下如果.c文件中用 “env->” 编译会找不到此结构,必须用“(*env)->”,或者改成.cpp文件,以 c++的方式来编译。


以下是两者的区别:

jni.h中

struct JNINativeInterface_;

struct JNIEnv_;

#ifdef __cplusplus
typedef JNIEnv_ JNIEnv;
#else
typedef const struct JNINativeInterface_ *JNIEnv;
#endif

/*
* We use inlined functions for C++ so that programmers can write:
*
*   env->FindClass("java/lang/String")
*
* in C++ rather than:
*
*    (*env)->FindClass(env, "java/lang/String")
*
* in C.
*/

即C++中使用
env->FindClass("java/lang/String")


C中使用

(*env)->FindClass(env, "java/lang/String")

77&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
findClass():这个方法用来依据名称查找并加载Java类。 loadClass():这个方法用来依据名称加载Java类。 resolveClass():这个方法用来链接一个Java类。 这里比较 容易混淆的是findClass()方法和loadClass()方法的

findClass():这个方法用来依据名称查找并加载Java类。

loadClass():这个方法用来依据名称加载Java类。

resolveClass():这个方法用来链接一个Java类。

这里比较 容易混淆的是findClass()方法和loadClass()方法的作用。 前面提到过, 在Java类的链接进程中, 会需要对Java类停止解析, 而解析能够会导致以后Java类所援用的其它Java类被加载。 在这个时候, JVM就是通过调用以后类的定义类加载器的loadClass()方法来加载其它类的。 findClass()方规律是应用创立的类加载器的扩展点。 应用自己的类加载器应该覆写findClass()方法来添加自定义的类加载逻辑。 loadClass()方法的默许实现会负责调用findClass()方法。

前面提到, 类加载器的代理形式默许运用的是父类优先的战略。 这个战略的实现是封装在loadClass()方法中的。 假如希望修改此战略, 就需要覆写loadClass()方法。

上面的代码给出了自定义的类加载的常见实现形式:

public class MyClassLoader extends ClassLoader . . . {

protected Class findClass(String name) throws ClassNotFoundException . . . {

byte[] b = null; //查找或生成Java类的字节代码

return defineClass(name, b, 0, b. length);

}

}

本文到此结束,希望通过以上对java编程中类的加载、链接和初始化等三方面的介绍,能够给你带来一些帮助。

&&&&&&&&&&&&&&&&&&&&&&
以上面的代码为例:先调用JNIEnv的FindClass方法,该函数传入一个参数,该参数就是java类的全局带包名的名称,如上面示例中的test/Demo表示test包中的Demo类。这个方法会在你创建JVM时设置的classpath路径下找相应的类,找到后就会返回该类的class对象。 Class是JAVA中的一个类,每个JAVA类都有唯一的一个静态的Class对象,Class对象包含类的相关信息。为了使FindClass方法能找到你的类,请确保创建JVM时-Djava.class.path=参数设置正确。注意:系统环境变量中的CLASSPATH对这里创建JVM没有影响,所以不要以为系统CLASSPATH设置好了相关路径后这里就不用设置了。


     e、利用FindClass返回的class对象,调用GetMethodID函数可以获得里面方法的ID,在这里GetMethodID函数传入了三个参数:第一个参数是class对象,因为方法属于某个具体的类;第二个参数是方法的名称;第三个参数是方法的签名,这个签名可以在前面3.3中介绍的方法获得。

&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

GetFieldID是得到java类中的参数ID,GetMethodID得到java类中方法的ID,它们只能调用类中声明为 public的参数或方法。使用如下:

jfieldID topicFieldId = env -> GetFieldID (objectClass , "name" , "Ljava/lang/String;" ) ;
jmethodID getcName =env -> GetMethodID (objectClass , "getcatName" , "()Ljava/lang/String;" ) ;

第一参数是Java 类对象。第二个参数是参数(或方法名),第三个参数是该参数(或方法)的签名。第三个参数由以下方法得到。
有类

public class Cat {    
private int catNumber ;    
          String catName ;

public Cat ( int i, String name )
              {catNumber =i ;catName =name ; }    
public String getCatName ( )
              { return this. catName ; }
public void setCatName ( String catName )
              { this. catName =catName ; }
}

比如查看 Cat类 进入到Cat所在目录 先用javac Cat.java进行编译 然后输入命令:
Javap –s Cat
得到Cat方法 getcatName 的签名是()Ljava/lang/String,Cat类中的参数是private 所以它没有签名。

原文连接:http://hi.baidu.com/scuyangl/blog/item/f8d93110f670c55cf919b84d.html







  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值