分类: 工具(Tool)

微信公众号爬虫

微信公众号爬虫

** 最近在翻看整理之前的工作代码和文档,发现2017年做的微信公众号爬虫的两张流程图,来聊聊曾经使用过的微信公众号爬虫方案吧。**

当时为了实现能够准实时的抓取微信公众号文章,先后实现了7、8种方案。每个方案都在使用一段时间之后,由于微信更新了反爬策略,使得对应的方案被废弃或者需要更新。

需求

这个东西的需求其实很简单

  • 获取指定公众号的文章列表(包括历史文章)
  • 需要获得文章的永久链接
  • 有新文章更新的话,抓取延迟在30分钟之内

问题

面临的问题主要有这么几个:

  • 微信的反爬是基于账号来做的,服务端那边有实时统计也有阶段性统计,一旦发现有爬取迹象,微信会直接封锁账号
  • 微信的服务端通信是加密的,不仅仅是 https,大部分重要的请求和返回的数据本身也是加密过的。好在公众号文章列表和内容都是 json 和 html 非加密格式的。
  • 起初抓取到的公众号链接都是永久链接,但是从2017年下半年某个时间开始,能够抓取到的公众号链接就只有临时链接了,临时链接有效时间大概是几个小时。过期之后,普通浏览器包括微信 Web 版本打开都会提示页面过期,只能通过微信原生客户端内置的浏览器打开。

方案

曾经采用过的方案大概包括

  • 利用搜狗的微信搜索或者文章列表和更新信息,利用搜狗的漏洞建立 cookie 池应对反爬
  • 利用中间人的方式,拦截微信客户端请求结果,抓取文章列表和文章链接
  • 利用微信 Android 客户端调起微信内部UI打开公众号详情页获得文章列表(通过逆向工程获得的公众号详情页调起方式)
  • 利用微信 Android 客户端调起微信内部浏览器打开文章临时链接,再利用中间人的方式获得永久链接
  • 从第三方抓取

我找到的这两张图应该是倒数第2和第3个方案,为了保证每个部件的灵活性导致整个调度流程很复杂(毕竟微信三天两头都在更新反爬策略)。最终的实现大概能够达到的效果是,跟踪10000多个公众号的更新,每天抓取大约3w 篇文章,更新延迟大概在10-30分钟左右。

不过放上来的两个方案现在只能是当做回忆,基本不可用了。所以,想做这个东西的同学就不必浪费时间尝试了。

这两个流程图真是看看都头大,这里面使用了 redis、flask api、mitm、微信 Android 客户端等各种。

方案1

方案2

再次强调一下,这两个方案现在由于包括微信反爬策略的调整等某些原因基本属于不可用的状态了。最新的可用方案就不放上来了,用的人越多被封锁的越快。

心得

现在的工作基本不碰爬虫了,讲那段时间研究微信公众号爬虫的一点点心得吧。不一定对,仅供参考。

微信的数据交互有两种方式,一种是 https 请求,一种是 wss 连接。微信的 web 版只用 https 请求方式(所以功能有限),原生微信客户端两种方式都用。

前者 https 请求可以通过中间人的方式拿到一部分请求的响应数据,但是大部分的请求和响应结果都是加密过的。

后者 wss 连接是在微信一启动的时候就会建立,这个wss 连接的数据交有一套协议,其中大概会交换加密密钥(猜测),这个加密密钥应该不是在客户端而是通过 wss 由服务端下发的,需要加密的 https 请求会使用 wss 那里拿到的加密密钥进行加密解密。

不过 wss 的通信协议程已经有人逆向出来了,现在很多微信机器人的高级功能都是通过这个方式实现的(简单的机器人可以直接用微信的 web api,但是复杂的就不行了),卖的不便宜哦。

JavaScript 中几种不同的基于 prototype 继承方式的区别

#JavaScript 中几种不同的基于 prototype 继承方式的区别

普通属性的继承

第一种方式

来自于 MDN 对象模型的细节

function Employee1 (name, dept) {
  console.log('Employee constructor')
  this.name = name || "";
  this.dept = dept || "general";
  this.work = function() {console.log("Employee.work")}
  this.workAsEmployee = function() { this.work() }
}

function WorkerBee1 (projs) {
  console.log('WorkerBee constructor')
  this.projects = projs || [];
  this.work = function() {console.log("WorkerBee.work")}
  this.workAsBee = function() { this.work() }
}
WorkerBee1.prototype = new Employee1;

function Engineer1 (mach) {
  console.log('Engineer constructor')
  this.dept = "engineering";
  this.machine = mach || "";
  this.work = function() {console.log("Engineer.work")}
  this.workAsEngineer = function() { this.work() }
}

Engineer1.prototype = new WorkerBee1;

e1 = new Engineer1()
e1.work()
console.log(Employee1.prototype.isPrototypeOf(e1))
console.log(WorkerBee1.prototype.isPrototypeOf(e1))
console.log(e1)

Alt text

第二种方式

来自于 YUI 的实现,利用中间对象传递 prototype

function extend(Child, Parent) {
  var F = function () { };
  F.prototype = Parent.prototype;
  Child.prototype = new F();
  Child.prototype.constructor = Child;
  Object.defineProperty(Child, "super", { "value": Parent.prototype })
}

function Employee2 (name, dept) {
  console.log('Employee constructor')
  this.name = name || "";
  this.dept = dept || "general";
  this.work = function() {console.log("Employee.work")}
  this.workAsEmployee = function() { this.work() }
}

function WorkerBee2 (projs) {
  console.log('WorkerBee constructor')
  this.projects = projs || [];
  this.work = function() {console.log("WorkerBee.work")}
  this.workAsBee = function() { this.work() }
}
extend(WorkerBee2, Employee2);

function Engineer2 (mach) {
  console.log('Engineer constructor')
  this.dept = "engineering";
  this.machine = mach || "";
  this.work = function() {console.log("Engineer.work")}
  this.workAsEngineer = function() { this.work() }
}
extend(Engineer2, WorkerBee2);

e2 = new Engineer2()
e2.work()

console.log(Employee2.prototype.isPrototypeOf(e2))
console.log(WorkerBee2.prototype.isPrototypeOf(e2))
console.log(e2)

Alt text

可以看到,区别主要在于,直接 Child.prototype = new Parent() 会把定义在 Parent 里面的方法也带到 prototype 里面去。
另外,这种方式并没有执行父类的构造函数。

对于定义在 prototype 里面的方法呢

下面对上面的方法定义进行一点改进,把方法定义在 prototype 里,类似正常的 OO 编程中在类里面定义方法。

第一种方式改进

function Employee3 (name, dept) {
  console.log('Employee constructor')
  this.name = name || "";
  this.dept = dept || "general";
}
Employee3.prototype.work = function() {console.log("Employee.work")}
Employee3.prototype.workAsEmployee = function() { this.work() }

function WorkerBee3 (projs) {
  console.log('WorkerBee constructor')
  this.projects = projs || [];
}
WorkerBee3.prototype = new Employee3;
WorkerBee3.prototype.work = function() {console.log("WorkerBee.work")}
WorkerBee3.prototype.workAsBee = function() { this.work() }

function Engineer3 (mach) {
  console.log('Engineer constructor')
  this.dept = "engineering";
  this.machine = mach || "";
}
Engineer3.prototype = new WorkerBee3;
Engineer3.prototype.work = function() {console.log("Engineer.work")}
Engineer3.prototype.workAsEngineer = function() { this.work() }

e3 = new Engineer3()
e3.work()

console.log(Employee3.prototype.isPrototypeOf(e3))
console.log(WorkerBee3.prototype.isPrototypeOf(e3))
console.log(e3)

Alt text

第二种方式的改进

function extend(Child, Parent) {
  var F = function () { };
  F.prototype = Parent.prototype;
  Child.prototype = new F();
  Child.prototype.constructor = Child;
  Object.defineProperty(Child, "super", { "value": Parent.prototype })
}

function Employee4 (name, dept) {
  console.log('Employee constructor')
  this.name = name || "";
  this.dept = dept || "general";
}
Employee4.prototype.work = function() {console.log("Employee.work")}
Employee4.prototype.workAsEmployee = function() { this.work() }

function WorkerBee4 (projs) {
  console.log('WorkerBee constructor')
  this.projects = projs || [];

}
extend(WorkerBee4, Employee4);
WorkerBee4.prototype.work = function() {console.log("WorkerBee.work")}
WorkerBee4.prototype.workAsBee = function() { this.work() }

function Engineer4 (mach) {
  console.log('Engineer constructor')
  this.dept = "engineering";
  this.machine = mach || "";
}
extend(Engineer4, WorkerBee4);
Engineer4.prototype.work = function() {console.log("Engineer.work")}
Engineer4.prototype.workAsEngineer = function() { this.work() }

e4 = new Engineer4()
e4.work()

console.log(Employee4.prototype.isPrototypeOf(e4))
console.log(WorkerBee4.prototype.isPrototypeOf(e4))
console.log(e4)

Alt text

注意观察 constructor__proto__ 属性。

要执行所有构造函数

上述第二种方法,都没有执行父类的构造函数,也就没有真正的继承父类的初始化数据。为了弥补这一点,如下两种写法都可以达到目的。

利用 super 变量

function extend(Child, Parent) {
  var F = function () { };
  F.prototype = Parent.prototype;
  Child.prototype = new F();
  Child.prototype.constructor = Child;
}

function Employee5 (name, dept) {
  console.log('Employee constructor')
  this.name = name || "";
  this.dept = dept || "general";
}
Employee5.prototype.work = function() {console.log("Employee.work")}
Employee5.prototype.workAsEmployee = function() { this.work() }

function WorkerBee5 (projs) {
  console.log('WorkerBee constructor')

  this.super = Employee5;
  this.super();

  this.projects = projs || [];

}
extend(WorkerBee5, Employee5);
WorkerBee5.prototype.work = function() {console.log("WorkerBee.work")}
WorkerBee5.prototype.workAsBee = function() { this.work() }

function Engineer5 (mach) {
  console.log('Engineer constructor')

  this.super = WorkerBee5;
  this.super();

  this.dept = "engineering";
  this.machine = mach || "";
}
extend(Engineer5, WorkerBee5);
Engineer5.prototype.work = function() {console.log("Engineer.work")}
Engineer5.prototype.workAsEngineer = function() { this.work() }

e5 = new Engineer5()
e5.work()

console.log(Employee5.prototype.isPrototypeOf(e5))
console.log(WorkerBee5.prototype.isPrototypeOf(e5))
console.log(e5)

Alt text

类似,但是用 Parent.apply 方法

function extend(Child, Parent) {
  var F = function () { };
  F.prototype = Parent.prototype;
  Child.prototype = new F();
  Child.prototype.constructor = Child;
}

function Employee6 (name, dept) {
  console.log('Employee constructor')
  this.name = name || "";
  this.dept = dept || "general";
}
Employee6.prototype.work = function() {console.log("Employee.work")}
Employee6.prototype.workAsEmployee = function() { this.work() }

function WorkerBee6 (projs) {
  console.log('WorkerBee constructor')
  Employee6.apply(this)
  this.projects = projs || [];
}
extend(WorkerBee6, Employee6);
WorkerBee6.prototype.work = function() {console.log("WorkerBee.work")}
WorkerBee6.prototype.workAsBee = function() { this.work() }

function Engineer6 (mach) {
  console.log('Engineer constructor')
  WorkerBee6.apply(this)
  this.dept = "engineering";
  this.machine = mach || "";
}
extend(Engineer6, WorkerBee6);
Engineer6.prototype.work = function() {console.log("Engineer.work")}
Engineer6.prototype.workAsEngineer = function() { this.work() }

e6 = new Engineer6()
e6.work()

console.log(Employee6.prototype.isPrototypeOf(e6))
console.log(WorkerBee6.prototype.isPrototypeOf(e6))
console.log(e6)

Alt text

Facebook的Dalvik运行期补丁

对于一个功能丰富的Android应用来说,要面对的挑战很多,其中很多问题绝大多数开发者都可能意识不到。Android应用的方法数量限制可能就是一个。

在一些传统的企业应用中,当处理跟业务数据相关的时候,通常不会将数据变量设置为公开访问public,而是会用getter/setter这样的存取方法来封装访问数据模型中的私有变量。在一般的编程实践中,这是一个被推荐和鼓励的做法。但是其副作用是类文件中会产生大量的方法(每一个私有变量都对应着2个方法)。

这个副作用带来的问题是,当应用安装在一些老机型上的时候,可能导致Android的一个Bughttp://code.google.com/p/android/issues/detail?id=22586。原因在于,在应用的安装过程中,会运行一个程序(dexopt)去根据当前机型为应用做一些特定的准备和优化。dexopt中使用了一个固定大小的缓冲区(名为LinearAlloc)来存储应用中方法的信息。最新的几个Android版本中,该缓冲的大小是8MB或16MB。但是Froyo(2.2)和Gingerbread(2.3)只有5MB。因为老版Android的这个限制,当方法数量超过这个缓冲大小的时候,会导致应用崩溃。

解决办法之一是可以利用http://android-developers.blogspot.hk/2011/07/custom-class-loading-in-dalvik.html提到的技术,将dex分成多个dex文件分别加载,首先加载核心dex,其他的模块和扩展功能放在其他的dex文件中。

但是某些时候,如果第二个dex包中的类需要被Android
Framework直接访问到的话,上述方案就是不可行的。此时必须要将第二个dex文件注入到Android的系统class loader中。这个一般情况下是无法做到的,但好在Android是开源的系统,可以利用Java的反射去修改Android内部的一些数据结构来解决这个问题。

这个方案在实际运行中,你会发现其实LinearAlloc不仅仅只是在dexopt中存在,而是在每个运行中的Android程序中都有它的身影。dexopt使用LinearAlloc储存dex文件的方法信息的时候,运行期的Android应用使用它访问实际用到的方法。如果在运行期将所有的dex文件都加载到一个进程中的话,实际的方法数量还是会超过限制。应用虽然可以启动但是很快就会崩溃。

看起来似乎要么只能精简应用的功能,要么就只能放弃支持Android部分版本而只支持最新的ICS以上的版本。

再次回头去看Android的源码,找到LinearAlloc缓冲的定义https://github.com/android/platform_dalvik/blob/android-2.3.7_r1/vm/LinearAlloc.h#L33,也许你能意识到:其实只要能将缓冲从5MB增加到8MB就可以安全运转了。

也许,可以使用JNI把当前的缓冲替换成一个更大的缓冲区。这个方案看起来太疯狂了。修改Java的Class Loader的内部是一回事儿,修改运行中的Davlik虚拟机的内部可是另外一回事儿——运行中的代码可能会很危险。但是仔细查看过代码、分析所有使用LinearAlloc的地方后发现,只要在应用启动的时候去做,应该没什么危险。所需要做的就是,找到LinearAlloc对象,锁定,然后替换缓冲。

事实上,只要找到它,后面的事情就很顺利了。在这里https://github.com/android/platform_dalvik/blob/android-2.3.7_r1/vm/Globals.h#L519,DvmGlobals对象中保存了LinearAlloc缓冲区。大概距离对象的开始地址700个字节的位置。从对象的开头开始扫描到这里风险很大,但是幸运的是,可以用距离一步之遥的vmList对象作为起点。这里包含了一个值,可以通过JNI和JavaVM的指针做比较。

最终方案是,找到vmList的值,扫描DvmGlobals对象找到匹配的位置,往后跳几个字节找到LinearAlloc的头,然后替换缓冲区。编写JNI扩展,嵌入到应用中,启动,然后应用应该可以正常运行在Gingerbread上了。

但是又有一个问题是,这个方案在Sumsung Galaxy S II上会失败,这可是最流行的运行Gingerbread的手机。

似乎三星对Android做了一些改动,导致这个方案的失败。其它的厂商可能会做同样的事情。所以这个方案的代码应该得更加健壮才行。

经过观察,在GS II上,LinearAlloc的缓冲区地址距离实际要找的地址只有4个字节。于是调整代码,如果在期望的地址没有找到LinearAlloc,那么就在附近几个字节的范围内查找。做这件事需要获取当前线程的内存映射表,确保没有在附近的查找过程中访问到无效地址(否则会立刻导致应用崩溃)。

(以上就是Facebook的故事,via

理解AnguarJS中的模板编译

在一开始学习AngularJS的过程中,模板的工作原理可能是最令人难解的问题之一。在其他的框架中,一般来说模板就是一个字符串,在这个字符串中使用一个特殊的表达方式可以嵌入一个传入的外部对象的数据,其本质上就是一个字符串的操作:输入字符串,输出字符串,然后通过`.innerHTML()`把所有搞出来的东西塞到DOM里面去。

很明显AngularJS的模板不是这样做的。它有双向数据绑定,可以完美的在视图中与最新的数据模型同步。它还会自动在`ng-click`上注册事件监听器,甚至可以将视图中输入控件中的数据同步回数据模型。太神奇了!

这个当然不是什么魔术,事实上将一个模板转换为一个带动态绑定和事件监听功能的动态视图,这中间的过程其实很简单。一旦搞明白了这个过程,对于AngularJS应用的理解也会上升一个台阶。

这里有一个模板


Hello {{yourName}}!

如你所见,这是个最简单的AngularJS的应用页面。

##反向控制(IoC)

第一个需要了解的概念是“反向控制”(IoC)。AngularJS不需要手工启动应用,而是假定你会遵循一定的规则在代码中放一些基本的设定。

例如,AngularJS会假定HTML代码中有一个带有`ng-app`属性的元素,表示你的应用的“根”节点。它表示AngularJS可以接管这个节点以及之下所有子节点。这一点可以保证AngularJS可以和其他的JavaScript框架共存。当AngularJS启动应用的时候,它会遍历DOM内的节点,并查找这个属性,这个在AngularJS术语中叫“指令”(Directive)。注意这个`ng-app`指令可以放在任何DOM节点上,包括``和``标签。

AngularJS还会假定其他的框架不会接触到它的“根”节点下面的所有内容,否则可能会导致未知行为,或者打破双向数据绑定和事件监听器。

##深入模板


Hello {{yourName}}!

这一部分是模板。“根”节点下面的所有内容都会被视为模板,之后将会被编译。模板与数据模型(`scope`)和控制器一起构成了动态视图,用户可以在浏览器中看到并与之交互。本例中没有控制器和明确的数据模型,但AngularJS会在后台创建对应的内容。 所有的AngularJS都会有一个`rootScope`,它会持有所有子scope的引用,它自身保存的数据也可以被所有的`scope`共享。本例中,由于没有明确指定控制器,所以模板会被绑定到这个`rootScope`上,并设置一个子`scope`。 在模板中,给输入控件绑定了一个模型(由ng-model指明的yourName),AngularJS会在这个数据控件上注册一个键盘事件监听器,并且将所有输入到这个控件中的内容自动保存到`rootScope.yourName`,但我们并没有也并不需要声明这个变量。`scope`中的变量会被初始化为`undefined`,就像正常的JavaScript对象一样。

模板中还用到了`interpolate`指令,表示为两对大花括号 `{{ yourName }}`。这个指令会创建一个`watcher`,监听模型的变化,并在发生变化的时候将数据更新到视图上。

接下来看看如何将一个DOMElement转换成动态视图。

##编译模板

AngularJS找到`ng-app`指令之后,它会启动并创建一个新的`rootScope`,开始编译“根”节点(带`ng-app`的那个节点)下面所有的子节点。 接下来发生的事情分为两步,编译和链接(这里借用了编译原理中的术语,但本质上是一样的)。更深的介绍可以参考文档[AngularJS documentation on the HTML Compiler](http://docs.angularjs.org/guide/compiler)。 首先,Angular的`compile`函数将传入的DOMElement作为输入。这一点与其它的框架很不一样,AngularJS会使用浏览器的API遍历整个DOM,而其它模板只是在做字符串替换。如果需要用字符串作为传入的模板,则先要用`angular.element`函数将字符串转为DOMElement。这个函数实际上就是jQuery中的`()`函数的。

`compile`函数会遍历DOM,并查找“指令”(Directive),将找到的每个“指令“添加到一个列表中,整个DOM遍历完成后,再将列表中的”指令“按照“优先级”排序。之后,执行每个“指令”自己的`compile`函数,让“指令”有机会去修改DOM。每个指令的`compile`函数会返回一个“链接”函数,该函数会被拼接成一个完整的链接函数,并被返回。 接下来,AngularJS会执行返回的“链接”函数,对应的scope会被传入到这个执行过程中。这一步中,所有的子“链接”函数都会被执行,并绑定在同一个scope上,或依照“指令”的设定创建一个新的scope。所有的“链接”函数执行完毕后,每个“链接”函数都会返回一组DOMElement,这些DOMElement已经完成数据绑定和事件监听,AngularJS会将它们添加到父节点。 上述过程的伪代码可以表示如下 var compile = ...; //注入到你的代码
var rootScope = ...; //注入 var parent = ...; //编译过的模板内容会被添加到该DOMElement下 var template = ...; //我们的模板的DOMElement var linkFn = compile(template); //编译模板,返回“链接”函数

var element = linkFn(scope); //“链接”,返回处理好的DOMElement

parent.appendChild(element); //将处理好的DOMElement添加到父节点

##总结
基本就是这些了。如你所见,AngularJS完全不同于其他的模板系统,它约定了一些规则,并基于这些规则做了一些假定,之后你就不必将精力耗费在启动代码上,而可以放在真正的应用上。这只是AngularJS的一部分,之后会继续讲讲`watch`和`digest`如何保证视图刷新。

via [src](http://daginge.com/technology/2014/03/04/understanding-template-compiling-in-angularjs/)

在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版本,复制到设备上,然后就可以通过命令行启动起来了(见[上一篇文章](http://log4think.com/use-apache-felix-in-android/)和[这里](http://log4think.com/use-apache-felix-in-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的[教程](http://www.knopflerfish.org/releases/3.2.0/docs/android_dalvik_tutorial.html)编译的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](http://nilvec.com/embedding-osgi-into-an-android-application-part-1.html)

在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的工具目录`/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=

3rd-party apt-key list for Ubuntu

## Google Chrome

wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
sudo sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
sudo apt-get update
sudo apt-get install google-chrome-stable

## JDK 1.7

echo "deb http://ppa.launchpad.net/webupd8team/java/ubuntu precise main" | tee -a /etc/apt/sources.list
echo "deb-src http://ppa.launchpad.net/webupd8team/java/ubuntu precise main" | tee -a /etc/apt/sources.list
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys EEA14886
apt-get update
apt-get install oracle-java7-installer

## NodeJS

sudo add-apt-repository ppa:chris-lea/node.js

## MongoDB

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list