在android下,其实有另外的一类应用程序,他们用Java开发,但却不使用android的应用框架,不包含android应用程序四大组件中的任何一个,几乎就与我们平常在PC机上开发的那些Java应用程序一样。这里,我们来看一下,这样的应用程序要如何实现。
最典型的无android framework的android Java程序就是两个我们经常会用到的工具,一个是monkey,另外一个是am。我们通常用前者来做monkey测试,而常常使用后者,来执行通过命令行启动Activity等操作。这里我们也会以这两个应用程序为例,来研究android下frameworkless Java应用程序的开发方法。
要了解这些app的结构和实现,最简单的方式则是直接来阅读code和Android.mk 文件了。我们来看一下monkey的code,在development/cmds/monkey/monkey下。首先,我们可以先来看一下这个project的目录结构:
01-Nov-2013 | 4 KiB | ||
01-Nov-2013 | 423 | ||
01-Nov-2013 | 731 | ||
01-Nov-2013 | 0 | ||
01-Nov-2013 | 217 | ||
01-Nov-2013 | 10.4 KiB | ||
01-Nov-2013 | 4.1 KiB | ||
src/ | 01-Nov-2013 | 4 KiB |
这个project根目录下面的内容不是很多,src/目录存放所有的源代码文件,Android.mk用于编译这个project,一个看上去有点奇怪的名为"monkey"的文件,还有其他一些对于我们了解这样的项目的创建方法而言无关紧要的文件,比如readme,notice和license之类。接着我们来看一下这个project Android.mk 文件的实现:
# Copyright 2008 The Android Open Source Project
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_MODULE := monkey
include $(BUILD_JAVA_LIBRARY)
################################################################
include $(CLEAR_VARS)
ALL_PREBUILT += $(TARGET_OUT)/bin/monkey
$(TARGET_OUT)/bin/monkey : $(LOCAL_PATH)/monkey | $(ACP)
$(transform-prebuilt-to-target)
这个Android.mk指导编译系统在编译时做了两件事情,一是将project下所有的Java文件编译为一个Java library,LOCAL_MODULE的值为monkey,也就意味着编译的结果将会是一个名为monkey.jar的文件,编译出来的这个jar文件最终将位于设备的/system/framework/目录下。另外,就是将当前目录下那个名为monkey的文件,做一个copy,最终这个文件将位于设备的/system/bin/目录下。
但monkey.jar和那个monkey文件分别是做什麽的,而它们到底又是一种什麽样的关系呢?我们平常下monkey命令时执行的又是哪一个呢?我们来看project根目录下的那个monkey文件:
# Script to start "monkey" on the device, which has a very rudimentary
# shell.
#
base=/system
export CLASSPATH=$base/framework/monkey.jar
trap "" HUP
exec app_process $base/bin com.android.commands.monkey.Monkey $*
哦,原来这是一个shell脚本文件。在这个脚本文件中,就是设置环境变量,然后执行app_process本地应用程序。传给脚本文件的所有参数,也都会直接传递给app_process命令。由此,即是说,当我们执行monkey命令时,所直接执行的其实是这个脚本文件,然后透过脚本文件,再来执行monkey Java应用程序。
app_process大概就像是我们PC平台Java环境中的java命令一样,即是Java虚拟机。我们在设备上直接执行app_process这个命令,可以看到这个app吐出如下的log:
root@cay:/ # app_process
Error: no class name or --zygote supplied.
Usage: app_process [java-options] cmd-dir start-class-name [options]
Illegal instruction (core dumped)
这个即是android下DVM虚拟机,以命令行的方式启动的方法。由这个usage和前面那个shell脚本文件的实现,不难猜测,com.android.commands.monkey.Monkey应该是monkey这个Java app的main class。接着我们来简单看一下这个main class的实现:
/**
* Command-line entry point.
*
* @param args The command-line arguments
*/
public static void main(String[] args) {
// Set the process name showing in "ps" or "top"
Process.setArgV0("com.android.commands.monkey");
int resultCode = (new Monkey()).run(args);
System.exit(resultCode);
}
/**
* Run the command!
*
* @param args The command-line arguments
* @return Returns a posix-style result code. 0 for no error.
*/
private int run(String[] args) {
// Super-early debugger wait
for (String s : args) {
if ("--wait-dbg".equals(s)) {
Debug.waitForDebugger();
}
}
// Default values for some command-line options
mVerbose = 0;
mCount = 1000;
mSeed = 0;
mThrottle = 0;
// prepare for command-line processing
mArgs = args;
mNextArg = 0;
// set a positive value, indicating none of the factors is provided yet
for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
mFactors[i] = 1.0f;
}
if (!processOptions()) {
return -1;
}
if (!loadPackageLists()) {
return -1;
}
// now set up additional data in preparation for launch
if (mMainCategories.size() == 0) {
mMainCategories.add(Intent.CATEGORY_LAUNCHER);
mMainCategories.add(Intent.CATEGORY_MONKEY);
}
if (mSeed == 0) {
mSeed = System.currentTimeMillis() + System.identityHashCode(this);
}
/**
* Command-line entry point.
*
* @param args The command-line arguments
*/
public static void main(String[] args) {
// Set the process name showing in "ps" or "top"
Process.setArgV0("com.android.commands.monkey");
int resultCode = (new Monkey()).run(args);
System.exit(resultCode);
}
/**
* Run the command!
*
* @param args The command-line arguments
* @return Returns a posix-style result code. 0 for no error.
*/
private int run(String[] args) {
// Super-early debugger wait
for (String s : args) {
if ("--wait-dbg".equals(s)) {
Debug.waitForDebugger();
}
}
// Default values for some command-line options
mVerbose = 0;
mCount = 1000;
mSeed = 0;
mThrottle = 0;
// prepare for command-line processing
mArgs = args;
mNextArg = 0;
// set a positive value, indicating none of the factors is provided yet
for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
mFactors[i] = 1.0f;
}
if (!processOptions()) {
return -1;
}
if (!loadPackageLists()) {
return -1;
}
// now set up additional data in preparation for launch
if (mMainCategories.size() == 0) {
mMainCategories.add(Intent.CATEGORY_LAUNCHER);
mMainCategories.add(Intent.CATEGORY_MONKEY);
}
if (mSeed == 0) {
mSeed = System.currentTimeMillis() + System.identityHashCode(this);
}
这些就完全是常规Java 应用程序的写法了。不过我们也需要更正一下前面一些提法的错误之处。类似于monkey的这种Java应用程序,不一定完全不会使用到android framework的任何组件,只不过这些应用程序的写法,不同于常规android应用程序的写法,倒是与常规的Java应用程序的写法无异。如果这些应用程序要使用android framework提供的功能,则方法就与使用普通的jar文件无异。
android下am命令的project结构与monkey大同小异,只不过,am这个project的路径为frameworks/base/cmds/am/。am的project结构:
01-Nov-2013 | 4 KiB | ||
01-Nov-2013 | 210 | ||
01-Nov-2013 | 354 | ||
01-Nov-2013 | 0 | ||
01-Nov-2013 | 10.4 KiB | ||
src/ | 01-Nov-2013 | 4 KiB |
am project Android.mk文件的实现:
# Copyright 2008 The Android Open Source Project
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_MODULE := am
include $(BUILD_JAVA_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := am
LOCAL_SRC_FILES := am
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE_TAGS := optional
include $(BUILD_PREBUILT)
am脚本文件的实现:
#!/system/bin/sh
#
# Script to start "am" on the device, which has a very rudimentary
# shell.
#
base=/system
export CLASSPATH=$base/framework/am.jar
exec app_process $base/bin com.android.commands.am.Am "$@"
Done.