本主题说明如何转换 OSGI 框架以在 android 上运行。然后它给出了将 android 包转换为能够调用 android API 的 OSGI 包的提示。
在当前阶段,这些 Android OSGI 捆绑包唯一不能做的就是操纵活动并使用资源和资产。我一直在努力解决这个限制。我希望有关于这个问题的好消息。
我发现在 Eclipse 中使用 Create Plugin 项目工具比将标准 android 包转换为 OSGI 包更难,所以我不会多谈。
您可以在此消息末尾的日志部分跟踪我的成就。
我指的是Knopflerfish项目,因为它是我工作的基础。这些修改旨在在Knopflerfish OSGi android 项目上执行,但实际上适用于其他 OSGI 框架。不需要修改 OSGi 框架本身,我们只会更新项目KfServiceLib
和Knopflerfish 发行版KfBasicApp
的tool
目录。
为捆绑包添加基本的 android 支持
特点和限制
这是android框架的第一级定制。这些更改与上下文或调用线程无关,但它们允许使用一组有限的 android API 类,如android.util.Log
.
由于这些变化,bundle 将能够在其原型和实现中使用 android 类。然而,它们将无法与图形用户界面、内容提供者和系统服务等相关,因为它们缺乏对它的强制性引用。
Knopflerfish 应用程序的变化
实际上,tools/android/apk 下的应用程序能够在 android 上执行 OSGi 框架,但前提是捆绑包仅调用 java 类。作为 Knopflerfish 框架一部分的包就是这种情况,但是想要调用 android API 的自定义包呢?以下是在框架中进行的更改,以使捆绑包能够解析 android 类。
首先,android 包必须是框架包的一部分,这样它们才能被解析。这是 OSGi 属性的目的org.osgi.framework.system.packages.extra
在创建框架之前将属性设置为要导出的 android 包列表,然后您就设置好了。请注意,wild charandroid.*
似乎没有任何作用:我们必须像下面这样一个一个地告诉每个包。
添加到KfServiceLib
文件 src/org/knopflerfish/android/service/KfApk.java
static final String ANDROID_FRAMEWORK_PACKAGES = (
"android,"
+ "android.app,"
+ "android.content,"
+ "android.database,"
+ "android.database.sqlite,"
+ "android.graphics,"
+ "android.graphics.drawable,"
+ "android.graphics.glutils,"
+ "android.hardware,"
+ "android.location,"
+ "android.media,"
+ "android.net,"
+ "android.net.wifi,"
+ "android.opengl,"
+ "android.os,"
+ "android.provider,"
+ "android.sax,"
+ "android.speech.recognition,"
+ "android.telephony,"
+ "android.telephony.gsm,"
+ "android.text,"
+ "android.text.method,"
+ "android.text.style,"
+ "android.text.util,"
+ "android.util,"
+ "android.view,"
+ "android.view.animation,"
+ "android.webkit,"
+ "android.widget");
然后我们将额外的包设置在KfApk.newFramework()
config.put(Constants.FRAMEWORK_STORAGE, fwDir);
// Export android packages so they can be referenced by bundles
config.put(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA,
ANDROID_FRAMEWORK_PACKAGES);
备注:如果可以的话,最好用文件而不是程序中的代码来设置额外的配置。
以包的形式导入 android 包
即使 android 包被添加到框架声明的系统包中,bundle 仍然必须导入它们才能解析,就像任何其他导入的包一样。
例子:
导入包:org.osgi.framework、android.content、android.widget、android.util
备注:您可以使用 Knopflerfish Eclipse 插件的“自动”按钮自动更新导入,就像它们应该的那样。
将上下文传递给捆绑包
Knopflerfish 应用程序的更多变化
在这些更改之后,您应该能够运行捆绑包自行启动活动或访问上下文的资源。整套 android API 类应该对 bundle 完全可用。但是有一些限制适用于捆绑编码来实现这一点。我们所需要的只是应用程序上下文的引用,所以我们将把它推送到框架中!
添加到org.knopflerfish.android.service.Knopflerfish.onStartCommand()
if (fw != null) {
// Register the application's context as an OSGi service!
BundleContext bundleContext = fw.getBundleContext();
regContext = bundleContext.registerService(Context.class,
getApplicationContext(), new Hashtable());
sendMessage(Msg.STARTED, (Serializable) KfApk.getFrameworkProperties());
} else {
// framework did not init/start
sendMessage(Msg.NOT_STARTED);
stopSelf();
return;
}
我们正在传递应用程序的上下文,并且只有这个,因为它是应用程序整个生命周期中唯一存在的上下文。它将在应用程序启动后立即设置,这意味着在安装或系统启动之后。捆绑包可以在此上下文上保持强引用,这很好。
捆绑包如何使用上下文
一个包Context
从BundleContext
传递给它的激活器中获取:
static Context context;
public void start(BundleContext bc) throws Exception {
ServiceReference<Context> ref = bc.getServiceReference(Context.class);
context = bc.getService(ref);
}
由于包在与 UI 线程不同的线程中运行,因此 UI 操作只能在 UI 线程上“推送”时执行。为此,明智的做法是设计一个可重用的实用程序方法,如下所示:
public static void runOnContext(Context context, Runnable runnable) {
Handler handler = new Handler(context.getMainLooper());
handler.post(runnable);
}
此方法应该是实用程序包中服务的一部分,因为它应该由许多不同的 android 包以相同的方式访问。
例如,这个包在开始时显示“Hello”:
public void start(BundleContext bc) throws Exception {
ServiceReference<Context> ref = bc.getServiceReference(Context.class);
final Context context = bc.getService(ref);
runOnContext(context, new Runnable() {
public void run() {
Toast.makeText(context, "Hello", Toast.LENGTH_LONG).show();
}
});
}
使用捆绑软件等应用程序的廉价方法
我将把转换为 OSGI bundle 的 APK 简称为bundle APK。
- 创建一个常规的 APK,这要归功于 Eclipse Android Project
- 将参考库条目添加到OSGi 框架的项目构建路径(在我的例子中是 framework.jar)
- 编辑描述包的包清单文件
bundle.manifest
(参见下面的示例)。此文件实际上不是 APK 的一部分,但将在自定义构建步骤中使用 - 假设您的应用程序包是
com.acme.helloworld
(此值通过 AndroidManifest.xml 中的 manifest:package 设置),您的 OSGI 包的 Activator 类必须放在包中com.acme.helloworld
,并且您必须Bundle-SymbolicName: com.acme.helloworld
在包清单中设置。如果不满足这些条件中的任何一个,则将导致java.lang.NoClassDefFoundError
运行时运行。
提醒一下,您的捆绑清单文件应如下所示:
Manifest-Version: 1.0
Bundle-Vendor: Acme
Bundle-Version: 1.0.0
Bundle-Name: HelloWorldBundle
Bundle-ManifestVersion: 2
Bundle-Activator: com.acme.helloworld.Activator
Bundle-Description: Hello World Bundle
Import-Package: org.osgi.framework
Bundle-SymbolicName: com.acme.helloworld
Bundle-RequiredExecutionEnvironment: OSGi/Minimum-1.0
- 使用Android 工具 > 导出未签名的 Android 包
- 将
bundle.manifest
生成的未签名 APK 复制为META-INF/MANIFEST.MF
- 使用您想要的任何证书对 APK 进行签名。在这里,您已准备好捆绑包 APK
- 像往常一样安装捆绑包 APK。需要安装才能解决这些活动。没有这个,活动将无法解决,并且捆绑包将失败
- 让 OSGi 框架加载并启动捆绑 APK(相同的 APK 文件)
要从捆绑包 APK 启动 Activity,请使用以下代码。
// This is the application's context provided by the framework
// Context ctx = ...
Intent intent = new Intent();
String pkgName = YourActivity.class.getPackage().getName();
String clssName = YourActivity.class.getName();
intent.setClassName(pkgName, clssName);
// You may add the NEW_TASK flag
intent.addFlag(Intent.FLAG_ACTIVITY_NEW_TASK);
// Important: do not use startActivity(Context, Class) version because it will fail to resolve the activity
ctx.startActivity(intent);
日志
最初的
在我努力的这一点上,android捆绑了:
- 可以调用android SDK类,只要它们不需要AndroidManifest.xml中的资源或声明,
- 可以访问应用程序的
android.content.Context
,可用于在 OSGi 框架之外启动活动。
捆绑包不能:
- 请求安卓权限,
- 从布局构建活动甚至根本不启动它们,
- 定义静态广播接收器,在 中声明
AndroidManifest.xml
,尽管由代码实例化的接收器应该没问题。
这是我迄今为止所经历的限制,我试图克服这些限制以及我寻求帮助的目标。
我的目标是:
- 支持内部资源,尤其是布局,
- 如果 XML 构建器不可能,则能够通过代码创建和启动内部活动。
到目前为止,我通过试验取得了哪些成就:
- 通过混合构建器、导出未签名的二进制文件并
bundle.manifest
在使用jarsigner
. 结果是 APK 由 OSGi 框架加载并达到已解决的状态,但由于我的激活器类而无法启动java.lang.NoClassDefFoundError
,即使该类是 classes.dex 的一部分并且路径上没有明显错误. 使项目成为具有 android 依赖项的 OSGi 包可以访问激活器,但不能访问 JAR 中的 android 资源。令人费解。 - 验证本指南“可以做”部分中描述的所有功能。
编辑 2013-09-03
我找到了一种启动 android 捆绑包拥有的活动的方法。见相应章节。
编辑 2013-09-10:通用 OSGI 框架容器
几天后,我使 Knopflerfish 程序成为通用的,可以运行我想要的任何 OSGi 框架。例如,我目前正在以相同的方式运行 Knopflerfish 或 Felix。仍然需要特定的框架配置。
这意味着主题不再仅仅是 Knopflerfish,即使所需的程序是由 Knopflerfish 发布的。
2013-09-27:状态和框架整体比较
由于优先级的变化,我不得不把这个项目搁置一段时间。但是,到目前为止,我评估了以下解决方案:
- Knopflerfish OSGi [开源],
- Felix 和 FelixDroid [开源],
- ProSyst mBS SDK(基于 Equinox,商业用途)
总而言之,它们在 GUI 支持方面都没有一个明显的优势:它们都不能以 android 方式处理资产或资源(字符串、布局、图像),但您仍然可以将它们作为 OSGi 资源使用OSGi API,但您将无法像往常一样在 android 中使用它们。
我个人喜欢 Knopflerfish 的管理控制台 servlet,但它的 GUI 支持一无所获。Felix + FelixDroid 为免费的 OSGi 解决方案提供了良好的平衡,而 mBS SDK 支持大量不同的 VM 目标,并定义了一个基于意图的应用程序框架,可能更适合专业开发人员的口味。
虽然 Knopflerfish 和 Felix 的使用方式几乎相同,但 mBS SDK 在许多方面都非常不同。Knopflerfish 和 Felix 是可互换的:我编写了一个容器程序,其中选择 OSGi 框架只是选择不同的手工制作的 JAR 依赖项!
在 GUI 方面,Knopflerfish 几乎一无所获。您需要阅读我的指南才能获得更多支持。FelixDroid 的主要思想是好的,它实际上是在 mBS SDK 中实现的类似的东西,但是没有将实现作为一个包有点浪费。更重要的是,mBS SDK 通过定义一个由特定意图启动的 OSGi 应用程序框架,做得更好。两者都以相同的方式将视图集成到主要活动中。
mBS SDK 的另一个惊人区别是您不需要添加 android 框架依赖项,也不需要在包中为它们添加 Import-Package 指令。在依赖 Knopflerfish 或 Felix 一段时间后肯定会感到不安。它还完全集成在 Eclipse 中,为开发人员提供了许多方便的任务:PC 到目标 OSGi 框架监控(Kf 和 Felix 仅提供目标管理控制台)和快速部署。这些坑本质上是不是免费的,容器应用程序几乎不可能定制。