在Android中使用OSGi框架(Knopflerfish)

OSGi是用Java实现的一个模块化服务平台。每个模块被称之为Bundle,可以提供服务,也可以在不重启OSGi框架的情况下被安装或卸载。Knopflerfish是一个完全开源的OSGi R4.2标准的实现。

Android能够无缝的集成现有的Java代码,尽管使用的是与现有java字节码格式不兼容的虚拟机Dalvik,但是它可以轻松的将现有的jar文件和类转换为Android使用的Dalvik字节码格式。由于OSGi框架自身和Bundle都只是普通的jar文件,所以他们都应该可以在Android上运行。事实上,大多数时候是没问题的。

注意:这里只是如何在Android中嵌入OSGi系列文章的第一部分

如果只是想让OSGi框架在Android上跑起来,那么只需要编译Knopflerfish的Android版本,复制到设备上,然后就可以通过命令行启动起来了(见上一篇文章这里)。

现在来看看如何将Knopflerfish和一系列Bundle嵌入到Android应用中,并且从应用中启动和管理OSGi框架和Bundle。

通过代码启动OSGi大概需要下面这几个步骤:

  1. 创建framework实例(通过framework factory)
  2. 初始化framework
  3. 设置initlevel,并启动/安装 bunldes
  4. 为所有的initlevel重复前述步骤
  5. 设置startlevel
  6. 启动framework

嵌入Framework

现在创建一个Android应用,包含一个Actviity。然后在app中引入framework.jar,这样就可以通过一个FrameworkFactory创建OSGi的framework实例了。

import org.knopflerfish.framework.FrameworkFactoryImpl;
import org.osgi.framework.BundleException;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.launch.FrameworkFactory;
...
private Framework mFramework;
...
Dictionary fwprops = new Hashtable();
// add any framework properties to fwprops
FrameworkFactory ff = new FrameworkFactoryImpl();
mFramework = ff.newFramework(fwprops);
try {
    mFramework.init();
} catch (BundleException be) {
    // framework initialization failed
}

引入的jar文件不需要dex化,后面build的时候会自动完成这一步的。

Bundle文件dex化

现在bundle的jar文件可以被添加到应用中了,可以作为raw资源放在res/raw下面,也可以放在assets/bunldes。后面这种方式有一个优势:不需要被重命名,而且res资源的名字数量是有限的。

Bundle的jar文件需要被转换成dex格式,下面这个简单的脚本可以完成这件事:

dexify() {
    for f in $*; do
        tmpdir="`mktemp -d`"
        tmpfile="${tmpdir}/classes.dex"
        dx --dex --output=${tmpfile} ${f}
        aapt add ${f} ${tmpfile}
        rm -f ${tmpfile}
        rmdir ${tmpdir}
    done
}

然后就可以通过命令dexify assets/bundles/*将bundles转换为dex文件。如果是按照Knopflerfish的教程编译的Knopflerfish,那么不需要将这些bundle的jar文件dex化,但是必须从knopflerfish的framework.jar文件中去掉classes.dex。

安装、启动Bundles

下面这段代码可以帮助启动bundle,并设置initlevel/startlevel。

private void startBundle(String bundle) {
    Log.d(TAG, "starting bundle " + bundle);
    InputStream bs;
    try {
        bs = getAssets().open("bundles/" + bundle);
    } catch (IOException e) {
        Log.e(TAG, e.toString());
        return;
    }

    long bid = -1;
    Bundle[] bl = mFramework.getBundleContext().getBundles();
    for (int i = 0; bl != null && i < bl.length; i++) {
        if (bundle.equals(bl[i].getLocation())) {
            bid = bl[i].getBundleId();
        }
    }

    Bundle b = mFramework.getBundleContext().getBundle(bid);
    if (b == null) {
        Log.e(TAG, "can't start bundle " + bundle);
        return;
    }

    try {
        b.start(Bundle.START_ACTIVATION_POLICY);
        Log.d(TAG, "bundle " + b.getSymbolicName() + "/" + b.getBundleId() + "/"
                + b + " started");
    } catch (BundleException be) {
        Log.e(TAG, be.toString());
    }

    try {
        bs.close();
    } catch (IOException e) {
        Log.e(TAG, e.toString());
    }
}

private void installBundle(String bundle) {
    Log.d(TAG, "installing bundle " + bundle);
    InputStream bs;
    try {
        bs = getAssets().open("bundles/" + bundle);
    } catch (IOException e) {
        Log.e(TAG, e.toString());
        return;
    }

    try {
        mFramework.getBundleContext().installBundle(bundle, bs);
        Log.d(TAG, "bundle " + bundle + " installed");
    } catch (BundleException be) {
        Log.e(TAG, be.toString());
    }

    try {
        bs.close();
    } catch (IOException e) {
        Log.e(TAG, e.toString());
    }
}

private void setStartLevel(int startLevel) {
    ServiceReference sr = mFramework.getBundleContext()
        .getServiceReference(StartLevel.class.getName());
    if (sr != null) {
        StartLevel ss =
            (StartLevel)mFramework.getBundleContext().getService(sr);
        ss.setStartLevel(startLevel);
        mFramework.getBundleContext().ungetService(sr);
    } else {
        Log.e(TAG, "No start level service " + startLevel);
    }
}

private void setInitlevel(int level) {
    ServiceReference sr = mFramework.getBundleContext()
        .getServiceReference(StartLevel.class.getName());
    if (sr != null) {
        StartLevel ss =
            (StartLevel)mFramework.getBundleContext().getService(sr);
        ss.setInitialBundleStartLevel(level);
        mFramework.getBundleContext().ungetService(sr);
        Log.d(TAG, "initlevel " + level + " set");
    } else {
        Log.e(TAG, "No start level service " + level);
    }
}

现在可以安装并启动bundle了

setInitlevel(1);
installBundle("event_all-3.0.4.jar");
startBundle("event_all-3.0.4.jar");
// install/start other bundles...

setStartLevel(10);

try {
    mFramework.start();
} catch (BundleException be) {
    Log.e(TAG, be.toString());
    // framework start failed
}

Log.d(TAG, "OSGi framework running, state: " + mFramework.getState());

问题

如果你按照上文所述一步步做下来了,你可能会发现还是没法跑起来。由于framework的classloader是在运行期加载的bundle文件,Dalvik虚拟机会试图将优化过的dex类文件放到一个系统目录下面/data/dalvik-cache,但是没有root权限的普通应用程序是不能写入那儿的。

下回将如何解决这个问题。

via source

在Android中使用OSGi框架(Apache Felix)

本文描述了如何在Android中使用Apache Felix

Dalvik VM

Android允许开发者使用Java开发应用,但出于某些原因,代码实际是运行在名为Dalvik的一个针对移动设备平台的虚拟机上,而不是标准的Java虚拟机。Dalvik并不使用标准的Java字节码格式,而是使用Android SDK中的一个工具dx将由Java编译出来的类文件转换为另外一种类文件格式(.dex格式)。这个转换是在编译期完成的。

准备Bundles

虽然Felix从1.0.3开始内置了Android的支持,但是想要成功的让它跑起来还是需要费点力气。我们仍然需要安装Android SDK,并且PATH环境变量中包含Android SDK的工具目录<android_SDK_HOME>/tools

第一步: 每一个用到的Jar文件,无论是Felix库还是你自己写的Bundle,都需要包含对应的DEX。也就说,需要为jar文件创建对应的dex文件:

dx --dex --output=classes.dex JAR_file.jar

然后将这个dex文件加入到jar文件中:

aapt add JAR_file.jar classes.dex

第二步: 将处理过的jar文件传到模拟器(或真机)中:

adb push JAR_file.jar path_emulator/JAR_file.jar

第三步: 以演示代码为例,准备Felix的jar文件和Bundle的jar文件:

目录结构

osgi-android: /
\- bin
\- bundle
\- conf
\- felix.sh

准备Felix jar文件

export PATH=<path-to-android>/tools:$PATH
cd bin
dx --dex --output=classes.dex felix.jar
aapt add felix.jar classes.dex

准备bundle的jar文件

cd bundle
dx --dex --output=classes.dex     org.apache.felix.shell-1.0.0.jar
aapt add org.apache.felix.shell-1.0.0.jar classes.dex
dx --dex --output=classes.dex org.apache.felix.shell.tui-1.0.0.jar
aapt add org.apache.felix.shell.tui-1.0.0.jar classes.dex
dx --dex --output=classes.dex EnglishDictionary.jar
aapt add EnglishDictionary.jar classes.dex
dx --dex --output=classes.dex FrenchDictionary.jar
aapt add FrenchDictionary.jar classes.dex
dx --dex --output=classes.dex SpellChecker.jar
aapt add SpellChecker.jar classes.dex

复制到模拟器中

cd osgi-android
find * -type f -exec adb push {} /data/felix/{} \;

启动Felix

完成上面的步骤之后,现在可以准备在Android上启动Felix和Bundle了

adb shell
cd /data/felix
sh felix.sh

felix.sh是一个shel脚本,用于启动Felix main class。

/system/bin/dalvikvm -Xbootclasspath:/system/framework/core.jar \

-classpath bin/felix.jar org.apache.felix.main.Main

如果一切顺利,现在你应该能看到Felix的命令行shell了。输入help可以看到命令说明。

现在可以安装EnglishDictionary,FrenchDictionary和SpellChecker来试试看Felix是否工作正常。这里有几个Apache Felix的示例:Apache Felix 教程例子2Apache Felix 教程例子2bApache Felix 教程例子5

  • EnglishDictionary - 提供一个字典服务,支持下面几个词"welcome", "to", "the", "osgi", "tutorial"
  • FrenchDictionary - 提供一个字典服务,支持下面几个词"bienvenue", "au", "tutoriel", "osgi"
  • SpellChecker - 提供一个拼写检查服务,可以检查第一个英文此单的几个单词

在Felix Shell中启动Bundle

start file:bundle/EnglishDictionary.jar
start file:bundle/FrenchDictionary.jar
start file:bundle/SpellChecker.jar

嵌入Felix

Apache Felix也可以被集成到Android的应用中。只需要在Activity的onCreate中嵌入Felix,然后用上面的办法启动bundle即可。

下载

上面的演示代码在此下载

via source

How to enable ORMLite internal log on Android

When using ORM system sometimes we want to what SQLs are executed at background to understand how it works. The ORMLite support several different log system, like SLF4J,COMMONS_LOGGING, LOG4J and ANDROID native log android.util.Log. It will use Android log for internal log by default. In code com.j256.ormlite.android.AndroidLog method isLevelEnabledInternal, it will determinate if log is enabled by Log.isLoggable

private final static String ALL_LOGS_NAME = "ORMLite";
...
return Log.isLoggable(className, androidLevel) || Log.isLoggable(ALL_LOGS_NAME, androidLevel);

That means there are two ways to enable ORMLite internal log.

  1. You can change the default level by setting a system property: 'setprop log.tag. ' Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will turn off all logging for your tag.
  2. You can also create a local.prop file that with the following in it: 'log.tag.=' and place that in /data/local.prop.

The YOUR_LOG_TAG could be any ORMLite short class name or "ORMLite" for all of them. So that you can enable ORMLite internal log like this:

$ adb shell
# setprop log.tag.ORMLite DEBUG

Notice that setprop is temporary until you reboot your device, even on rooted phones. You can persist properties through reboots if you write them into local.prop which is only possible on rooted phones.

"adb shell dumpsys" parameter list

Android开发中,常常可以用adb shell dumpsys这条命令来dump出系统运行时的状态信息,例如可以这样来察看某个应用的内存使用信息

adb shell dumpsys meminfo com.google.android.apps.maps

察看TaskStack

adb shell dumpsys activity activities

察看Alarm列表

adb shell dumpsys alarm

如果在linux下配合 watch 命令更是可以自动刷新实时察看。这条命令的用法是 adb shell dumpsys SERVICE [option] [process name],可用的SERVICE列表:

$ adb shell dumpsys | grep 'DUMP OF SERVICE' | awk '{print $4}' | tr -d ':'
CustomFrequencyManagerService
DirEncryptService
SYSSCOPE
SecTVOutService
SurfaceFlinger
TvoutService_C
accessibility
account
activity
alarm
apn_settings_policy
application_policy
apppermission_control_policy
appwidget
audio
backup
battery
batteryinfo
bluetooth
bluetooth_a2dp
bluetooth_avrcp
bluetooth_policy
browser_policy
clipboard
clipboardEx
com.orange.authentication.simcard
commontime_management
connectivity
content
country_detector
cpuinfo
date_time_policy
dbinfo
device_info
device_policy
devicestoragemonitor
diskstats
drm.drmManager
dropbox
eas_account_policy
edm_proxy
email_account_policy
email_policy
enterprise_policy
enterprise_vpn_policy
entropy
firewall_policy
gfxinfo
hardware
input
input_method
iphonesubinfo
isms
kioskmode
location
location_policy
lock_settings
mdm.remotedesktop
media.audio_flinger
media.audio_policy
media.camera
media.player
meminfo
mini_mode_app_manager
misc_policy
motion_recognition
mount
netpolicy
netstats
network_management
nfc
notification
package
password_policy
permission
phone
phone_restriction_policy
phoneext
power
remoteinjection
restriction_policy
roaming_policy
samplingprofiler
samsung.facedetection_service
scheduling_policy
search
security_policy
sensorservice
serial
servicediscovery
simphonebook
sip
statusbar
telephony.registry
textservices
throttle
tvoutservice
uimode
updatelock
usagestats
usb
vibrator
voip
vpn_policy
wallpaper
wfd
wifi
wifi_policy
wifip2p
window

此外,某些服务还支持如下参数

ACTIVITY MANAGER PENDING INTENTS (adb shell dumpsys activity intents)
ACTIVITY MANAGER BROADCAST STATE (adb shell dumpsys activity broadcasts)
ACTIVITY MANAGER CONTENT PROVIDERS (adb shell dumpsys activity providers)
ACTIVITY MANAGER SERVICES (adb shell dumpsys activity services)
ACTIVITY MANAGER ACTIVITIES (adb shell dumpsys activity activities)
ACTIVITY MANAGER RUNNING PROCESSES (adb shell dumpsys activity processes)
INPUT MANAGER (adb shell dumpsys input)
WINDOW MANAGER LAST ANR (adb shell dumpsys window lastanr)
WINDOW MANAGER POLICY STATE (adb shell dumpsys window policy)
WINDOW MANAGER SESSIONS (adb shell dumpsys window sessions)
WINDOW MANAGER TOKENS (adb shell dumpsys window tokens)
WINDOW MANAGER WINDOWS (adb shell dumpsys window windows)

ant 中通过重新定义 project.all.jars.path 在 classpath 中引入外部 jar 文件

在Android开发中,除了通常在Eclipse中的编译方法之外,有的时候为了进行持续集成,可能还需要用ant进行自动化编译。Android SDK本身已经提供了默认的ant编译脚本,就在每个工程下的build.xml中,其中引用了SDK的编译脚本${sdk_dir}/tools/ant/build.xml 。 通常情况下,在工程根目录下直接执行 ant debug 即可进行一次正常的build。默认的classpath会包括libs目录下的所有jar文件。但是如果工程中使用了USER LIBRARY,或者引用了外部的jar文件,那么在编译中就可能会遇到问题,因为这些jar文件不会被自动包含在classpath中,这时就要扩展ant的path变量,把自己的jar文件加入到classpath中。

通过察看sdk提供的build.xml编译脚本,可以发现javac使用的classpath定义如下:

<path id="project.javac.classpath">
    <path refid="project.all.jars.path"></path>
    <path refid="tested.project.classpath"></path>
</path>


<javac encoding="${java.encoding}"
        source="${java.source}" target="${java.target}"
        debug="true" extdirs="" includeantruntime="false"
        destdir="${out.classes.absolute.dir}"
        bootclasspathref="project.target.class.path"
        verbose="${verbose}"
        classpathref="project.javac.classpath"
        fork="${need.javac.fork}">
    <src path="${source.absolute.dir}"></src>
    <src path="${gen.absolute.dir}"></src>
    <compilerarg line="${java.compilerargs}"></compilerarg>
</javac>

其中 project.all.jars.path 包含了所有的jar文件,我们可以通过在工程目录下的buildxml中重新定义这个变量来引入其他的jar文件。例如在我的工程中,引用了ormlite这个ORM库,为了能够在开发中使用“attach source”察看源码,该jar文件不能放在libs目录中,因为Eclipse不允许对libs目录中的jar文件“attach source”。因此我将此文件放到了libs/ormlite目录中,为了能够将这两个jar文件加入到classpath中,就要重新定义 project.all.jars.path 这个元素。

基本思路是,重新定义-pre-compile这个target,在其中重新定义 project.all.jars.path 的值。

<target name="-pre-compile">
    <echo message="JARPATH=${toString:project.all.jars.path}"></echo>

    <property name="ormlite.dir" value="${jar.libs.dir}/ormlite"></property>
    <path id="ormlite.lib">
        <path path="${toString:project.all.jars.path}"></path>
        <pathelement location="${ormlite.dir}/ormlite-android-4.41.jar"></pathelement>
        <pathelement location="${ormlite.dir}/ormlite-core-4.41.jar"></pathelement>
    </path>

    <path id="project.all.jars.path">
        <path refid="ormlite.lib"></path>
    </path>

    <echo message="JARPATH=${toString:project.all.jars.path}"></echo>
</target>

Android的滚动条实现细节

在Android的UI系统中,每个View都有一个ScrollabilityCache的单例对象。该对象定义在View.java中,用于保存ScrollBar的相关实例和属性(ScrollBar实际上是一个ScrollBarDrawable对象),并且实现了淡入淡出动画效果的线程代码。这些会在该View的所有ScrollBar中共用。

以垂直滚动条为例,画出滚动条的过程大致是:

1. View::Draw
2. View::onDrawScrollBars
scrollBar.setParameters(computeVerticalScrollRange(),
            computeVerticalScrollOffset(),
            computeVerticalScrollExtent(), true);

3. View::onDrawVerticalScrollBar
    scrollBar.draw

4. ScrollBarDrawable::draw

Rect r = getBounds();

if (drawTrack) {
    drawTrack(canvas, r, vertical);
}

if (drawThumb) {
    int size = vertical ? r.height() : r.width();
    int thickness = vertical ? r.width() : r.height();
    int length = Math.round((float) size * extent / range);
    int offset = Math.round((float) (size - length) * mOffset / (range - extent));

    // avoid the tiny thumb
    int minLength = thickness * 2;
    if (length < minLength) {
        length = minLength;
    }
    // avoid the too-big thumb
    if (offset + length > size) {
        offset = size - length;
    }

    drawThumb(canvas, r, offset, length, vertical);
}

How to debug with Android Logging

AP Logger Architecture

AP Logger Architecture

The above image shows the architecture of Android logging system.It provides a java class for logging named android.util.Log. It also provides log macros for native C applications in liblog.

There are four log devices in the kernel. Three for user space log: /dev/log/main, /dev/log/radio, /dev/log/events. One for kernel space log: /dev/log/kernel.

For user space applications, those binary log messages will be written to /dev/log/events. Those log messages with the tag "HTC_RIL" "RILJ" "RILC" "RILD" "RIL" "AT" "GSM" "STK" will be written to /dev/log/radio. Other log messages will be written to /dev/log/main.

The log devices named /dev/log/kernel is for kernel log message collection. A console is registered in /dev/log/kernel and collect all the printk output to the device. It will output kernel log messages with the same log format as other log devices.

Logcat is a tool provided by Android. It could read log messages from log devices and output to the console or to a file. You can find the detail usage later.

An aplog daemon is added for offline log. A filter is added to do some security checking.

Logging interface For Java applications

Logging class Introduction

Class Name: android.util.Log General method:

 Log.v() 
 Log.d() 
 Log.i() 
 Log.w()

The order in terms of verbosity, from least to most is ERROR, WARN, INFO, DEBUG, VERBOSE. VERBOSE should never be compiled into an application except during development. DEBUG logs are compiled in but stripped at runtime. ERROR, WARN and INFO logs are always kept.

TIP: A good convention is to declare a TAG constant in your class: private static final String TAG = "TAG_MyActivity"; and use that in subsequent calls to the log methods. Log.v(TAG, "index=" + i);

When you're building the string to pass into Log.d, Java uses a StringBuilder and at least three allocations occur: the StringBuilder itself, the buffer, and the String object. Realistically, there is also another buffer allocation and copy, and even more pressure on the GC. That means that if your log message is filtered out, you might be doing significant work and incurring significant overhead.

For more details, please visit Android log reference

Program Example

package com.android.hello;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
import android.util.Log; /*import log class*/

private static final String TAG = "MyActivity"; /* define log tag*/

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

        TextView tv = new TextView(this);
        tv.setText("Hello, Android");
        setContentView(tv);

        Log.i(TAG, "this is a log.i message");
        Log.v(TAG, "test is a log.v message");
        Log.d(TAG, "test is a log.d message");
        Log.w(TAG, "test is a log.w message");
        Log.e(TAG, "test is a log.e message");
    }
}

Logging interface For Native applications

Header File include <cutils/log.h>

Logging Macros

Common Logging Macros

LOGV LOGD LOGI LOGW LOGE

Condition Logging Macros

LOGV_IF LOGD_IF LOGI_IF LOGW_IF LOGE_IF

The definition is as below:

#define CONDITION(cond) (__builtin_expect((cond)!=0,0))
#define LOGV_IF(cond, ...)   ( (CONDITION(cond)) ?((void)LOG(LOG_VERBOSE, LOG_TAG, VA_ARGS)) : (void)0 )

Logging Macros overview

API for sending log output.

NOTE:

  1. You should define LOG_TAG in your C source code firstly.
  2. To build out C/C++ applications outside android, you should add LOCAL_SHARED_LIBRARIES := liblog libcutils in your Android.mk file

Program Example

#include <stdio.h> 
#include <cutils/log.h> /* log header file*/
#include <cutils/properties.h>

/* define log tag */
#ifdef LOG_TAG
#undef LOG_TAG
#define LOG_TAG "app"
#endif
int main()
{
    LOGV("Verbose: _app");
    LOGD("Debug: _app");
    LOGI("Info: _app");
    LOGW("Warn: _app");
    LOGE("Error: _app");
    printf("Hello Android.\n");
    return 0;
}

Log command on Android

Command location

/system/bin/log

Command usage

log [-p priorityChar] [-t tag] message

priorityChar should be one of : v, d, i, w, e

Log format on Android

The format of a log messages is

tv_sec   tv_nsec     priority     pid    tid     tag     messageLen       Message
  • tag: log tag
  • tv_sec & tv_nsec: the timestamp of the log messages
  • pid: the process id of where log messages come from
  • tid: the thread id
  • Priority value is one of the following character values, ordered from lowest to highest priority:

    • V — Verbose (lowest priority)*
    • D — Debug*
    • I — Info*
    • W — Warning*
    • E — Error*
    • F — Fatal*
    • S — Silent (highest priority, on which nothing is ever printed)*