Android JNI和NDK学习(2)--编程入门

NDK代码编写

####1. 首先是Java代码的编写
Android NDK Sample里面的Hello-jni工程. Hellojni.java

/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 	* Licensed under the Apache License, Version 2.0 (the "License");
 	* you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 	*/
package com.example.hellojni;

import android.app.Activity;
import android.widget.TextView;
import android.os.Bundle;


public class HelloJni extends Activity
{
  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);

     /* Create a TextView and set its content.
      * the text is retrieved by calling a native
      * function.
      */
	    TextView  tv = new TextView(this);
	   tv.setText( stringFromJNI() );
	   setContentView(tv);
  }

 /* A native method that is implemented by the
  * 'hello-jni' native library, which is packaged
  * with this application.
  */
 public native String  stringFromJNI();

 /* This is another native method declaration that is *not*
  * implemented by 'hello-jni'. This is simply to show that
  * you can declare as many native methods in your Java code
	 * as you want, their implementation is searched in the
  * currently loaded native libraries only the first time
	 * you call them.
	 *
	 * Trying to call this function will result in a
	 * java.lang.UnsatisfiedLinkError exception !
	 */
	public native String  unimplementedStringFromJNI();

	/* this is used to load the 'hello-jni' library on application
	 * startup. The library has already been unpacked into
	 * /data/data/com.example.hellojni/lib/libhello-jni.so at
	 * installation time by the package manager.
	 */
	static {
	    System.loadLibrary("hello-jni");
 }
}

这段代码很简单,注释也很清晰,这里只提两点::

static{ 
System.loadLibrary("hello-jni"); 
}

表明程序开始运行的时候会加载hello-jni, static区声明的代码会先于onCreate方法执行,因为这是属于类的静态方法。如果你的程序中有多个类,而且如果HelloJni这个类不是你应用程序的入口,那么hello-jni(完整的名字是libhello-jni.so)这个库会在第一次使用HelloJni这个类的时候加载。

public native String stringFromJNI(); 
public native String unimplementedStringFromJNI();

可以看到这两个方法的声明中有 native 关键字, 这个关键字表示这两个方法是本地方法,也就是说这两个方法是通过本地代码(C/C++)实现的,在java代码中仅仅是声明。

用eclipse编译该工程,生成相应的.class文件,这步必须在下一步之前完成,因为生成.h文件需要用到相应的.class文件。
####2.编写C/C++代码

利用javah这个工具生成相应的.h文件,然后根据这个.h文件编写相应的C/C++代码。

2.1 生成相应.h文件:

就拿我这的环境来说,首先在终端下进入刚刚建立的HelloJni工程的目录:

~$ cd workspace/android/NDK/hello-jni/

ls查看工程文件

~/workspace/android/NDK/hello-jni$ ls 
AndroidManifest.xml  assets  bin  default.properties  gen  res  src 

可以看到目前仅仅有几个标准的android应用程序的文件(夹)。

首先我们在工程目录下建立一个jni文件夹:

~/workspace/android/NDK/hello-jni$ mkdir jni 
~/workspace/android/NDK/hello-jni$ ls 
AndroidManifest.xml  assets  bin  default.properties  gen  jni  res  src 

下面就可以生成相应的.h文件了:

~/workspace/android/NDK/hello-jni$ javah -classpath bin -d jni com.example.hellojni.HelloJni 

-classpath bin:表示类的路劲

-d jni: 表示生成的头文件存放的目录

com.example.hellojni.HelloJni 则是完整类名

执行这个命令的时候可能会出错,因为需要加载android.jar关联类库.所以需要加上这个参数

-bootclasspath …/android.jar

这一步的成功要建立在已经在 bin/com/example/hellojni/ 目录下生成了 HelloJni.class的基础之上。

现在可以看到jni目录下多了个.h文件:

~/workspace/android/NDK/hello-jni$ cd jni/ 
~/workspace/android/NDK/hello-jni/jni$ ls 
com_example_hellojni_HelloJni.h

我们来看看com_example_hellojni_HelloJni.h的内容:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_hellojni_HelloJni */

#ifndef _Included_com_example_hellojni_HelloJni
#define _Included_com_example_hellojni_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_hellojni_HelloJni
 * Method:    stringFromJNI
 	* Signature: ()Ljava/lang/String;
 	*/
JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI
  (JNIEnv *, jobject);

/*
 * Class:     com_example_hellojni_HelloJni
 * Method:    unimplementedStringFromJNI
* Signature: ()Ljava/lang/String;
 	*/
JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_unimplementedStringFromJNI
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

上面代码中的JNIEXPORT 和 JNICALL 是jni的宏,在android的jni中不需要

按照:java_pacakege_class_mathod 形式来命名。

也就是说:

Hello.java中 stringFromJNI() 方法对应于 C/C++中的Java_com_example_hellojni_HelloJni_stringFromJNI() 方法

2.2 编写C文件

/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 	* You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
#include <string.h>
#include <jni.h>

/* This is a trivial JNI example where we use a native method
 	* to return a new VM String. See the corresponding Java source
 	* file located at:
 *
 	*   apps/samples/hello-jni/project/src/com/example/HelloJni/HelloJni.java
 */
jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
    return (*env)->NewStringUTF(env, "Hello Wolrd JNI");
}

在这里我们先不对Android.mk文件做改动.android.mk在jni目录下,Android.mk 文件是Android 的 makefile文件,内容如下:

# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.c

include $(BUILD_SHARED_LIBRARY)

在eclipse下进行重新编译.将生成的so库打包在apk内运行,就可以看到结果了.

To Be Continued

我会继续对Android.mk文件做研究

Android JNI和NDK学习(1)--搭建开发环境

NDK简介

NDK的好处:

1. 代码的保护,由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大。

2. 在NDK中调用第三方C/C++库,因为大部分的开源库都是用C/C++代码编写的。

3. 便于移植,用C/C++写得库可以方便在其他的嵌入式平台上再次使用。

Linux下NDK环境搭建

android的NDK开发需要在linux下进行: 因为需要把C/C++编写的代码生成能在arm上运行的.so文件,这就需要用到交叉编译环境,而交叉编译需要在linux系统下才能完成。
安装android-ndk开发包,在google android 官网下载: 通过这个开发包的工具才能将android jni 的C/C++的代码编译成库
android应用程序开发环境: 包括eclipse、java、 android sdk、 adt等。
安装完之后,需要将android-ndk的路劲加到环境变量PATH中:

sudo gedit /etc/environment

在environment的PATH环境变量中添加你的android-ndk的安装路劲,然后再让这个更改的环境变量立即生效:

 source  /etc/environment

经过了上述步骤,在命令行下敲:

ndk-bulid

弹出如下的错误,而不是说ndk-build not found,就说明ndk环境已经安装成功了。

Windows下NDK环境搭建

在NDKr7开始,google的windos版NDK提供了一个ndk-build.cmd的脚本,这样就可以直接利用这个脚本编译,而不需要cygwin了。前面章节我介绍了NDK利用cygwin来进行配置(《NDK利用cygwin环境配置》),现在开始我们要与时俱进了,其实不同之处就是设置c/c++编译器的不同了。
1. 选择你的android工程,右击选择Properties,弹出配置界面,之后再点击Builders,弹出项目的编译编译工具 列表,之后点击new,新添加一个编译器,点击之后出现添加界面,选择Program,点击ok。
2. 出现了添加界面,我们先给编译器设置名称,如XXX_builder。
3. 设置Location为\ndk-build.cmd 4. 设置Working Directory为${workspace_loc:/项目名称} 截图如下:

ndk_configure.jpg

  1. 切换到Refersh选项卡,给Refersh resources upon completion打上勾,选择The entire resource选项。
    截图如下:

ndk_configure2.jpg

  1. 切换到Build Options选项卡,勾选上最后三项。再点击Specify Resource按钮,选择你的android工程的jni目录。
    截图如下:

ndk_configure3.jpg

  1. 在编译工具列表,我们最好将我们新建的编译器置顶。选中点击Up按钮置顶。避免重复编译两次

用Pelican和Github Pages在Linux上搭建个人博客

搭建环境

Linux环境下搭建,采用ubuntu,使用其它发行版过程基本相同。

Github Pages

  • 注册Github,注册和配置SSH密钥过程help page写得很清楚。
  • 不过现在github支持http传输良好,所以也可以不用配置SSH,通过用户名密码即可登录。
  • 在Github创建一个名为username.github.io的版本库(将username替换成自己的Github账户名)。
  • Setting -> Automatic page generator -> Continue to layout,选择一个模板,并发布。

十分钟后在username.github.io页面就已经生成了一个页面。访问该网址即可看到。

配置本地环境

安装Pelican和Markdown:

在这里我没有用Jekyll因为它是Ruby写的对它没什么兴趣。所以我采用Python编写的Pelican

介绍

Pelican是一套开源的使用Python编写的博客静态生成, 可以添加文章和和创建页面, 可以使用MarkDown reStructuredTextAsiiDoc 的格式来抒写, 同时使用 Disqus评论系统, 支持 RSSAtom输出, 插件, 主题, 代码高亮等功能, 采用Jajin2模板引擎, 可以很容易的更改模板
####安装

安装Pelican有很多种方法。一种使用python的包管理器pip进行安装。

$sudo apt-get install python-pip
$sudo pip install pelican
$sudo pip install markdown

另一种就是从github上克隆Pelican

git clone git://github.com/getpelican/pelican.git       
cd pelican
python setup.py install

写第一篇博客

####搭建目录

mkdir blog
cd blog
pelican-quickstart

在回答一系列问题过后你的博客就建成的, 主要生成下列文件:
生成的目录结构:

blog/
├── content
│   └── *.md             # markdown文件
├── output               # 默认的输出目录
├── develop_server.sh
├── Makefile
├── pelicanconf.py       # 主配置文件
└── publishconf.py

写一篇文章

content 目录新建一个 test.md文件, 填入一下内容:

Title: 文章标题
Date: 2013-04-18
Category: 文章类别
Tag: 标签1, 标签2
这里是内容

然后执行

make html

生成了html。然后执行

./develop_server.sh start

开启一个测试服务器, 这会在本地 8000 端口建立一个测试web服务器, 可以使用浏览器打开:http://localhost:8000来访问这个测试服务器, 然后就可以欣赏到你的博客了

创建一个About页面

这里以创建 About页面为例

content目录创建pages目录

mkdir content/pages

然后创建About.md并填入下面内容

Title: About Me
Date: 2013-04-18

About me content

执行 make html 生成html, 然后打开 http://localhost:8000查看效果

使用Pelican支持评论

使用Disqus作为评论系统,注册帐号后直接在pelicanconf.conf中添加:

DISQUS_SITENAME = your_shortname

然后执行

make html

使用浏览器打开:http://localhost:8000来查看效果

主题

安装主题:
Pelican本身提供很多主题可供选择,可以从githubclone下来

git clone https://github.com/getpelican/pelican-themes.git
cd pelican-themes
pelican-themes -i bootstrap2

其中bootstrap2是选择使用的主题,pelican主题的Github目录下几乎每个都提供了预览.

然后,在配置文件pelicanconf.py中添加:

THEME = u"bootstrap2'

重新make,就生成了带有选定主题的页面。

使用插件

Pelican 一开始是将插件内置的, 但是新版本 Pelican将插件隔离了出来, 所以我们要到github上 克隆一份新的插件, 在博客目录执行

git clone git://github.com/getpelican/pelican-plugins.git    

现在我们博客目录就新添了一个 pelican-plugins目录, 我们已配置sitemap插件为例, sitemap插件可以生成 sitemap.xml 供搜索引擎使用

pelicanconf.py配置文件里加上如下项:

PLUGIN_PATH = u"pelican-plugins"

PLUGINS = ["sitemap"]

配置sitemap 插件

SITEMAP = {
    "format": "xml",
    "priorities": {
        "articles": 0.7,
        "indexes": 0.5,
        "pages": 0.3,
    },
    "changefreqs": {
        "articles": "monthly",
        "indexes": "daily",
        "pages": "monthly",
    }
}

然后再执行

make html

打开浏览器请求 http://localhost:8000/sitemap.xml即可看到生成的 Sitemap 了

添加Google Analytics

去Google Analytics申请账号,记下跟踪ID。 在pelicanconf.py添加

GOOGLE_ANALYTICS = 跟踪ID

使用Google Webmasters

Google Webmasters上注册即可。
这个就是Google站长工具,使用它的目的是为了让博客被Google更好的收录,比如手动让Googlebot抓取、提交Robots、更新Sitemap等等,各方面完爆百度站长工具。
###上传Github
最后在你的output文件夹内

git init
git add .
git commit -m 'first commit' 
git remote add origin git@github.com:yourname/yourname.github.io.git
git push -u origin master

这样就大功告成了!