Android NDK介绍及JNI调用

Android NDK是用来编译能在手机ARM平台上运行的,通过C/C++编写的LIB库。

注:理论上不只在ARM平台,以后应该也能在Intel x86平台上运行的。

JNI调用,是为了使java能够调用C/C++所编写的程序的一种机制。

Android NDK和JNI调用结合,就能够使Android程序,调用原生底层用C/C++实现的功能。

1.NDK开发环境配置

在Windows下开发需要安装Cygwin,安装时除了默认选择项外,还要选中gcc和make.

在Linux下,如Ubuntu,通过apt-get 安装gcc和make即可。

然后下载NDK,解压到特定的目录(如:D:\Android)

由于awt兼容性的问题,需要将D:\Android\android-ndk-r7\prebuilt\windows\bin中的awt.exe改为awt_.exe.

同时把D:\Android\android-ndk-r7加入到系统的PATH中,方便使用,

打开Cygwin,cd到/cygdrive/d/Android/android-ndk-r7/samples/hello-jni/jni目录,输入ndk-build,测试环境是否正常

出现以上显示的内容,即表示环境配置正常。

注:/cygdrive/d/即表示windows中的D盘。

2.开发Android测试程序

新建一个Android项目hello,包名com.hello

把自动生成的HelloActivity中修改为以下内容

public class HelloActivity extends Activity {

	private static final String TAG = "Hello";

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

	private native String printJNI();

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		Log.d(TAG, printJNI());
	}
}

通过System.loadLibrary(“hello”)加载C编写的LIB,并定义native方法printJNI,最后调用printJNI()方法测试。

3.开发C语言程序,并通过NDK编译

在新建在helo项目的根目录下新建jni目录,如D:\MyWorkSpace3\hello

打开终端,转到D:\MyWorkSpace3\hello\,执行

javah -classpath bin/classes -d jni com.hello.HelloActivity

执行成功后,会在jni目录下生成com_hello_HelloActivity.h头文件;
接着在jni目录下新建一个C文件com_hello_HelloActivity.c,编写如下代码:

#include "com_hello_HelloActivity.h"
#define LOG_TAG "JNITest"
#undef LOG
JNIEXPORT jstring JNICALL Java_com_hello_HelloActivity_printJNI
(JNIEnv * env, jobject obj)
{
return (*env)->NewStringUTF(env, (char *)"JNITest Native String");
}

再在jni目录下新建一个Android.mk文件,用于配置编译的文件及选项

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hello
LOCAL_SRC_FILES := com_hello_HelloActivity.c

include $(BUILD_SHARED_LIBRARY)

LOCAL_PATH指编译文件的目录,$(call my-dir)指向当前目录
LOCAL_MODULE指定编译生成的lib名字
LOCAL_SRC_FILES指需要编辑的源文件,可自动多个文件,换行时用\分隔

完成后打开Cygwin,cd到项目的jni目录下,
比如/cygdrive/d/MyWorkSpace3/hello/jni
执行ndk-build

生成的lib文件会安装到\libs\armeabi下,名称为libhello.so,但注意在java加载中只用写hello作为lib名称就可以了。

4.检测执行结果

在eclipse中刷新项目,运行,在logcat中应该可以看到“JNITest Native String”

项目附件:hello.zip

Android EditText强制输入数字

需要控制同时软输入法和实体输入法

//控制输入法只能用数字输入法
inputEdit.setInputType(InputType.TYPE_CLASS_PHONE);

//设置文本框只接收数字输入,禁止实体键盘输入其他字符
DigitsKeyListener listener = new DigitsKeyListener();
inputEdit.setKeyListener(listener);

Android EditText 增加自定义过滤

在Android中,可以通过对EditText设置setFilters方法,用代码控制EditText的输入长度,或控制输入小数的位数等。

1.设置EditText的输入长度

inputEdit.setFilters(new InputFilter[] { new InputFilter.LengthFilter(
				length) });

2.控制输入小数的位数

		// 设置小数位数控制
		InputFilter lengthfilter = new InputFilter() {
			public CharSequence filter(CharSequence source, int start, int end,
					Spanned dest, int dstart, int dend) {

				// 删除等特殊字符,直接返回
				if ("".equals(source.toString())) {
					return null;
				}

				String dValue = dest.toString();
				String[] splitArray = dValue.split("\\.");
				if (splitArray.length > 1) {
					String dotValue = splitArray[1];
					int diff = dotValue.length() + 1 - digLength;
					if (diff > 0) {
						return source.subSequence(start, end - diff);
					}
				}
				return null;
			}
		};

		inputEdit.setFilters(new InputFilter[] { lengthfilter });

Android LinearLayout根据状态切换图片(模拟按钮的效果)

在Android中Button可以根据选中,点击等状态切换图片,我想用LinearLayout实现类似的功能,

但是默认情况下pressed状态“容器类”(继承于ViewGroup的类)是接收不到的,

所以LinearLayout的按下就没有效果,后来分析代码,可以通过设置setAddStatesFromChildren

方法获得内部View的状态,就可以取得pressed状态。

linearLayout.setAddStatesFromChildren(true);

还有一个相反的方法setDuplicateParentStateEnabled,内部类获得外部容器类的状态,

这个方法和setAddStatesFromChildren不能同时设置,同时设置会产生异常。

Android layout布局相关的注意点

1.不要过多的嵌套布局,特别是在ListView中需要重复获取的情况下。嵌套布局的情况可以采用RelativeLayout替代LinearLayout,减少嵌套层数。

2.可以采用SDK工具里的hierarchyviewer,分析layout的执行效率。

3.利用新版的ADT(adt1.6)的提示功能,纠正布局文件中影响性能的部分。

4.在采用LinearLayout布局时,尽量不要嵌套中使用layout_weight属性,这会导致所有的内部VIew执行两次measure.

5.对于公用的layout编写,可以采用<merge>作为父标签,这样include到其他layout中后,merge标签不会作为容器,可以减少layout层次。

<merge xmlns:android="http://schemas.android.com/apk/res/android">

<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/add"/>

<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/delete"/>

</merge>

6.采用ViewStub按需加载View

<ViewStub
    android:id="@+id/stub_import"
    android:inflatedId="@+id/panel_import"
    android:layout="@layout/progress_overlay"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom" />

加载方法:

View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

加载完成后,ViewStub的Id就不再有效了

7.在ListView中采用ViewHolder保存View的引用,不要重复使用findViewById方法,
这样可加快执行的性能。

ViewHolder holder = new ViewHolder();
holder.icon = (ImageView) convertView.findViewById(R.id.listitem_image);
holder.text = (TextView) convertView.findViewById(R.id.listitem_text);
holder.timestamp = (TextView) convertView.findViewById(R.id.listitem_timestamp);
holder.progress = (ProgressBar) convertView.findViewById(R.id.progress_spinner);
convertView.setTag(holder);