Chasing an mobile web vision

闯荡在移动互联网的世界中

2006年2月14日 #

移动互联网时代--Android上的一个例子

我们来演示一个获取联系人,并用网页展现出来的简单例子。

 首先,我们在eclipse环境中创建一个Android project,我们的Activity名称是com.example.RIAExample,并且修改界面的layout文件如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation
="vertical"
    android:layout_width
="fill_parent"
    android:layout_height
="fill_parent"
    
>
<WebView android:id="@+id/web"
 android:layout_width
="fill_parent" android:layout_height="fill_parent">
</WebView>
</LinearLayout>

可以看到,界面中仅仅包含一个WebView控件。

 接下来,创建一个简单的java类来描述一个联系人的信息,它包含联系人姓名和号码。

 

package com.example;

import java.util.Vector;

import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;

public class RIAExample extends Activity {
    
    
private WebView web;
    
    
//模拟号码簿
    private Vector<Person> phonebook = new Vector<Person>();
    
/** Called when the activity is first created. */
    @Override
    
public void onCreate(Bundle savedInstanceState) {
        
super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
this.initContacts();
        web 
= (WebView)this.findViewById(R.id.web);
        web.getSettings().setJavaScriptEnabled(
true);//开启javascript设置,否则WebView不执行js脚本
        web.addJavascriptInterface(this"phonebook");//把RIAExample的一个实例添加到js的全局对象window中,
                                                        
//这样就可以使用window.phonebook来调用它的方法
        web.loadUrl("file:///android_asset/phonebook.html");//加载网页
     
    }

    
    
/**
     * 该方法将在js脚本中,通过window.phonebook.getContacts()进行调用
     * 返回的JavaArrayJSWrapper对象可以使得在js中访问Java数组
     * 
@return
     
*/

    
public JavaArrayJSWrapper getContacts(){
        System.out.println(
"fetching contacts data");
        Person[] a 
= new Person[this.phonebook.size()];
        a 
= this.phonebook.toArray(a);
        
return new JavaArrayJSWrapper(a);
    
    }

    
    
/**
     * 初始化电话号码簿
     
*/

    
public void initContacts(){
        Person p 
= new Person();
        p.name 
= "Perter";
        p.phone_number 
= "8888888";
        phonebook.add(p);
        p 
= new Person();
        p.name 
= "Jack";
        p.phone_number 
= "777777";
        phonebook.add(p);
       
    }

    
    
/**
     * 通过window.phonebook.debugout来输出js调试信息。
     * 
@param info
     
*/

    
public void debugout(String info){
        
        System.out.println(info);
    }

}

下面是html文件,它非常简单。
<html>
    
<head>
        
<script type="text/javascript" src="fetchcontacts.js"/>
</head>
<body>
    
<div id = "contacts">
        
<p> this is a demo </p>
    
</div>
</body>
</html>

而主角就是我们的javascript脚本fetchcontacts.js
window.onload= function(){
    window.phonebook.debugout(
"inside js onload");//调用RIAExample.debugout
    var persons = window.phonebook.getContacts();//调用RIAExample.getContacts()
    if(persons){//persons实际上是JavaArrayJSWrapper对象
        window.phonebook.debugout(persons.length() + " of contact entries are fetched");
        
var contactsE = document.getElementById("contacts");
        
var i = 0
        
while(i < persons.length()){//persons.length()调用JavaArrayJSWrapper.length()方法
            
            pnode 
= document.createElement("p");
            tnode 
= document.createTextNode("name : " + persons.get(i).getName() + " number : " + persons.get(i).getNumber());//persons.get(i)获得Person对象,然后在js里面直接调用getName()和getNumber()获取姓名和号码
            pnode.appendChild(tnode);
            contactsE.appendChild(pnode);
            i 
++;
        }

    }
else{
        window.phonebook.debugout(
"persons is undefined");
    }

    
}


 例子很简单,我加了注释希望有助大家理解,其他我就不深入解释了。
我把例子的源代码放上来,你可以下来试一试。

RIADemo

这个例子说明通过WebView.addJavascriptInterface方法,我们可以扩展JavaScript的API,获取Android的数据。这样,JS的粉丝就可以使用Dojo,JQuery,Prototy等这些知名的js框架来搭建android应用程序来展现它们很酷很玄的效果!但是,目前addJavascriptInterface还不够灵活强大,为什么呢?敬请关注第四篇“what next?”


posted @ 2009-03-14 16:16 勤劳的蜜蜂 阅读(5464) | 评论 (6)编辑 收藏

移动互联网时代--忽如一夜春风来,web花开各终端

自从Apple在safari上使用webkit并移植到iphone后,webkit就仿佛是获得了选秀第一名似的,吸引了众多眼球。
其实,我最早听说webkit是在iphone出来前一两年,当时有报道说Nokia正在采用webkit为它下一代的Symbian平台做一个引擎,而且还有专门的社区,但比较封闭,很难进入那个圈子,让人一点感觉没有。不过,至少说明Nokia很早就盯上这一块了。
iphone发布后,Apple似乎就接管webkit了,从此,webkit社区就有了巨大变化。Apple把能开放的东西都放到了webkit社区里面,速度还相当的快(用Apple自己的话说,他们贡献了81%的力量,看看webkit社区的主力开发人员,有多少来自Apple!)现在,我们时不时的可以看到webkit的更新,比如对html5的跟进,css的特效等新功能!其中不得不重点提出的是2008年6月2日,webkit社区发布了高性能javascript引擎SquirrelFish!9月3日google chrome发布,采用了V8 javascript引擎,号称比SquirrelFish还快,两个礼拜后webkit就发布了SquirrelFish Extreme给予正面还击。巨头们牟足了劲争先把javascript引擎油门踩到底!我不想猜测他们卖力的真正原因,但是,我觉得随着移动终端能力加强,javascript大面积攻进终端已经指日可待了,iphone和android已经实现了。
这期间,由于Apple的快速组合拳,Nokia经营的webkit专区很快就倒闭了。但他并没有放弃对webkit的投入,2008年1月28日Nokia宣布收购了Trolltech公司。现在Trolltech lab作为Nokia的代表仍然活跃在webkit社区之中。就前两天,他们还在http://planet.webkit.org/发布了一篇标题极其引人的博文“Creating a Google chat client in 15 minutes”,为QWebView做了一把广告,相信qt的粉丝肯定不会错过。而本月初发布的qt4.5中,明确指出qt加强了对webkit诸如SquirrelFish等新功能的整合。那么就让我们期待一下Nokia的新款机器吧。
到此,我们已经看到Google, Apple, Nokia, Palm等移动终端的领头羊们所采取的行动,然而,不要忘记软件厂商!Adobe就是杰出的代表。作为RIA的强烈倡导者,他也选择把webkit整合到其AIR平台,你可以想象这个webkit+flash的威力有多强大!而Adobe发起的openscreen项目,更是表明了他要在RIA上争夺王位的野心!
好了,不啰嗦那么多了,总之呢,webkit已经成为移动互联网这出大戏的主角了,回归到我在上一篇中提到的主题,让我们来研究一下android.webkit.WebView的功能,来看android上的RIA。
敬请关注下一篇--使用WebView的小例子。

posted @ 2009-03-10 22:39 勤劳的蜜蜂 阅读(2267) | 评论 (0)编辑 收藏

移动互联网时代的终端--暂时遗忘OSGi,让我们去品味一杯android磨出的移动互联网咖啡吧

1年多前,揣着在移动终端推广OSGi的梦想,我离开了原来的公司,来到了一个自认为更利于osgi的地方,在我看来osgi拥有eRCP这样的粉丝,应该会给它在移动终端上提供广阔的舞台。然而半路杀出个程咬金,google android!第一次见到它时,我不相信osgi会输,于是就废寝忘食的研究它,结果我没有进行太多的抵抗,很快就被Android收编了。我得承认它在某些关键的因素,确实比osgi更适合移动终端。对此,我只能感叹google开源的伟大,以及摒弃jcp的雷厉风行!但是,更为重要的是,在移动互联网时代即将到来的时刻,Android为我们打开了一扇方便之门,这到底为何?

通过对Android的(java)源码进行扫描后,我看到了像jsr211,MVM这些老朋友的影子,嗯,我可不想再炒这些旧饭了。还有啥?网上热炒android用了webkit作为其浏览器的引擎,和iphone的safari使用的引擎一样,这个东西应该不错,因为我一直觉得eRCP没有很好的web控件,使得它在移动平台上逊色不少,于是我翻到了android.webkit这个java包,wow! Google给webkit封装了很多java的接口,这真是java开发人员的福气,(听说很多java开发人员对sun的javafx期待度最高的就是一个传说中的JWebView控件,但是不知道出来没,算了,我懒得查证了,因为俺现在不想浪费太多在sun java上,呵呵),不过我觉得这更是广大互联网应用开发人员的福气!想想!用html + css + javascript就能编写android的类似电话簿,日历甚至手机桌面的应用,这难道不让您浮想联翩吗?所以,如果说Android为移动终端进入互联网开了一扇门,那么android.webkit.WebView就是这扇门的金钥匙,而webkit就是通向互联网的康庄大道!

其实android的webview是一个极端复杂的控件,而我个人认为它是可以实现现在热炒的RIA/webos等概念的基石!虽然我进入这个领域不长,但觉得它非常有意思,很有前途,所以我打算写一个系列,分享对它的理解,强烈欢迎各位高手指点!

怎么开始说呢?不知道大家有没有注意,年初Palm公司在CES上palm pre的基于webkit的webos惊艳之秀,十足掉起了大家的胃口,这是我听到第一个冠以webos的移动终端,这是不是意味着目前由palm来唱这个webos的独角戏呢?答案当然不是,其实很多终端厂家以及平台厂商很早就率领大部队兵临webos的城下了,相信很快一场硝烟弥漫的战争就要开始了......

欲知端的,敬请关注移本系列第二篇--忽如一夜春风来,web花开各终端

posted @ 2009-03-09 22:53 勤劳的蜜蜂 阅读(3407) | 评论 (7)编辑 收藏

OSGi介绍(七)bundle和service(续)

接上一篇的例子,为了更具体一点,我们考虑这样的case,
假设房地产开发商construction A采纳了规划公司design A的方案,打算建造公寓类型的房子CityApartment
然后客户A买了一套房子

用ooa方式分析这个case,
我抽象这几个实体:规划公司,图纸类型,开发商,房子,买房人
然后描述这几件事情:开发商选择设计图纸,盖楼然后销售;买房人根据图纸买房并使用房子

下面是我用java语言来简单描述它:

design A 公司

package design.a;
interface Apartment {
..
//方法省略先
}

 

开发商construction A

package construction.a

import design.a.*;//开发商要按照图纸盖楼

class CityApartment implements Apartment {
.
//方法省略先
}


class Construction implements BundleActivator{

}



客户 costumer A

package customer.a

import design.a.*;//客户要按照图纸选择房子

class Customer implements BundleActivator {
.
//方法省略先
}



然后我们把他们分别做成bundle
Bundle A : design A
其manifest中这样描述

BundleSymbolicName: design A
Export
-Package: design.a


Bundle B : construction A
其manifest中这样描述

BundleSymbolicName: construction A
Import
-Package: design.a
Bundle
-Activator: construction.a.Construction


Bundle C : customer C
其manifest中这样描述

BundleSymbolicName: customer C
Import
-Package: design.a
Bundle
-Activator: customer.c.Customer



这样,装入到framework后,framework就会把BundlB和C与BundleA关联起来,正好描述开发商A选择design A的图纸,客户A也不得不选择design A的图纸啦
可是,单从这里,我们看不出来,开发商和客户拿同一份图纸干什么。那我们得必须在BundleB和C的实现里面写点东西来说明。

这里给出开发商construction.a.Construction的伪代码:

class Construction implements BundleActivator {

 
public void start(BundleContext context){
  CityApartment apartment 
= null;
  Hashtable properties 
= null;
  
for(int i = 0; i < 100; i ++){
   properties 
= new Hashtable();
   properties.put(
"price",new Integer(1000 + i*5));//开发商为房子定价
   apartment = new CityApartment();//一套房子盖好
   
//把房子按照公寓注册出去并打广告,等待客户来购买,framework就相当于一个售楼处兼房屋中介
   context.registerService(Apartment.class.getName()/*公寓类型*/,apartment/*房子作为服务对象*/,properties/*与房子相关的附带信息*/);
   
  }

  
//这样开发商一共注册一百套房子
 }

}



而客户的代码可以如下:

class Customer implements BundleActivator {
 
public void start(BundleContext context){
  Apartment apartment 
= null;
  ServiceReference ref 
= context.getServiceReference(Apartment.class.getName,"(price=1050)");//先签署购房合同,而且指明选择Apartment类型,价格为1050的房子。
  apartment = (Apartment)context.getService(ref);//然后买到房子
  
//买房人就可以使用房子apartment对象进行日常生活了
 }

}


这样,我们就很清楚的看出,Design A为Construction A和Customer A提供了共同的Apartment定义,才使得他们有交易的可能。于此同时,Construction A和Customer A之间的耦合是非常松的,因为,如果有另外一个开发商onstruction B加入进来也构造了Apartment的对象,Customer就可以通过改变选择条件,轻易的获得B的房子,而客户本身不关心房子是A还是B盖的,这个是典型的面向对象的多态应用。

总的说来,Bundle在framework的帮助下,使得其他bundle使用其类型定义成为可能。service就是在这些共享的类型定义基础上产生的具体对象,而使用这些service对象的bundle,必然也是对应共享类型的使用者。
这种类型共享,在osgi里面叫做"class space". framework运行时通过关联bundle之间的类型定义,可以构成一个或多个"class space",而某个bundle在framework里面,只能处在一个"class space"里面,不能同时出现在多个"class space"中。
怎么理解这个话呢?请看下一篇,外星人入侵了。

 

posted @ 2007-07-21 22:28 勤劳的蜜蜂 阅读(1702) | 评论 (1)编辑 收藏

OSGi 介绍(七)bundle和service的关系

osgi系列已经发表了有将近2年的时间了,很高兴这期间得到了许多朋友的关注,你们和我的讨论切磋都让我兴奋无比。而过去很长的时间里,由于灵感枯竭外加精力有限,不能给大家分享更多osgi的精彩,实在辜负大家的期望,还请谅解。不过,根据这段时间大家和我私下的讨论,发现很多人都苦恼于分不清楚bundle和service的关系,而我的osgi 5-6又臭又长,而且还非常依赖技术,实在是坑害入门者的必备武器。这个不足,犹如头上方圆半尺盘旋的苍蝇,困扰着我,解释的邮件也不知写多少,估计收效甚微。于是我决定再次出山,写一个惊世骇俗的分析文章,力图让更多读者都能够把这两个疯马牛理清楚。

开讲之前,还是用老办法,给大家举个例子。放心,这个例子一点都不技术,而且我相信你可能比我还清楚。大家都知道房地产商是怎么运作楼盘的吧。据我浅显的认识,他们都会先进行一些图纸上的规划,里面包含小区的整体规模,楼房外观以及广大疾苦民众最为关心和渴求的户型图。据说,很多房地产商在搞定政府某些关键部门(个人意见仅供参考)拿到一块地后,根本不用费心思去自己规划这些自己都一窍不通的东西,只要请一个有资质的第三方公司写写画画,甚至照搬某西方发达国家的某社区概念,冠于中国特色云云,就能使广大民众趋之若鹜,倾囊抢购。ok,你可能受不了我又愤世嫉俗了,这个到底和osgi有啥关系?别着急,天色已经很晚,下次某个时间请继续关注osgi(七)续,其间,请大家先考虑一下,如何用ooa的方式来描述人们如何购房,以及在房子里进行日常生活这样的简单场景。

posted @ 2007-07-16 23:31 勤劳的蜜蜂 阅读(2453) | 评论 (2)编辑 收藏

OSGi on mobile phone !

如果你一直关注osgi在embedded的发展,并且还能到旧金山参加正在举行的java one,那我真是羡慕死你了.

Nokia在她的N800上demo运行了osgi(此消息来源于david beers对BJ在osgi alliance blog上的评论,关于demo可以看这个link http://thehereweb.googlepages.com/)
同时美国运行商Sprint也宣布采用osgi的手机平台将会在年底面世.多么值得期待的事情!

posted @ 2007-05-14 10:11 勤劳的蜜蜂 阅读(1773) | 评论 (0)编辑 收藏

framework implementation updated

瞎整了半年多,克服了重重困难,终于把原来framework做了更新,如果有兴趣,还可以按照原来的Link下载。
framework下载后,解压并运行startframework.bat就可以启动framework了。
目前该framework只支持在内存中存储bundle.

另外,多增加了一个管理bundle,它为framework提供了简单的图形化操作界面,可以在这里下载。

http://www.blogjava.net/Files/Ferrari4000/bundlemanagement.zip

下载后,最好先把名字改为bundlemanagement.jar
然后可以这样安装,假设该文件下载到d:\bundles下,则可以在framework的shell下输入
in file:d:/bundles/bundlemanagement.jar
安装该bundle,安装成功后输入stt 1(注意:1是数字一),启动该bundle就可以出现图形操作界面了。

图形界面提供bundle的安装(只支持本地安装),启动,停止,升级和删除等简单功能。
其他功能会在后面陆续增加。目前这个bundle只能运行在j2se环境下。

给的源代码没有很好的build文件,等我有时间了,再写一个。

感谢毕嘉兄弟的支持,他帮助设计了bundle存储模块以及实现了bundle在内存存储的第一版。

有啥问题,可以直接给我发邮件,jerrylee.li@gmail.com

posted @ 2006-09-16 23:40 勤劳的蜜蜂 阅读(1545) | 评论 (0)编辑 收藏

OSGi的曙光?

前两天,看到Peter的blog里说,由ibm牵头发起了jsr291(http://www.jcp.org/en/jsr/detail?id=291),
要把osgi的core应用到目前的j2se上,以填补jsr277发布前的需求空白。

osgi和277的pk开始了!我投osgi一票,呵呵,你买谁?

posted @ 2006-03-03 13:51 勤劳的蜜蜂 阅读(1720) | 评论 (1)编辑 收藏

OSGi Alliance开设面向公众的邮件列表

继开设Blog后(http://www.osgi.org/blog/index.html),昨天OSGi Alliance又宣布开设一个public的mail list,每个对OSGi感兴趣的人都可以加入到这个列表中,通过这个列表可以讯问OSGi的相关的各种问题以及订阅列表的邮件。
请到http://bundles.osgi.org/mailman/listinfo/osgi-dev这里注册。

下面是BJ Hargrave的代表OSGi Alliance发的announcement


Hello,

As part of the OSGi evangelism work, I would like to announce that OSGi now has a new public mail list for OSGi technical questions and discussion. This mail list is a public list and is open to anyone to participate. This new list is mainly for non-members to discuss OSGi technology and ask question about the technology. ......

The new mail list address is:

osgi-dev@bundles.osgi.org

You can subscribe to the list here:
http://bundles.osgi.org/mailman/listinfo/osgi-dev or by sending an e-mail to osgi-dev-subscribe@bundles.osgi.org.

So please go ahead and subscribe to the new osgi-dev mail list. Your participation there can help educate others about the OSGi technology.

BJ Hargrave
Senior Technical Staff Member, IBM
OSGi Fellow and CTO of the OSGi Alliance

posted @ 2006-02-16 10:20 勤劳的蜜蜂 阅读(1279) | 评论 (0)编辑 收藏

framework implementation

这里是我目前的成果。
http://www.blogjava.net/Files/Ferrari4000/framework.zip包含了framework的jar文件。
解压后,直接运行.bat文件就可以了。这个framework实现了r4core的大部分api(除一些支持local和安全的外,secrurity admin和conditional permission这两个服务也没有实现,url service也没有实现),输入help可以看到可以运行的命令(不过全是英文的,还是我写的,将就一下吧)。

http://www.blogjava.net/Files/Ferrari4000/src.zip包含所有源码。代码有点乱,hoho

要成功编译代码,您还需要下载这个http://www.blogjava.net/Files/Ferrari4000/osgi.rar
解压并后得到4个jar,并把他们放入到build path中,import顺序保证它们优先于jre就可以了。

我打算重新编写了。把framework的实现重新规划一下,然后实现core的所有内容,进而再实现cmpn的所有服务。
如果你对开发osgi framework感兴趣,一起来吧!

给我发信:jerrylee.li@gmail.com

posted @ 2006-02-15 09:15 勤劳的蜜蜂 阅读(1780) | 评论 (8)编辑 收藏

OSGi介绍(六)OSGi的service

在给出采用service方式实现的“扶贫助手”之前,我们稍微回顾一下上一篇的成果。
在(五)中,我们看到程序被分成多个bundle后,程序的模块程度得到提高,而控制模块间的耦合度由Import-Package和Export-Package来控制,相对比较灵活。另一方面程序的更新和升级的粒度变小了。谁都知道只更新部分要比全部更新强,尤其当更新发生在一些需要建立昂贵的连接时,细粒度会节省不少花销。除了这些,我们看不到其他新鲜的东西。说白了,也就是挖空心思想一些design pattern来划分程序模块。
 
好了,马上就新鲜了。下面你会看到通过采用service方式来改造(五)中的程序,gui bundle在某些情况下不用重新启动,就能直接某些适应需求的变更!
先给出model bundle的代码,该bundle包含两个java package,分别是:
com.bajie.test.family.model
com.bajie.test.family.model.impl
在com.bajie.test.family.model这个package中包含如下的class和interface:
package com.bajie.test.family.model;
import java.util.List;
import javax.swing.table.AbstractTableModel;
public abstract class FamilyInfoDatabase extends AbstractTableModel{
   
    public abstract void sort(SortingFamilyInfoCriteria sortField) throws IllegalArgumentException;
   
    public abstract void addEntry(List columns, List values) throws IllegalArgumentException;
    public abstract void deleteEntry(String familyName);
    public abstract void update(String familyName,List columns, List values)throws IllegalArgumentException;
}

这是database的model,与(五)定义成interface不同,我们直接让它继承了AbstractTableModel,这是因为我们希望当数据或显示需求变化时,gui上的JTable能获得通知,并显示更新的结果。SortingFamilyInfoCriteria这个类型下文会给出说明。
 
package com.bajie.test.family.model;
public class FamilyInfoEntry {
    private String familyName;
    private int population;
    private int incomePerYear;
   
    public FamilyInfoEntry(String familyName,int population,int income){
        this.familyName = familyName;
        this.population = population;
        this.incomePerYear = income;
    }
   
    public String getFamilyName() {
        return familyName;
    }
    public int getIncomePerYear() {
        return incomePerYear;
    }
    public int getPopulation() {
        return population;
    }
}

这个类的结构和在(五)中完全一样,用来纪录一条家庭信息。唯一不同的是,在(五)中我们把它放入了实现(.impl)package中,在后面给出bundle的manifest文件时,我将解释为什么要这样改。
 
package com.bajie.test.family.model;
public interface FamilyInfoColumn {
    public Object getColumnValue(FamilyInfoEntry entry);
   
    public String getColumnName();
}
这个类用来描述table中的某个列。
package com.bajie.test.family.model;
import java.util.Comparator;
public interface SortingFamilyInfoCriteria extends Comparator{
    public String getSortFieldString();
}
这个类将用于对家庭纪录按某一列的值进行排序。
在com.bajie.test.family.model.impl这个package中包含上面抽象类和interface的实现:
package com.bajie.test.family.model.impl;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import com.bajie.test.family.model.FamilyInfoColumn;
import com.bajie.test.family.model.FamilyInfoDatabase;
import com.bajie.test.family.model.FamilyInfoEntry;
import com.bajie.test.family.model.SortingFamilyInfoCriteria;
public class FamilyDatabase extends FamilyInfoDatabase implements  BundleActivator,
        ServiceListener {
    private LinkedList familyEntryList = new LinkedList();
    private Object[] sortedValues = null;
    private LinkedList columns = new LinkedList();
    private BundleContext context;
    public int getColumnCount() {
        return this.columns.size();
    }
    public String getColumnName(int index) {
        return ((FamilyInfoColumn)columns.get(index)).getColumnName();
    }
   
    public Object getValueAt(int row, int column) {
        FamilyInfoEntry entry = (FamilyInfoEntry) this.sortedValues[row];
        if(column >= this.familyEntryList.size()){
            return null;
        }
        return ((FamilyInfoColumn) this.columns.get(column))
                .getColumnValue(entry);
    }
    public int getRowCount() {
        return this.familyEntryList.size();
    }
    public void addEntry(List columns, List values)
            throws IllegalArgumentException {
    }
    public void deleteEntry(String familyName) {
    }
    public void update(String familyName, List columns, List values)
            throws IllegalArgumentException {
    }
    public void sort(SortingFamilyInfoCriteria sortField) {
        Arrays.sort(this.sortedValues, sortField);
    }
    public void start(BundleContext context) throws Exception {
        this.context = context;
        this.familyEntryList.add(new FamilyInfoEntry("Zhang", 3, 1200));
        this.familyEntryList.add(new FamilyInfoEntry("Li", 6, 1800));
        this.familyEntryList.add(new FamilyInfoEntry("Liu", 5, 1500));
        this.familyEntryList.add(new FamilyInfoEntry("Wang", 4, 1300));
       
        this.sortedValues = this.familyEntryList.toArray();
 //向framework注册一个类型为FamilyInfoDatabase的服务
        context.registerService(FamilyInfoDatabase.class.getName(),this,null);
 //向framework注册三个服务,每个服务的类型既为FamilyInfoColumn,也是SortingFamilyInfoCriteria
        String[] clazzes = new String[] {FamilyInfoColumn.class.getName(),SortingFamilyInfoCriteria.class.getName()};
        context.registerService(clazzes,new FamilyNameColumn(),null);
        context.registerService(clazzes,new FamilyPopulationColumn(),null);
        context.registerService(clazzes,new FamilyIncomeColumn(),null);
       
 //向framework查找所有注册类型为FamilyInfoColumn的服务
 //先获得服务的引用
        ServiceReference[] columnRefs = context.getServiceReferences(
                FamilyInfoColumn.class.getName(), null);
        FamilyInfoColumn column = null;
        for (int i = 0; i < columnRefs.length; i++) {
            System.out.println(i + ":" + ((String[])(columnRefs[i].getProperty(Constants.OBJECTCLASS)))[0]);
     //通过引用获得具体的服务对象,每一个对象都将转化成gui中table的一列
            column = (FamilyInfoColumn) context.getService(columnRefs[i]);
            if (column != null) {
                this.columns.add(column);
            }else{
                System.out.println("null service object.");
            }
        }

 //注册服务侦听器,该侦听器专门侦听FamilyInfoColumn服务对象的动态(主要是增加和删除)
        context.addServiceListener(this,"(" + Constants.OBJECTCLASS + "="
                + FamilyInfoColumn.class.getName() + ")");
    }
    public void stop(BundleContext context) throws Exception {
    }
    public void serviceChanged(ServiceEvent event) {
        switch (event.getType()) {
        case ServiceEvent.MODIFIED:
            return;
        case ServiceEvent.REGISTERED://表明有新的列产生了。
            ServiceReference ref = event.getServiceReference();
            Object service = this.context.getService(ref);
            this.columns.add(service);
            this.fireTableStructureChanged();//通知gui,表结构发生变化
            return;
        case ServiceEvent.UNREGISTERING://表明有些列将被删除
            ref = event.getServiceReference();
            service = this.context.getService(ref);
            this.columns.remove(service);
            this.fireTableStructureChanged();//通知gui,表结构发生变化
            return;
        }
    }

    //这个类定义一个“Family Name”这个列,以及如何按这个列的值进行排序
    class FamilyNameColumn implements FamilyInfoColumn,SortingFamilyInfoCriteria {
        private static final String COLUMNNAME = "Family Name";
       
        public Object getColumnValue(FamilyInfoEntry entry) {
            return entry.getFamilyName();
        }
       
       
        public String getColumnName() {
            return FamilyNameColumn.COLUMNNAME;
        }
       
        public String getSortFieldString() {
            return FamilyNameColumn.COLUMNNAME;
        }
        public int compare(Object obj1, Object obj2) {
            if (obj1 == obj2) {
                return 0;
            }
            FamilyInfoEntry en1 = (FamilyInfoEntry)obj1;
            FamilyInfoEntry en2 = (FamilyInfoEntry)obj2;
           
            return en1.getFamilyName().compareTo(en2.getFamilyName());
        }
       
    }
    //这个类定义一个“Family Population”这个列,以及如何按这个列的值进行排序
    class FamilyPopulationColumn implements FamilyInfoColumn, SortingFamilyInfoCriteria {
        private static final String COLUMNNAME = "Family Population";
        public Object getColumnValue(FamilyInfoEntry entry) {
            return new Integer(entry.getPopulation());
        }
        public String getColumnName() {
            return FamilyPopulationColumn.COLUMNNAME;
        }
       
        public String getSortFieldString() {
            return FamilyPopulationColumn.COLUMNNAME;
        }
       
        public int compare(Object obj1, Object obj2) {
            if (obj1 == obj2) {
                return 0;
            }
            FamilyInfoEntry en1 = (FamilyInfoEntry)obj1;
            FamilyInfoEntry en2 = (FamilyInfoEntry)obj2;
           
            return en1.getPopulation() - en2.getPopulation();
        }
    }
   
    //这个类定义一个“Family Income”这个列,以及如何按这个列的值进行排序
    class FamilyIncomeColumn implements FamilyInfoColumn, SortingFamilyInfoCriteria {
        private static final String COLUMNNAME = "Family Income";
        public Object getColumnValue(FamilyInfoEntry entry) {
            return new Integer(entry.getIncomePerYear());
        }
        public String getColumnName() {
            return FamilyIncomeColumn.COLUMNNAME;
        }
       
       
        public String getSortFieldString() {
            return FamilyIncomeColumn.COLUMNNAME;
        }
        public int compare(Object obj1, Object obj2) {
            if (obj1 == obj2) {
                return 0;
            }
            FamilyInfoEntry en1 = (FamilyInfoEntry)obj1;
            FamilyInfoEntry en2 = (FamilyInfoEntry)obj2;
           
            return en1.getIncomePerYear() - en2.getIncomePerYear();
        }
       
    }
}
 
与(五)相比,最大的不同就是表结构的“列”是通过查找所有类型为FamilyInfoColumn的服务对象而组成的。而通过framework提供的服务侦听机制(即实现ServiceListener接口并注册到framework中),bundle能够获得该类服务对象的动态事件通知,如果该事件是新服务注册,则添加一个显示列,如果是服务被注销,则删除对应的显示列。
 
下面是bundle的manifest文件
Manifest-Version: 1.0
Bundle-SymbolicName: com.bajie.test.family.model
Bundle-Name: family model
Bundle-Version: 1.0
Bundle-Vendor: LiMing
Bundle-Activator: com.bajie.test.family.model.impl.FamilyDatabase
Import-Package: org.osgi.framework;version=1.3,com.bajie.test.family.model
Export-Package: com.bajie.test.family.model;version=1.0
从中我们看到com.bajie.test.family.model这个package被export出来,这样其他bundle就能够import这个package,并根据FamilyInfoEntry所提供的基本内容提供一些额外的处理结果,从而产生新列(FamilyInfoColumn)以及排序方法(SortingFamilyInfoCriteria),比如家庭人均年收入。

下面来看看gui bundle,它只包含一个package
package com.bajie.test.family.gui;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Hashtable;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import com.bajie.test.family.model.FamilyInfoDatabase;
import com.bajie.test.family.model.SortingFamilyInfoCriteria;
public class FamilyInfoGui implements BundleActivator, ActionListener,
        ItemListener, ServiceListener {
    private JFrame mainFrame;
    private JPanel contentPanel;
    private JTable familiesTable;
    private JScrollPane familiesTableScrollPane;
    private JPanel sortedByPanel = new JPanel(new GridLayout(1, 2));
    private JLabel sortedByLabel = new JLabel("Sorted By: ");
    private JComboBox sortedByList = null;
    private JPanel commandPanel = new JPanel(new GridLayout(1, 3));
    private JButton addEntry = new JButton("Add");
    private JButton deleteEntry = new JButton("Delete");
    private JButton updateEntry = new JButton("Update");
    private Hashtable sortingFields = new Hashtable();
    private BundleContext context;
    FamilyInfoDatabase database = null;
    public void start(BundleContext context) throws Exception {
        this.context = context;
        //查找所有注册类型为FamilyInfoDatabase的服务对象。在我们这个例子,它是由上面给出的model bundle注册的
        ServiceReference databaseServiceRef = context
                .getServiceReference(FamilyInfoDatabase.class.getName());
        if (databaseServiceRef == null) {
            System.out.println("No database service is registered.");
            return;
        }
 //这个服务对象将成为JTable的数据model
        this.database = (FamilyInfoDatabase) context
                .getService(databaseServiceRef);
        if (this.database == null) {
            System.out.println("Can not get database object");
            return;
        }
        //查找所有注册类型为SortingFamilyInfoCriteria的服务对象。
        ServiceReference[] sortingCriteria = context.getServiceReferences(
                SortingFamilyInfoCriteria.class.getName(), null);
        sortedByList = new JComboBox();
        SortingFamilyInfoCriteria criterion = null;
        if (sortingCriteria != null) {
            for (int i = 0; i < sortingCriteria.length; i++) {
                criterion = (SortingFamilyInfoCriteria) context
                        .getService(sortingCriteria[i]);
                if (criterion != null) {
      //每个服务对象将对应一种排序方法,并加入到下拉列表中
                    sortedByList.addItem(criterion.getSortFieldString());
                    this.sortingFields.put(criterion.getSortFieldString(),
                            criterion);
                }
            }
        }
 //注册服务侦听器,该侦听器专门侦听SortingFamilyInfoCriteria服务对象的动态(主要是增加和删除)
        context.addServiceListener(this, "(" + Constants.OBJECTCLASS + "="
                + SortingFamilyInfoCriteria.class.getName() + ")");
        sortedByList.addItemListener(FamilyInfoGui.this);
        //construct gui
        Runnable r = new Runnable() {
            public void run() {
                contentPanel = new JPanel();
                familiesTableScrollPane = new JScrollPane();
  //获得的FamilyInfoDatabase对象成为gui中JTable的model
                familiesTable = new JTable(database);
                familiesTableScrollPane.setViewportView(familiesTable);
                sortedByPanel.add(sortedByLabel);
                sortedByPanel.add(sortedByList);
                commandPanel.add(addEntry);
                commandPanel.add(deleteEntry);
                commandPanel.add(updateEntry);
                contentPanel.add(sortedByPanel, BorderLayout.NORTH);
                contentPanel.add(familiesTableScrollPane, BorderLayout.CENTER);
                contentPanel.add(commandPanel, BorderLayout.SOUTH);
                mainFrame = new JFrame();
                mainFrame.setContentPane(contentPanel);
                mainFrame.setSize(new Dimension(500, 600));
                mainFrame.show();
            }
        };
        Thread t = new Thread(r);
        t.start();
    }
    public void stop(BundleContext context) throws Exception {
        if (this.mainFrame != null)
            this.mainFrame.dispose();
    }
    public void actionPerformed(ActionEvent event) {
    }
    public void itemStateChanged(ItemEvent event) {
        if (event.getSource() == this.sortedByList) {
            SortingFamilyInfoCriteria criterion = (SortingFamilyInfoCriteria) this.sortingFields
                    .get(event.getItem());
            if (criterion == null)
                return;
            this.database.sort(criterion);
            this.familiesTable.repaint();
        }
    }
    public void serviceChanged(ServiceEvent event) {
        switch (event.getType()) {
        case ServiceEvent.MODIFIED:
            return;
        case ServiceEvent.REGISTERED://有新的排序方法注册到framework当中
            ServiceReference ref = event.getServiceReference();
            SortingFamilyInfoCriteria criterion = (SortingFamilyInfoCriteria) this.context
                    .getService(ref);
            if (criterion != null) {
  //把新的排序方法加入到下拉列表中
                sortedByList.addItem(criterion.getSortFieldString());
                this.sortingFields.put(criterion.getSortFieldString(),
                        criterion);
            }
            return;
        case ServiceEvent.UNREGISTERING://一个现有的排序方法将被从framework被取消
            ref = event.getServiceReference();
            criterion = (SortingFamilyInfoCriteria) this.context
                    .getService(ref);
            if (criterion != null) {
  //把该排序方法从下拉列表中删除
                sortedByList.removeItem(criterion.getSortFieldString());
                this.sortingFields.remove(criterion);
            }
            return;
        }
    }
}
 
与(五)相比不同的地方是,这个gui的table model以及排序的方法,都是通过查询service对象获得。
 
manifest文件如下:
Manifest-Version: 1.0
Bundle-SymbolicName: com.bajie.test.family.gui
Bundle-Name: family gui
Bundle-Version: 1.0
Bundle-Vendor: LiMing
Bundle-Activator: com.bajie.test.family.gui.FamilyInfoGui
Import-Package: org.osgi.framework;version=1.3,com.bajie.test.family.model
 
然后我们生成bundle的jar文件。分别为familymodel.jar和familygui.jar,之后我们用“in”命令把两个bundle装入framework。
接着我们先启动model bundle,然后再启动gui bundle,我们会看到JTable中有3列,而排序方法列表中也有3个选项,完全和程序的逻辑符合。
 
接下来,我们假设客户需要添加显示每个家庭的人均年收入并按其排列纪录。要满足这个需求,我们可以参考在(五)中做法,就是在model bundle里面再添加一个同时实现了FamilyInfoColumn和SortingFamilyInfoCriteria的类,并在bundle的启动中作为服务注册到framework中?不过这样就得更新model bundle然后调用rfr命令来刷新。为什么不再装一个补丁bundle,在这个bundle中包含了同时实现FamilyInfoColumn和SortingFamilyInfoCriteria的类,并在这个新bunle启动时注册产生该类的新对象作为服务注册到framework中,这样gui和model bundle都能侦听到该新服务的到来(他们都实现了服务侦听接口ServiceListener),gui上马上就能有所体现。

这个新bundle的代码如下:
package com.bajie.test.family.model.impladd;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import com.bajie.test.family.model.FamilyInfoColumn;
import com.bajie.test.family.model.FamilyInfoEntry;
import com.bajie.test.family.model.SortingFamilyInfoCriteria;
public class FamilyIncomePerPerson implements BundleActivator {
    public void start(BundleContext context) throws Exception {
 //注册一个新的服务,服务的类型既为FamilyInfoColumn,也是SortingFamilyInfoCriteria
        String[] clazzes = new String[] {FamilyInfoColumn.class.getName(),SortingFamilyInfoCriteria.class.getName()};
        context.registerService(clazzes,new FamilyIncomePerPersonColumn(),null);
       
    }
    public void stop(BundleContext context) throws Exception {
    }
    //这个类实现了“Income Per Person”这个列以及按该列排序的方法。
    class FamilyIncomePerPersonColumn implements FamilyInfoColumn,SortingFamilyInfoCriteria {
        private static final String COLUMNNAME = "Income Per Person";
       
        public Object getColumnValue(FamilyInfoEntry entry) {
            return new Integer(entry.getIncomePerYear()/entry.getPopulation());
        }
       
       
        public String getColumnName() {
            return FamilyIncomePerPersonColumn.COLUMNNAME;
        }
       
        public String getSortFieldString() {
            return FamilyIncomePerPersonColumn.COLUMNNAME;
        }
        public int compare(Object obj1, Object obj2) {
            if (obj1 == obj2) {
                return 0;
            }
            FamilyInfoEntry en1 = (FamilyInfoEntry)obj1;
            FamilyInfoEntry en2 = (FamilyInfoEntry)obj2;
           
            return en1.getIncomePerYear()/en1.getPopulation() - en2.getIncomePerYear()/en2.getPopulation();
        }
       
    }
}
 
manifest文件如下:
Manifest-Version: 1.0
Bundle-SymbolicName: com.bajie.test.family.modeladd
Bundle-Name: family model add
Bundle-Version: 1.0
Bundle-Vendor: LiMing
Bundle-Activator: com.bajie.test.family.model.impladd.FamilyIncomePerPerson
Import-Package: org.osgi.framework;version=1.3,com.bajie.test.family.model
 
打包安装到framework后,启动该bundle,我们就会在gui上看到新的列已经被添加,而且排序列表中增加了一个新的排序选项。
这个结果,完全符合需求的意图。
如果我们用stp命令停止这个bundle,我们在gui上就会发现,新列消失,而且排序列表中对应选项也没有了。这就是service带来的动态效果。不过,如果我们的model发生了一些实质的变化,比如FamilyInfoEntry需要添加一个“地址”列,那么model bundle就要更新,进而gui bundle以及使用到这个类型的bundle都需要通过rfr命令刷新。
 
好了,对扶贫助手的分析就此打住,我们总结一下,通过程序可以看到注册服务一点都不复杂。最简单的情况我们只需要提供一个java类型名称,以及实现这个类型的一个java对象就可以了,
不需要提供复杂的类型描述,比如xml描述文件。而使用服务的bundle通过类型名称就轻而易举的查找到相关的服务对象。
 
到此,osig介绍系列就要结束了,只希望这个系列能够把你引入到osgi的门口,其后面的精彩世界就看你的兴趣了。
就我个人的关注和理解,今年是osgi很重要的一年。JSR249今年应该投票,如果osgi入选,那么osgi将成为高端手机中java体系结构的重要组成部分。
在汽车领域,siemensVDO已经推出了基于osgi的解决方案,听说已经配备在BMW serials 5里面了。应该还会有更多的应用......
 
如果你是osgi的粉丝,欢迎你来信jerrylee.li@gmail.com拍砖交流。

posted @ 2006-02-14 16:08 勤劳的蜜蜂 阅读(5920) | 评论 (13)编辑 收藏

OSGi介绍(五)两个bundle

     摘要: (四)中提到的直接型改造法实际上和一个传统的java应用程序没有区别。因此客户的需求发生变化,通常是牵一发而动全身。那么我们现在就看看如果在osgi framework中,用多个bundle来实现的效果吧。 我的想法是用两个bundle来配合实现“扶贫助手”的功能。一个bundle专门负责录入和显示纪录,一个bundle专门负责纪录的数据结构和对数据的处理,用时下时髦的说法就是使用了mvc,只是...  阅读全文

posted @ 2006-02-14 16:02 勤劳的蜜蜂 阅读(4381) | 评论 (3)编辑 收藏

OSGi介绍(四)第一个bundle

先给出“扶贫助手”的第一种改造,我称之为“直接型”,请看:

package aa.bb.cc;
//需要import osgi的核心package
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
//实现了BundleActivator
public class FamilyInfo implements BundleActivator {
 
private String familyName;
 
private int population;
 
private int incomePerYear;
 省略了getter和setter方法 
 
public String toString() {
  
  
return "Family: " + this.familyName + ", population: " + this.population + ", income: " + this.incomePerYear;
 }

 
 
public int getIncomePerMember(){
  
return (int)(this.incomePerYear/this.population);
 }

 
public static void sortByIncomePerYear(FamilyInfo[] families){
  FamilyInfo temp 
= null;
  
for(int i = 0; i < families.length -1; i ++){
   
for(int j = i + 1; j < families.length; j ++){
    
    
if(families[i].getIncomePerYear() > families[j].getIncomePerYear()){
     temp 
= families[i];
     families[i] 
= families[j];
     families[j] 
= temp;
    }

   }

  }

  
 }

 
public static void sortByIncomePerMember(FamilyInfo[] families){
  FamilyInfo temp 
= null;
  
for(int i = 0; i < families.length -1; i ++){
   
for(int j = i + 1; j < families.length; j ++){
    
    
if(families[i].getIncomePerMember() > families[j].getIncomePerMember()){
     temp 
= families[i];
     families[i] 
= families[j];
     families[j] 
= temp;
    }

   }

  }

  
  
 }

 
//在framework每次启动该bundle的时候该方法会被framework调用执行。
 public void start(BundleContext context) throws Exception {
  FamilyInfo[] families 
= new FamilyInfo[3];
  families[
0= new FamilyInfo();
  families[
0].setFamilyName("Zhang");
  families[
0].setPopulation(3);
  families[
0].setIncomePerYear(1200);
  families[
1= new FamilyInfo();
  families[
1].setFamilyName("Li");
  families[
1].setPopulation(6);
  families[
1].setIncomePerYear(1800);
  families[
2= new FamilyInfo();
  families[
2].setFamilyName("Liu");
  families[
2].setPopulation(4);
  families[
2].setIncomePerYear(1500);
  FamilyInfo.sortByIncomePerYear(families);
  
for(int i = 0; i < families.length; i ++){
   System.out.println(families[i].toString());
  }

  FamilyInfo.sortByIncomePerMember(families);
  
for(int i = 0; i < families.length; i ++){
   System.out.println(families[i].toString());
  }

 }

 
//在framework停止该bundle时,该方法将被framework调用
 public void stop(BundleContext context) throws Exception {
 }

}


看到代码的区别了吗?我在不同之处都标注了注释。其实,从说白了,就是实现了org.osgi.framework.BundleActivator这个接口。
当然,细心的话,你会发现这个bundle没有public static void main(String[] args)方法了。那么它怎么被启动呢?这个就是bundle的奥秘所在。不过,如果你了解java的class loading机制以及reflection技术,你立马会明白这个bundle的运行机制。这两项技术广泛应用于j2ee(对吧?我得承认,j2ee的经验不多,呵呵)以及java的plugin机制。
简单说来,java.lang.Class这个类有一个方法:
public Object newInstance()throws InstantiationException,IllegalAccessException
针对上面的“扶贫助手”bundle而言,framework只要通过ClassLoader找到aa.bb.cc.FamilyInfo.class并加载后,就可以通过newInstance()方法创建一个BundleActivator的实例,然后调用public void start(BundleContext context)方法,就完成了启动bundle的动作了。之后,调用public
void stop(BundleContext context)方法来停止bundle
如果你接着问,framework怎么知道这个bundle里面的BundleActivator是哪个类呢?嗯,问到点子上了。这就涉及到下面我们要讲的bundle的部署了。在上一篇给出的bundle定义中指出,Jar文件是bundle的唯一格式,也就是说,我们要运行bundle,必须把代码打成jar文件。而jar文件可以带有manifest文件,这个文件对bundle是不可缺少的。OSGi规范里面,通过定义一系列适用于bundle的manifest关键字(bundle manifest header)来扩展manifest文件。
比如,开发人员在manifest中添加下面一行:
Bundle-Activator: aa.bb.cc.FamilyInfo
这样,在bundle被部署到framework后,framework就可以通过读取manifest的关键字来获得BundleActivator的具体实现类名,并通过reflection机制产生BundleActivator的实例。
这里就给出扶贫助手的manifest的一个例子:

Manifest-Version: 1.0  
Bundle-SymbolicName: aa.bb.cc.family //osgi specification 4强制要求的关键字,每个bundle都必须有唯一的symbolic name
Bundle-Name: Family Info Manager        //bundle的名称
Bundle-Version: 
1.0   //bundle的版本号
Bundle-Activator: aa.bb.cc.FamilyInfo   //指明BundleActivator的实现类名
Import-Package: org.osgi.framework
;version=1.3   //列出该bundle需要从其他bundle所引入的
                                                                     //package(s)(提供该package的bundle必须在其
                                                                     //manifest中有Export-Package: 
                                                                     //org.osgi.framework
;version=1.3)

然后我们用jdk自带的jar工具,来生成bundle jar文件。这样,第一个bundle就完成了,您可以下载一个开源的framework安装这个bundle试一试。在framework上尝试对该bundle的启动和停止,输出的结果应该和原先的java application是一样的,然后您还可以在那个start(context)的方法中,再增加一条记录,重新打包,然后通过framework的update功能,就能够在不重新启动framework的情况下升级该bundle,我就暂时偷懒不针对具体framework来给出操作的方法了,先给您自己先摸索了(当然您也可以偷懒,因为后面我会结合具体framework深入讲述的)。
好了,说完代码的改造,再看看改造所带来的程序设计结构变化:那~~~就~~~~是~~~~没变化!因此我把这种原封不动的改造方法称为“直接型”,用这种直接法,我们可以轻易的把一个java应用程序改造成bundle。而这种改造目前能看到的好处就是bundle的“热”升级。那怎样能更漂亮些呢?在下一篇中,我会进一步改造这个扶贫助手成为两个bundle,看看bundle的合作将会带来怎样的精彩效果

posted @ 2006-02-14 15:46 勤劳的蜜蜂 阅读(5503) | 评论 (4)编辑 收藏

OSGi介绍(三)OSGi service platform的体系结构

先让我们来看看OSGi service platform的体系结构。另外要说明的是,我在后面的文章中,将采用framework来代替OSGi service platfrom,这样比较简便。
下面这张图来自OSGi Alliance的主页(http://www.osgi.org/
 
OSGi Service Platform Architecture

层次很分明吧。放到我们假想的案例中,OS&Hardware可以对应为PDA的硬件和操作系统,您可以想象它是Intel xscacle + Microsoft window mobile,或者是Arm + embedded Linux
而Execution Environment当然是我们上次提到的CVM + CDC + FP + PP,有这个jvm的配置运行framework就绰绰有余了。而再往上,就是我们要重点学习和分析的OSGi framework了。而Modules, Life Cycle, Service Registry, Services和Security是从不同的5个角度来划分framework所具备的功能,后面我将会从除了Security外的四个方面分别结合我们的假设场景来分析。而体系结构的最上层是符合OSGi framework接口标准的应用程序,也就是OSGi世界中有名的“bundle”。

下面来看看OSGi规范是如何定义一个bundle的。在r4规范的第27页中大致这样描述到:Framework定义了模块(modularization)的单元,叫作bundle。Bundle实际就是一个具有jar(Java ARchive)格式的文件,其中包含了java的class文件和其他资源文件(比如图标,配置文件等等)。Bundle可以在自己的manifest文件中说明自己能够提供哪些java包,其他bundle如果在自己的manifest文件中指定了它需要这个包,那他们之间就可能产生java包的依赖关系,这样多个bundle之间就可以共享java包。值得注意的是,bundle是能够在framework中部署的唯一格式。下面给出原文的描述:
A bundle is a JAR file that:
? Contains the resources necessary to provide some functionality. These resources may be class files for the Java programming language, as well as other data such as HTML files, help files, icons, and so on. A bundle JAR file can also embed additional JAR files that are available as resources and classes. This is however not recursive.
? Contains a manifest file describing the contents of the JAR file and providing information about the bundle. This file uses headers to specify information that the Framework needs to install correctly and activate a bundle. For example, it states dependencies on other resources, such as Java packages, that must be available to the bundle before it can run.
? Can contain optional documentation in the OSGI-OPT directory of the JAR file or one of its sub-directories. Any information in this directory is optional. For example, the OSGI-OPT directory is useful to store the source code of a bundle. Management systems may remove this information to save storage space in the OSGi Service Platform.

framework的modules这一方面功能将主要负责bundle的安装部署,更新和卸载,以及bundle在设备的物理存储(如果有的话)。在这个层次,每个bundle都是独立的,它的安装,升级和卸载完全不依赖任何其他bundle,这点framework提供了强大的隔离性。Life Cycle专门负责对bundle的解析(比如关联两个有相互依赖关系的bundle),启动(相当于运行应用程序)和停止(相当于停止应用程序)。这个层次中,bundle间的逻辑关系被创建起来,这些关系能否成功的创建,将会直接影响bundle的成功解析和启动。Service Registry可以认为是一个数据库,bundle启动后,可以向这个数据库注册它动态提供的服务。只要bundle不被停止,且bundle不主动撤销注册的服务,这个服务将一直保存在这个数据库中供其它bundle来查询和使用。而Services就是由bundle运行时提供的具体服务对象,这些服务对象的存在,使得framework具有极其动态的特征,并为framework运行时提供灵活强大的功能。
另外,根据OSGi Alliance的说法,OSGi的运行平台包括了j2me(kvm + cldc + midp, cvm + cdc+fp), j2se, j2ee。不过,我个人还是觉得目前的midp规范还太弱,OSGi要想运行在上面,很多功能实现起来都比较不爽。

好,有了对framework结构层次的皮毛认识,下面我们就开始着手改造那个“扶贫助手”的程序,使其变成OSGi的bundle(s),然后从上面提到的4个方面来分析framework的机制。
这里,我先给出“扶贫助手”的java application模拟程序代码:

package aa.bb.cc;

public class FamilyInfo {
 
private String familyName; //家庭名称
 private int population; //人口数量
 private int incomePerYear; //家庭年收入

  …..
//省略了getter和setter方法

//获得家庭人均年收入
 public int getIncomePerMember(){
  
return (int)(this.incomePerYear/this.population);
 }


 
public String toString() {
  
  
return "Family: " + this.familyName + ", population: " + this.population + ", income: " + this.incomePerYear;
 }

 
//按家庭年收入又低到高排序
 public static void sortByIncomePerYear(FamilyInfo[] families){
  FamilyInfo temp 
= null;
  
for(int i = 0; i < families.length -1; i ++){
   
for(int j = i + 1; j < families.length; j ++){
    
    
if(families[i].getIncomePerYear() > families[j].getIncomePerYear()){
     temp 
= families[i];
     families[i] 
= families[j];
     families[j] 
= temp;
    }

   }

  }

  
 }


 
//按家庭人均年收入由低到高排序
 public static void sortByIncomePerMember(FamilyInfo[] families){
  FamilyInfo temp 
= null;
  
for(int i = 0; i < families.length -1; i ++){
   
for(int j = i + 1; j < families.length; j ++){
    
    
if(families[i].getIncomePerMember() > families[j].getIncomePerMember()){
     temp 
= families[i];
     families[i] 
= families[j];
     families[j] 
= temp;
    }

   }

  }

  
 }


 
public static void main(String[] args){
  FamilyInfo[] families 
= new FamilyInfo[3];
  families[
0= new FamilyInfo();
  families[
0].setFamilyName("Zhang");
  families[
0].setPopulation(3);
  families[
0].setIncomePerYear(1200);
  families[
1= new FamilyInfo();
  families[
1].setFamilyName("Li");
  families[
1].setPopulation(6);
  families[
1].setIncomePerYear(1800);
  families[
2= new FamilyInfo();
  families[
2].setFamilyName("Liu");
  families[
2].setPopulation(4);
  families[
2].setIncomePerYear(1500);
  FamilyInfo.sortByIncomePerYear(families);
  
for(int i = 0; i < families.length; i ++){
   System.out.println(families[i].toString());
  }

  FamilyInfo.sortByIncomePerMember(families);
  
for(int i = 0; i < families.length; i ++){
   System.out.println(families[i].toString());
  }

  
 }

}


 

posted @ 2006-02-14 15:42 勤劳的蜜蜂 阅读(5164) | 评论 (3)编辑 收藏

osgi介绍(二)一个假想的实例

如何分析OSGi service platform的机制?给出几个硬生生的例子,然后分析一下
代码?那还不如你自己看书看规范好了。因此,我觉得还是结合一个应用实例来分析会更
容易理解,当然,是一个假想的应用实例。用怎样一个实例呢?嗯......

几个月前,一个中学同学打电话给我说他们要在PDA上开发一个简单的应用程序来临时纪
录工作的结果,并向我咨询,这种开发的难度和周期。这事启发我了,就以PDA的应用为
背景,让我们来假想一个场景,从而来比较传统的应用模型与采用OSGi的应用模型有怎样
的区别。

我这样想象:
小李是一个软件工程师,在一家专门为PDA开发应用程序和解决方案的公司工作。最近,
他刚为公司的一个客户开发完成了一套运行在PDA的JAVA应用程序,我们不要关心PDA是
什么硬件配置,只要知道它配备了JVM(cvm) + CDC以及PP和文件系统(呵呵设备还是比较
强劲的)。而这个客户是一个慈善机构,该机构人员携带PDA进入偏远山区收集生活困难
家庭的信息,以准备进行资助。而这套程序将会暂时把家庭信息保存在PDA中,并随时供
用户查询修改。用户使用一个月后,反馈非常好,但是,他们有新需求了,说原来只是想纪录
信息就成了,现在希望能给出一些排序功能,比如按家庭年收入对纪录进行排序.

接到这个需求,小李一看,这个简单,只要增加一个排序方法就可以了,让我们假设他使用了如下
数据结构来纪录家庭信息:

Class FamilyInfo {

 
private String familyName;//家庭名称

 
private int population; //人口数量

 
private int incomePerYear; //年收入

 .(省略Getter和Setter方法)
}


 

为了满足这个需求,小李决定添加一个静态的排序方法:

public static FamilyInfo[] sortByIncomePerYear(FamilyInfo[] familyInfos){
 
//根据incomePerYear的值进行冒泡排序。
}



把相关连部分修改完毕后,小李重新制作了安装包和启动脚本,发送给客户,不管客户如何操作
总之,原来的PDA程序必须卸载,新程序必须拷贝到PDA上再次执行安装,重新启动运行。

又过了一阵,客户说,要求提供按人均年收入进行排序,然后同样的事情又发生了......

几个轮次下来,小李发现,客户的需求还在增加,他们可能要求增加一个字段,记录目前该
家庭得到的资助额,还可能添加按收入范围查询纪录等等,事情还远没有结束。

如何改进这个情况呢?当然,改进涉及多方面,比如从软件本身出发,可以使用合适的design
pattern重新设计程序的体系结构,使得程序更易于扩展,关于这一点,有太多的讨论了,我就不
掺和了。还有从部署方面说,配置,安装和卸载程序,对最终用户往往是一项mission impossible,
能否让应用程序自己升级,而用户只要点击一个"升级"来触发这个过程而已......

我想你当然知道我给的答案:OSGi,OSGi,OSGi!!!!

posted @ 2006-02-14 15:39 勤劳的蜜蜂 阅读(4661) | 评论 (3)编辑 收藏

osgi介绍(一)什么是osgi

过于的一年多,在和很多it届的同学及朋友见面时,他们总会问我最近在做什么。“OSGi!”,我不加思索的回答。到目前为止,对这个单词得到的反应都没有超出“这是什么?”,“我没有听说过”,“噢,能具体点吗?”等等。而我的回答更让他们糊涂,最后,大家干脆放弃这个话题,转到买房,运动等等更能体现聚会实质的问题上。不过最近,我一直在思考这个问题,下次再遇到这种情况时,该如何去表达才能让也是it届的哥们姐们能迅速的理解这个领域的范围呢?要知道,技术人员往往不善于表达,我们已经习惯了和业内人士用行话交流。

关于这个问题,我访问了OSGi Alliance的网站,在里面的faqs中,找到了我想要的东西。实际上,正如faqs中所解答的,OSGi涵盖了太多的范围,简单的两三句话是无法说清楚的。而我这里指的OSGi从技术的角度,应该说是“OSGi service platform ”,faqs中这样解释OSGi service platform(http://www.osgi.org/about/faqs.asp?section=1#q19) :
The OSGi service platform delivers an open, common architecture for service providers, developers, software vendors, gateway operators and equipment vendors to develop, deploy and manage services in a coordinated fashion. .......(以下省略上千英文单词)

好长!不过第一句话就已经能总结陈词了,“OSGi service platform是一个开放并且提供统一接口标准的体系框架,基于这个体系框架,服务提供商,程序开发人员,软件提供商,服务网管运营商,设备提供商能够协调地联合起来开发,部署以及管理向用户提供的各种服务。”还需要提到的是OSGi service platform是一个基于Java的platform。

OSGi的提出和诞生之初,其目的主要是能够灵活方便并远程管理互联的网络嵌入设备(听说是1997年左右提出,与Jini有深厚渊源)。随着硬件设备的能力不断提高,java技术的日益普及,尤其J2ME的壮大,现实应用的需求也不断扩大和推进,一个统一的标准变得非常的必要。OSGi Alliance就在这样的背景下成立了。从1999年成立以来,OSGi Alliance已经针对这个service platform发布了4版规范,其中r4是2005年10月份刚刚发布。

目前有不少公司对OSGi service platform推出了自己的实现,象ibm的smf(Service Management Framework,嗯,多好的名字,在那么多的platform实现中,我个人最喜欢这个名字,言简意赅)。

德国的ProSyst公司(http://www.prosyst.com)是OSGi Alliance中非常活跃的推动者,看看他们的产品列表吧http://www.prosyst.com/products/osgi.html(他们甚至提供了kvm + cldc的OSGi framework)

开源的Oscar(http://oscar.objectweb.org/),Knopflerfish(http://www.knopflerfish.org/)

对于OSGi的成功应用,最有名的应该是eclipse了,它就是基于OSGi service platform的产品。还有Apache,据说OSGi将被应用于其新一代的build工具中。这些都是j2se和j2ee的应用,而基于j2me的,手机(对应OSGi Alliance的MEG)和车载设备(对应OSGi Alliance的VEG)是OSGi的主要领域,OSGi Alliance已经有相应的规范,这些领域的应用相信会更加精彩,让我们拭目以待吧。

posted @ 2006-02-14 15:32 勤劳的蜜蜂 阅读(23119) | 评论 (10)编辑 收藏