2011/12/20

Android 筆記-讓 WebView 裡的 javascript 呼叫 java

這幾天碰到了一個問題
在 WebView 裡面有撥電話的連結
使用類似下面這樣的 html 碼
<a href="tel:0288888888">0288888888</a>

點下連結時 Android 會叫出撥號介面並且自動將號碼填進去
使用者只要按下撥號鍵就可以撥出

不過要是電話號碼有分機資訊時
我們需要的電話號碼可能是 0288888888,123 這樣的格式(","代表暫停兩秒)
使用上述方法叫出撥號介面的話
Android 會把","及其後面的號碼都給去掉

為此我做了一些測試
發現如果是透過 Intent.ACTION_DIAL 叫出撥號介面會出現一樣情形
但是 Intent.ACTION_CALL 直接撥號可以正常撥出分機

所以只要我讓 WebView 呼叫 java
透過 Intent.ACTION_CALL 直接撥號
就可以解決分機的問題了

那要怎麼做呢?
就是用 addJavascriptInterface 這個方法

把要做的事情寫在一個對應 Object 的 method 裡面

MyObj myObj = new MyObj();

public class MyObj{
public void telext(String telStr) {
Uri uri = Uri.parse("tel:"+telStr);
Intent intent = new Intent(Intent.ACTION_CALL, uri);
startActivity(intent);
}
}

在 WebView 載入資料前要指定對應的 Obj
//打開 JavaScriptEnabled 並加入對應物件
myWebView.getSettings().setJavaScriptEnabled(true);
myWebView.addJavascriptInterface(myObj, "myObj");

addJavascriptInterface 需要傳入兩個參數
第一個是 Object 物件,第二個是 javascript 呼叫時的名稱

載入的 html 就可以用類似這樣的方式呼叫 java
<a href="#" onClick="window.myObj.telext('"+sContactTel+"');\">sContactTel</a>

Android 筆記-WebView 無法正確顯示 UTF-8 編碼內容的解決辦法

公司的測試手機 Nexus S 升級 Ice Cream Sandwich (4.0.3) 之後
發現了一個小問題
就是原本 app 裡面的 WebView 裡面的中文字變成了亂碼
而在 2.3.6 的版本則是正常顯示

但是明明 html 碼裡面已經指定了
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

WebView 載入資料時也指定為 UTF-8 了
myWebView.loadData(sHtml, "text/html", "utf-8");

為什麼還是會有亂碼呢?

上網搜尋後發現有一個解法
就是改用 loadDataWithBaseURL 這個 method
myWebView.loadDataWithBaseURL(null, sHtml, "text/html", "utf-8", null);

其原理是利用這個方法強迫 WebView 去把 html 的內容給 base encode
資料來源是這裡

2011/02/10

PureMVC 入門筆記-2

接著來看看 Notification

Notification

Package:org.puremvc.as3.patterns.observer
實作 INotification,與 Flash 的 Event 機制不同的是
Event 機制遵循的是 責任鍊(Chain of Responsibility) 模式,事件會跟著 displayObject 的 parent 一直上浮
而 Notification 則是遵照發布/訂閱模式,訂閱者只接收感興趣的消息,與接收者之間並無 parent/child 關係
而 Notification 並不是用來取代 Event 機制的,它們需要相互合作使用

主要 public 方法有:
//建構子
Notification(name:String, body:Object = null, type:String = null)

getBody():Object
getName():String
getType():String

setBody(body:Object):void
setType(type:String):void

toString():String

由於 Proxy, Mediator, Command 都繼承自 Notifier 並實作 INotifier
所以有一些共通的屬性方法
//連到 Facade 實體的參照
facade : IFacade
initializeNotifier(key:String):void
sendNotification(notificationName:String, body:Object = null, type:String = null):void
這些就不一一列在下面了
而要注意的是 Mediator/View 之間的關係不可避免的是緊耦合,Proxy/Data 也是一樣。但這樣可以讓他們與其他程式碼變成松耦合

Proxy

Package:org.puremvc.as3.patterns.proxy
繼承 Notifier,實作 INotifier, IProxy
Proxy 的角色是被 Model 註冊的數據持有者,並用來實現 Domain Logic,其中 data 可以透過 getter 轉型為它真正的型別

Protected 屬性:
data : Object
proxyName : String

主要 public 方法有:
//建構子
Proxy(proxyName:String = null, data:Object = null)

setData(data:Object):void
getData():Object
getProxyName():String

Mediator

Package:org.puremvc.as3.patterns.mediator
繼承 Notifier,實作 INotifier, IMediator
Mediator 的工作是負責讓 view component 與系統其他部分進行溝通,其中 viewComponent 可以透過 getter 轉型為它真正的型別

Protected 屬性:
mediatorName : String
//用來存放視覺組件的變數
viewComponent : Object

主要 public 方法有:
//建構子
Mediator(mediatorName:String = null, viewComponent:Object = null)

//在這邊列出要 Mediator 感興趣的 INotification 的名稱,這裡有列的,handleNotification()才會收到
listNotificationInterests():Array
//Mediator需要監聽 Flash 裡的 Event (來自 view Component),也要監聽 Notification,其差別就在於使用 listNotificationInterests() 跟 handleNotification() 這兩個方法來管理 Notification
handleNotification(notification:INotification):void
//handleNotification 範例 CODE
override public function handleNotification(notification:INotification):void{
 //建議使用 switch/case 來處理 Notification
 switch(notification.getName()){
  //這裡處理的 Notification 如果太多(超過四五個),應把 Mediator 拆開
  case LoginProxy.LOGIN_FAILED:
   //..省略...
   break;
  case LoginProxy.LOGIN_SUCCESS:
   //..省略...
   break;
 }
}

//viewComponent 的取得與設定
setViewComponent(viewComponent:Object):void
getViewComponent():Object

getMediatorName():String

至於 Command,則分成 SimpleCommand 跟 MacroCommand 兩種
差別在於 MacroCommand 可以執行其他 ICommand
Command 的功能在於協調 Proxy、處理異常等等

SimpleCommand

Package org.puremvc.as3.patterns.command
繼承 Notifier,實作 INotifier, ICommand

繼承自 Notifier 的屬性與方法
//連到 Facade 實體的參照
facade : IFacade
sendNotification(notificationName:String, body:Object = null, type:String = null):void

自己的方法只有一個,就是
execute(notification:INotification):void
這個方法是給我們 override 用的,可以把要處理的事情寫進去

MacroCommand
Package org.puremvc.as3.patterns.command
繼承 Notifier,實作 INotifier, ICommand

除了跟 SimpleCommand 一樣擁有
facade : IFacade
execute(notification:INotification):void
sendNotification(notificationName:String, body:Object = null, type:String = null):void
之外

還有以下方法:
initializeMacroCommand():void
addSubCommand(commandClassRef:Class):void

addSubCommand(commandClassRef:Class) 可以為 Command 增加 SubCommand
( SubCommand 本身可以是 SimpleCommand 也可以是 MacroCommand)
在 execute() 時會遵循 First In/First Out (FIFO) 的順序

要注意的是 execute() 在這裡已經變成一個 final function
所以要改為 override 它的 initializeMacroCommand() 方法
initializeMacroCommand()的 override 應該要像這樣:
override protected function initializeMacroCommand():void{
 addSubCommand(me.myapp.controller.FirstCommand);
 addSubCommand(me.myapp.controller.SecondCommand);
 addSubCommand(me.myapp.controller.ThirdCommand);
}

以上,已經把這四個重要的類別掃過一遍了
再搭配高手們的範例來看應該就會對 PureMVC 有一些認識

PureMVC 入門筆記-1

雖然已經有很多先進寫過關於 PureMVC 的相關文章
但是我還是習慣自己整理一下筆記來釐清觀念

PureMVC 的目的就是實踐 MVC 設計模式,把程式分成Model、View、Controller 三個部份,當然裡面還實作了 GoF 設計模式:可復用物件導向軟體的基礎裡面的其他設計模式(代理模式, 命令模式, 觀察者模式, 外觀模式, 單例模式, 工廠方法模式, 中介者模式...等)

Model (負責轉發請求,對請求進行處理) --> Proxy
View (使用者介面) --> Mediator
Controller(資料管理或實作演算法) --> Command
Facade (實做外觀模式,使用單一類別作為MVC三者溝通之用)

而溝通則使用 Notification (實做觀查者模式),不使用 Flash 的 Event 機制,以便移植到其他語言
Notification 可以被用來觸發 Command 的執行
Facade, Proxy, Mediator, Command 大家都可以 sendNotification
而 Facade 跟 Proxy 不會接收 Notification

以下針對個別部份進行詳細說明:( Standard 版本)
Facade

Package:org.puremvc.as3.patterns.facade
是 IFacade 的單例實作
Facade 的功能有:
初始化 Model, View, Controller 的單例類別並提供所有方法
對主程式註冊 Command 的地方

主要 public 方法有:
getInstance():IFacade

//建立並發送 INotification.
sendNotification(notificationName:String, body:Object = null, type:String = null):void
//取得 Mediator
retrieveMediator(mediatorName:String):IMediator
//取得 Proxy
retrieveProxy(proxyName:String):IProxy

//註冊 Command, Mediator, Proxy
registerCommand(notificationName:String, commandClassRef:Class):void
registerMediator(mediator:IMediator):void
registerProxy(proxy:IProxy):void

//移除 Command, Mediator, Proxy
removeCommand(notificationName:String):void
removeMediator(mediatorName:String):IMediator
removeProxy(proxyName:String):IProxy

//檢查 Command, Mediator, Proxy 是否已註冊
hasCommand(notificationName:String):Boolean
hasMediator(mediatorName:String):Boolean
hasProxy(proxyName:String):Boolean

Protected 屬性及方法有:
instance : IFacade [static]
model : IModel
view : IView
controller : IController
//初始化的方法
initializeFacade():void
initializeModel():void
initializeView():void
initializeController():void

Facade 的範例 ApplicationFacade.as:
package com.me.myapp{
//import
import com.me.myapp.view.*;
import com.me.myapp.model.*;
import com.me.myapp.controller.*;

import org.puremvc.as3.interfaces.*;
import org.puremvc.as3..patterns.facade.*;

// 繼承 Façade, 實作 IFacade
public class ApplicationFacade extends Façade implements IFacade{
 // 為 Notification 定義常數
 public static const STARTUP:String = "startup";
 public static const LOGIN:String = "login";
 // 用以取得單一實體的工廠方法
 public static function getInstance() : ApplicationFacade{
  if ( instance == null ) instance = new ApplicationFacade();
  return instance as ApplicationFacade;
 }
 // 初始化 Controller,註冊 Command (建立 Notification 與 Command 的對應關係)
 override protected function initializeController( ):void{
  super.initializeController();
  registerCommand( STARTUP, StartupCommand );
  registerCommand( LOGIN, LoginCommand );
  registerCommand( LoginProxy.LOGIN_SUCCESS, GetPrefsCommand );
 }
 /** 建立一個 startup 方法把應用程式作為參數傳入
  * 並發出 Notification 將它送到已註冊的 StartupCommand
  */
 public function startup( app:MyApp ):void{
  sendNotification( STARTUP, app );
 }
}
}

整理到這邊好像篇幅差不多了
下一篇繼續來看 Proxy, Mediator, Command, 還有 Notification

2010/12/26

Error in an XML file: aborting build. 的解決方式

最近想來看一下 Android 的書
稍微了解一下手機應用程式開發會是怎麼樣的

但練習沒幾個範例就發現
寫完 main.xml ...編譯的時候會出現錯誤訊息
Error in an XML file: aborting build.
同時也自動新增了一個 main.out.xml 的空白檔案
但檢查來檢查去....程式碼都沒有錯誤阿

反覆測試後發現在 Eclipse 使用 ADT 開發 Android 的 apk 時
編譯的時候 Package Explorer 或編輯視窗不可以 focus 到任何一個 xml 檔案
不然編譯就會上述有問題,且會自動生成該 xml 檔案的 out.xml 檔

解決方式如下:
1. 先刪除自動生成的 ****.out.xml 檔案
2. 由於專案有錯誤是不能 compile 的,所以要先到 Project > Clean... 把該專案狀態與錯誤清空
3. 重新 Run (注意不要再 foucs 在 xml 檔案上)

在這裡做個筆記,希望能幫上遇到相同困擾的新手們

2010/11/03

flash.net.registerClassAlias 與 Value Object

接續前面的內容,在 Zend_Amf 入門 裡面,透過 gateway 程式我們可以呼叫 PHP 的方法傳回資料
今天來研究一下另一種 Typed Object 的方式,把 PHP 類別 mapping 到 ActionScript 裡面(這種專門用來存放資料的物件稱為 Value Object)

首先回顧一下我們的 gateway,透過 addDirectory 我們把 AMFapp/ 下的 .php 動態載入
<?php
require_once 'Zend/Amf/Server.php';

$server = new Zend_Amf_Server();
$server->addDirectory(dirname(__FILE__) . '/AMFapp/');

$response = $server->handle();
echo $response;
?>

接下來我們在 AMFapp 下面新開一個資料夾 vo,專門用來放 Value Object 的類別
並在裡面建立一個 VOPerson.php
<?php
class VOPerson{
 //注意這裡!! 用 public $_explicitType 設定類別別名
 public $_explicitType = "VOPersonAlias";
 
 public $fName = "Joseph 喬瑟夫";
 public $lName = "Joestar 喬斯達";
 public $favoriteFood = array("T-Bone Steak", "Fried Chicken", "Chewing Gum");
 
 protected $standName = "Hermit Purple";
 private $birthday = "1920-09-27";
}
?>

除了用 public $_explicitType 設定類別別名外,也可以用 getASClassName 回傳類別別名
兩種方式擇一即可
public function getASClassName(){
 return 'VOPersonAlias';
}

有了 mapping 的 PHP 類別後,我們再寫一個 TestVO.php (放在 AMFapp下) 用來回傳這個 VOPerson 類別

<?php
//記得要把類別匯入
include 'vo/VOPerson.php';

class TestVO{
 public function getVOPerson(){
  return new VOPerson();
 }
}
?>

在 Flash 裡面我們也要準備一個跟 VOPerson 對應的類別,本例為 VOPerson.as
範例方便起見就使用 default package
package  {
 public class VOPerson {
  public var fName:String;
  public var lName:String;
  public var country:String;
  public var favoriteFood:Array;
  //非 public 的物件成員是不會 mapping 過來的,這邊只是測試用而已
  protected var standName:String;
  private var birthday:String;
  //這邊我們定義 toString 方法以便傾印資料
  public function toString():String{
   var s:String = "======== " + fName +" ‧ "+ lName +" ========\n";
   s += "國籍: "+ country +"\n";
   s += "喜歡的食物: "+ favoriteFood +"\n";
   s += "替身名: "+ standName +"\n";
   s += "生日: "+ birthday +"\n";
   s += "=================================================";
   return s;
  }
 }
}

該準備的東西都齊了,組合!!
ValueObjectTest1.fla
import flash.net.*;

//注意這裡!! 一定要註冊類別別名且與 $_explicitType 的值相同,這樣才可以正確識別
registerClassAlias("VOPersonAlias", VOPerson);

[Bindable]
var person:VOPerson;

var nc:NetConnection = new NetConnection();
var responder:Responder = new Responder(onNCResult, onNCFault);
nc.connect("http://localhost/zend_gateway.php");
nc.call("TestVO.getVOPerson", responder);

function onNCResult(re:*):void{
 person = VOPerson(re);
 trace(person); //因為我們寫了 toString(),所以可以直接 trace
}
function onNCFault(fault:Object):void{
 for(var s:String in fault){
  trace(s);
 }
}

如果順利的話就可以見到如下資訊
可以看到非 public 的屬性是過不來的
======== Joseph 喬瑟夫 ‧ Joestar 喬斯達 ========
國籍: U.S.A.
喜歡的食物: T-Bone Steak,Fried Chicken,Chewing Gum
替身名: null
生日: null
=================================================

如果出現錯誤訊息的話,請檢察類別別名是否有設定?
在 PHP 跟 AS 裡面設的別名是否相同?
需要的檔案是否正確匯入?...等

2010/11/01

ActionScript (AMF3) 與 PHP 資料型別對照表

ActionScript (AMF3) 對應 PHP 資料型別
ActionScript type (AMF3)PHP type
undefined
null
null
intinteger (超出範圍時為 float)
Number
uint
float
Booleanboolean
Stringstring
Arrayarray
XmlSimpleXml
flash.utils.ByteArraystring
Object
mx.collections.ArrayCollection
object
RemoteClass Objectclass mapped object
dateZend_Date

 

PHP 對應 ActionScript (AMF3) 資料型別
PHP typeActionScript type (AMF3)
nullnull
booleanBoolean
stringString
integer
float
Number
DomDocumentXml
DateTimeDate
Array (索引式陣列)Array
object
Array (關聯式陣列)
Object
RemoteClass Zend_Amf_Value_TypedObjecttyped object
RemoteClass Zend_Amf_Value_ArrayCollectionmx.collections.ArrayCollection