Merapi是一个用于Java和AIR之间通信的桥接库。通过它可以极大的扩展AIR在本地的执行能力,当然前提是客户端不仅装有AIR运行时虚拟机,还需要安装有Java虚拟机。Merapi通过Socket来通信,使用Adobe的AMF作为数据传输格式。下面看看如何在Flash Builder中创建一个基于Adobe AIR的桌面软件。
在Java和ActionScript两端都存在一名为Bridge的类,它是一个Singleton,用来注册消息类型和发送消息。在这个例子中,AIR向Java端发送一个名为”flashEvent”的消息,消息内容是一个Dictionary实例;并且监听名为”javaEvent”的消息。
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" creationComplete="initApp()">
<mx:Script>
<![CDATA[
import demo.JavaMessageHandler;
import demo.SerializeMessageHandler;
import merapi.Bridge;
import merapi.messages.Message;
import merapi.error.MerapiErrorMessage;
private function initApp():void{
//register a error handler
new SerializeMessageHandler();
//send a message
var message:Message=new Message();
message.type="flashEvent";
var data:Dictionary=new Dictionary();
data["name"]="colorhook";
message.data=data;
message.send();
//register a message handler
Bridge.getInstance().registerMessageHandler("javaEvent",
new JavaMessageHandler());
}
]]>
</mx:Script>
</mx:WindowedApplication>
CoreErrorHandler继承自EventDispatcher,实现了IMessageHandler接口,它在构造函数中就集成了错误处理。所以不需要显示的在Bridge中注册,而是构造一个实例就行了。SerializeMessageHandler继承自CoreErrorHandler,用来处理全局错误。
package demo{
import merapi.handlers.mxml.CoreErrorHandler;
import mx.rpc.events.ResultEvent;
public class SerializeMessageHandler extends CoreErrorHandler{
public function SerializeMessageHandler(type:String=null){
super(type);
this.addEventListener(ResultEvent.RESULT,onMessageResult);
}
private function onMessageResult(event:ResultEvent):void{
trace("SerializeMessageHandler:",event);
}
}
}
JavaMessageHandler实现了接口IMessageHandler,并在handleMessage方法中输出从Java中返回的数据。
package demo{
import merapi.handlers.IMessageHandler;
import merapi.messages.IMessage;
public class JavaMessageHandler implements IMessageHandler{
public function handleMessage(message:IMessage):void{
trace(message.type);
trace(message.data);
}
}
}
编译运行会发现控制台输出SerializeMessageHandler中的trace信息,因为目前还没有开启Java端的ServerSocket。所以下面来启动一个Java程序。首先调用Bridge.open()来启动ServerSocket。然后监听名为”flashEvent”的消息。
package demo;
import org.apache.log4j.Logger;
import merapi.Bridge;
public class Application {
protected Logger logger=Logger.getLogger(Application.class);
public Application(){
logger.info("Application start");
//open Bridge
Bridge.open();
//register a message handler
Bridge.getInstance().registerMessageHandler("flashEvent",
new AIRMessageHandler());
}
public static void main(String[] args){
new Application();
}
}
在消息处理器AIRMessageHandler中,输出来自Flash的数据,AMF将ActionScript中的Dictionary映射为Java中的Map。接受到数据之后,我们再从Java中发出一个Message,该Message携带了一个List类型的数据,在Flash中,它会被映射成Array类型。
package demo;
import java.util.*;
import org.apache.log4j.Logger;
import merapi.handlers.IMessageHandler;
import merapi.messages.IMessage;
import merapi.messages.Message;
public class AIRMessageHandler implements IMessageHandler {
protected Logger logger=Logger.getLogger(AIRMessageHandler.class);
@Override
public void handleMessage(IMessage message) {
//handle message
logger.info(message.getType());
Map<String,String> map=(Map<String, String>)message.getData();
logger.info(map.get("name"));
//send a new message
Message newMessage=new Message();
newMessage.setType("javaEvent");
List<String> list=new ArrayList<String>();
list.add("Flash Player");
list.add("AIR");
newMessage.setData(list);
newMessage.send();
}
}
Merapi在Java端用到了Spring, log4j和Adobe AMF相关包, 所以运行时要保证项目包含了这些jar包。可以从Merapi的Google Code中得到这些包。
AIR2的发布,让我们在AIR和Java通信方面有了新的选择: flerry。
flerry使用了AIR2的新特性NativeProcess,它直接创建了一个进程了执行Java程序。从其作者的blog上可以了解更多信息: http://www.riaspace.net/2010/01/flerry-flex-java-bridge-for-adobe-air-2-0/
Android的android.widget包中有个名为VideoView的View类,见其名,就知道它是一个视频组件。下面就用它来实现Android手机系统下视频的播放。
添加一个3pg视频文件
3pg是手机上非常流行的视频文件,本次以一个3pg格式的视频文件作为best practice,将其命名为sample.3gp并保存在res/raw文件夹下。
添加一个VideoView到试图布局文件
声明它的名称为videoView,并填充父容器。
<?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"
>
<VideoView
android:id="@+id/videoView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
</LinearLayout>
设定VideoView的视频路径
在Activity中,通过Uri来获得视频路径,然后由VideoView来播放它。
package demo.media;
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.widget.MediaController;
import android.widget.VideoView;
public class MediaDemo2 extends Activity {
protected VideoView videoView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
videoView=(VideoView)findViewById(R.id.videoView);
initialize();
}
private void initialize(){
Uri uri=Uri.parse("android.resource://demo.media/"+R.raw.sample);
videoView.setVideoURI(uri);
videoView.setMediaController(new MediaController(this));
videoView.requestFocus();
}
}
Android API中有一个类MediaPlayer来控制媒体的音频播放,使用它有两种方式来达到声音播放的效果:
- MediaPlayer实例.setDataSource(Path)
- MediaPlayer类.create(Context, uri)
这里使用第二种方式来播放声音,首先需要在res/raw文件夹下放一个名为sound_test.mp3格式的音乐文件。界面上只用一个按钮用于控制停止和播放,在这里,按钮没有声明在XML布局文件中,而是用代码生成的。
贴上代码:
package demo.media;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;
import android.view.ViewGroup.LayoutParams;
import android.view.View;
import android.view.View.OnClickListener;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
public class MediaTest01 extends Activity{
protected MediaPlayer mediaPlayer;
protected Button controlButton;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initializeMediaPlayer();
initializeView();
}
//初始化声音
private void initializeMediaPlayer(){
mediaPlayer=MediaPlayer.create(this, R.raw.sound_test);
mediaPlayer.setOnCompletionListener(new OnCompletionListener(){
public void onCompletion(MediaPlayer player) {
controlButton.setText("Play");
releaseMediaPlayer();
}
});
}
//初始化视图界面
private void initializeView(){
controlButton=new Button(this);
LayoutParams params=new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
setContentView(R.layout.main);
addContentView(controlButton, params);
controlButton.setOnClickListener(new OnClickListener(){
public void onClick(View v){
MediaTest01.this.handleControlButtonClick();
}
});
if(mediaPlayer.isPlaying()){
controlButton.setText("Stop");
}else{
controlButton.setText("Play");
}
}
//处理按钮动作
public void handleControlButtonClick(){
if(!mediaPlayer.isPlaying()){
this.playSound();
}else{
this.stopSound();
}
}
}
//播放声音
private void playSound(){
Toast.makeText(this,"Play",Toast.LENGTH_SHORT).show();
releaseMediaPlayer();
initializeMediaPlayer();
mediaPlayer.start();
controlButton.setText("Stop");
}
//停止声音
private void stopSound(){
Toast.makeText(this,"Stop",Toast.LENGTH_SHORT).show();
mediaPlayer.stop();
controlButton.setText("Play");
}
//释放播放器资源
private void releaseMediaPlayer(){
if(mediaPlayer!=null){
mediaPlayer.release();
mediaPlayer=null;
}
}
@Override
protected void onDestroy(){
super.onDestroy();
releaseMediaPlayer();
}
}
在Web中使用Google Map,需要根据domain来申请一个apiKey,用来标识客户端。Android手机中的Google Map也需要一个apiKey来标识客户端。
在eclipse开发环境中,可以使用emulator来测试一个Android应用,在emulator中运行的Google Map需要有一个与emulator对应的apiKey,获取apiKey的步骤如下:
1. 获得emulator的MD5认证码。
在Android SDK的bin目录下,用如下指令得到MD5认证码,-keystore后跟的是debug.keystore的地址,可以在eclipse首选项Android配置中找到。
keytool -list -alias androiddebugkey -keystore "C:\Documents and Settings
\MS\.android\debug.keystore" -storepass android -keypass android
根据MD5码生成API Key。
在http://code.google.com/android/maps-api-signup.html下可以生成一个API Key。
得到API Key之后就可以试试创建一个Google Map应用了。
首先,要保证能够使用Google Map,这个应用必须要能够访问Internet,所以AndroidManifest.xml中需要加入如下的权限声明:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
其次,该应用用到了Google Map的Java库,所以也需要在AndroidManifest.xml中声明。
<uses-library android:name="com.google.android.maps" />
然后,Activity必须继承MapActivity,而不是默认的Activity。
package com.colorhook.android;
import android.os.Bundle;
import com.google.android.maps.*;
public class GoogleMapApp extends MapActivity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected boolean isRouteDisplayed() {
// TODO Auto-generated method stub
return false;
}
}
最后在res/layout/main.xml文件夹中声明一个MapView。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.google.android.maps.MapView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:enabled="true"
android:clickable="true"
android:apiKey="apisamples"
/>
</LinearLayout>
在Flex中使用RemoteObject可以很容易地对Java对象进行RPC调用,但是其配置较为复杂。使用AMFPHP对PHP进行RPC调用只需要指定RemoteObject的destination和endpoint属性即可,而在BlazeDS环境下,需要创建ChannelSet和Channel对象。
通常的做法是使用一个xml文件来配置一个Java destination,Flex在编译的时候会根据配置信息来创建一个完整的RemoteObject对象。但是如果需要更改该RemoteObject对象的destination或者ChannelSet等属性,则需要重新编译,这样的话很是麻烦。
Spring的IOC容器可以很好解决这个问题,它把对象的创建方式声明在外部,然后由容器来动态生成。
使用我的Spring框架来实现这个想法。
1.创建远程的Java对象,编译成class文件放在文件夹WEB-INF\classes\demo下面。
package demo;
public class HelloBlazeDS{
public HelloBlazeDS(){
}
public String sayHello(String info){
return "[BlazeDS return] : "+info;
}
}
2.配置BalzeDS。
打开文件WEB-INF\flex\remoting-config.xml,向其中添加一个destination定义。
<destination id="HelloBlazeDS">
<properties>
<source>demo.HelloBlazeDS</source>
</properties>
</destination>
3.创建Spring的配置文件spring_config.xml。
<?xml version="1.0" encoding="utf-8"?>
<spring-config>
<beans>
<bean id="remoteBean" class="mx.rpc.remoting.mxml.RemoteObject">
<property name="destination" value="HelloBlazeDS"/>
<property name="channelSet" ref="channelSet"/>
</bean>
<bean id="channelSet" class="mx.messaging.ChannelSet">
<method name="addChannel">
<method-arg ref="channel"/>
</method>
</bean>
<bean id="channel" class="mx.messaging.channels.AMFChannel">
<property name="id" value="my-amf"/>
<property name="uri" value="http://localhost:8080/blazeds/messagebroker/amf"/>
</bean>
</beans>
</spring-config>
该配置文件声明了一个Bean名字叫remoteBean,它是一个RemoteObject对象,这个Bean还引用了channelSet和channel这两个Bean。
4.在Flex中使用Spring,下面是代码片段。
//导入加载配置文件的类ContextLoader
import com.colorhook.spring.context.ContextLoader;
private var contextLoader:ContextLoader;
private var remoteService:RemoteObject;
//初始化时加载配置文件
private function init():void{
contextLoader=new ContextLoader();
contextLoader.addEventListener("complete",onContextLoaderComplete);
}
//加载完成之后创建RemoteObject,并调用远程方法sayHello
private function onContextLoaderComplete(event:Event):void{
contextLoader.removeEventListener("complete",onContextLoaderComplete);
remoteService=contextLoader.contextInfo.getBean("remoteBean");
remoteService.sayHello.addEventListener(ResultEvent.RESULT,onSayHelloResult);
remoteService.sayHello("Spring and BlazeDS");
}
//输出返回结果
private function onSayHelloResult(event:ResultEvent):void{
Alert.show(String(event.result));
}