把前面幾回講的用Scala寫的Android程式,放上 bitbucket 了,有興趣的可以自己去下載來看。
https://bitbucket.org/bluetang/android-taipei-disaster
Tuesday, November 22, 2011
Wednesday, November 16, 2011
My experience with Scala on Android
用Scala開發Android,是個滿有趣的經驗,大致上來因為我切入的時間點較晚,所以大部份的問題已經被前人所解決,就用 https://github.com/jberkel/android-plugin 把專案用 sbt 開好後,就可以開始寫 android.
我用的 IDE 是 intellij 11 EAP,用 sbt-idea 把 idea project 設好後,把預設的 asset pa th 從 .idea_module 改成 src/main ,就可以開始開發了。
在開發上,除了前一回碰上的proguard問題外,我還碰上另一個比較嚴重的問題-不能在Scala裡寫 AsyncTask
這問題跟SI-3622 SI-3494有關,看來是在 Scala 2.8解掉的問題,2.9又跑回來了,我這邊看到的狀況是
會被Scala compiler專換成
而底下的code,則是scala compiler會吐出 overides nothing.
不管怎樣,都跟Android要求的
解決方法是在 java 裡寫個 bridge interface ,幫 scala compiler 搞不定的東西,在這個 interface 裡定意好
我用的 IDE 是 intellij 11 EAP,用 sbt-idea 把 idea project 設好後,把預設的 asset pa th 從 .idea_module 改成 src/main ,就可以開始開發了。
在開發上,除了前一回碰上的proguard問題外,我還碰上另一個比較嚴重的問題-不能在Scala裡寫 AsyncTask
這問題跟SI-3622 SI-3494有關,看來是在 Scala 2.8解掉的問題,2.9又跑回來了,我這邊看到的狀況是
override protected def doInBackground(params: Params*): Result
會被Scala compiler專換成
public Result doInBackground(Seq params)
public Result doInBackground(Params[] params)
而底下的code,則是scala compiler會吐出 overides nothing.
override protected def doInBackground(params: Array[Params]): Result
不管怎樣,都跟Android要求的
protected Result doInBackground(Params[] params)不同,所以在runtime時會跑出NoSuchMethodError.解決方法是在 java 裡寫個 bridge interface ,幫 scala compiler 搞不定的東西,在這個 interface 裡定意好
public abstract class SAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
protected abstract Result doInBackground(Seq<Params> params);
@Override
protected Result doInBackground(Params... paramses) {
return doInBackground(JavaConversions.asScalaBuffer(Arrays.asList(paramses)));
}
}
Tuesday, November 15, 2011
Data Modeling With Jackson Json and Scala - Proguard
關於Proguard,官方的網頁是這麼自述的:
在Mobile App開發的時候,多會用proguard把沒有用到的程式碼濾除,並把變數名稱跟函式名稱用更精簡的字串來取代,這樣編出來的程式會更小,以我用Scala開發的Android程式,使用的的函式庫大小超過10MB,但是用proguard編出來的class.min.jar只有1.7MB,8.xMB的scala-runtime.jar一大票沒用到的功能都被移掉了。
這麼好的功具當然也有他的問題存在,proguard是使用靜態分析的方式,去追縱看有那些程式碼會被執行到,有那些程式碼是不會被碰到的可以被移除的;但由於Jackson是使用Reflection的方式去取得物件的屬性及是用reflection的方式去生成物件,因此,這些行為並不會被proguard偵測到,反而是被認為是dead code而被移除掉。
另外proguard會把變數名稱改寫,這也是跟jackson不相容的地方,當getXxxx被改寫成gY,jackson自然無法知道這是Xxxx的getter,因此若是要在proguard的環境下使用jackson,obfuscator是要被關掉的。
底下這邊,是我在台北積水地圖內,用的proguard設定檔
ProGuard is a free Java class file shrinker, optimizer, obfuscator, and preverifier. It detects and removes unused classes, fields, methods, and attributes. It optimizes bytecode and removes unused instructions. It renames the remaining classes, fields, and methods using short meaningless names. Finally, it preverifies the processed code for Java 6 or for Java Micro Edition.
在Mobile App開發的時候,多會用proguard把沒有用到的程式碼濾除,並把變數名稱跟函式名稱用更精簡的字串來取代,這樣編出來的程式會更小,以我用Scala開發的Android程式,使用的的函式庫大小超過10MB,但是用proguard編出來的class.min.jar只有1.7MB,8.xMB的scala-runtime.jar一大票沒用到的功能都被移掉了。
這麼好的功具當然也有他的問題存在,proguard是使用靜態分析的方式,去追縱看有那些程式碼會被執行到,有那些程式碼是不會被碰到的可以被移除的;但由於Jackson是使用Reflection的方式去取得物件的屬性及是用reflection的方式去生成物件,因此,這些行為並不會被proguard偵測到,反而是被認為是dead code而被移除掉。
另外proguard會把變數名稱改寫,這也是跟jackson不相容的地方,當getXxxx被改寫成gY,jackson自然無法知道這是Xxxx的getter,因此若是要在proguard的環境下使用jackson,obfuscator是要被關掉的。
底下這邊,是我在台北積水地圖內,用的proguard設定檔
proguardOption in Android :=
("-dontoptimize -dontpreverify -dontobfuscate" // shrinking only
:: "-dontskipnonpubliclibraryclassmembers" // keep Jackson's internal classes
:: "-dontskipnonpubliclibraryclasses" // keep Jackson's internal classes
:: "-keepattributes *Annotation*." // keep Jackson Json Annotations.
:: "-keep class org.codehaus.jackson.**"
:: "-keep class com.bluetangstudio.android.model.**"
:: "-keep class com.bluetangstudio.searchcloud.client.**"
:: """-keep class com.bluetangstudio.searchcloud.client.** {
(...);
public static ** valueOf(...);
}"""
:: """-keep class com.bluetangstudio.** {
void set*(***);
void set*(int, ***);
boolean is*();
boolean is*(int);
*** get*();
*** get*(int);
}"""
:: """-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}"""
:: """-keepclassmembers class * {
** MODULE$;
}"""
:: "-keep public class org.xml.sax.EntityResolver"
:: "-keep public class scala.Either"
:: "-keep public class scala.Function1"
:: "-keep public class scala.Function2"
:: "-keep public class scala.Tuple2"
:: "-keep public class scala.collection.Iterable"
:: "-keep public class scala.PartialFunction"
:: "-keep public class scala.collection.Seq"
:: "-keep public class scala.collection.TraversableOnce"
:: "-keep public class scala.collection.generic.CanBuildFrom"
:: "-keep public class scala.collection.immutable.Map"
:: "-keep public class scala.collection.immutable.List"
:: "-keep public class scala.collection.mutable.StringBuilder"
:: "-keep public class scala.Predef$$less$colon$less"
:: "-keep public class scala.math.Numeric"
:: "-keep public class scala.math.Ordering"
:: "-keep public class scala.reflect.ClassManifest"
:: "-keep public class scala.runtime.IntRef"
:: "-keep public class scala.runtime.BooleanRef"
:: "-keep public class scala.runtime.AbstractFunction1"
:: "-keep public class * extends android.app.Activity"
:: "-keep public class * extends android.app.Application"
:: "-keep public class * extends android.app.Service"
:: "-keep public class * extends android.appwidget.AppWidgetProvider"
:: "-keep public class * extends android.content.BroadcastReceiver"
:: "-keep public class * extends android.content.BroadcastReceiver"
:: "-keep public class * extends android.app.backup.BackupAgentHelper"
:: "-keep public class * extends android.content.ContentProvider"
:: "-keep public class * extends android.view.View"
:: "-keep public class * extends android.preference.Preference"
:: Nil
) mkString " "
Data Modeling With Jackson Json and Scala - lazy val
Scala中有個好用的功能是 lazy val - lazy initialized variable ,然而,Scala在實作這功能時,是會加入一個
若是這欄位沒有被從生成的 json 中替除的話,那麼,讀回來的 scala object 中,所有的lazy val,都會仍是 null ,不會被初始化。
這個問題,只有做好unit-testing可以避免,因為這個欄位是 public 的,所以不需要setter or constructor, Jackson 就可以直接把值設定進去,只用unit-test仔細檢查生成的,確定沒有預料之外的欄位被寫入到 json 中,才可以免去後來在 runtime 發生不可預料的問題。
至於怎麼去除這欄位呢,只要在class上標上
public volatile int bitmap$0;
若是這欄位沒有被從生成的 json 中替除的話,那麼,讀回來的 scala object 中,所有的lazy val,都會仍是 null ,不會被初始化。
這個問題,只有做好unit-testing可以避免,因為這個欄位是 public 的,所以不需要setter or constructor, Jackson 就可以直接把值設定進去,只用unit-test仔細檢查生成的,確定沒有預料之外的欄位被寫入到 json 中,才可以免去後來在 runtime 發生不可預料的問題。
至於怎麼去除這欄位呢,只要在class上標上
@JsonIgnoreProperties({"bitmap$0"})即可
Data Modeling With Jackson Json and Scala - Polymorphic Types
關於 Jackson Json 對多型的支援,原開發者已經有一篇 blog 很清楚的解釋該怎麼做,因此,我只寫下在 scala 上面要注意的眉角。
首先,我先寫下理想中,Jackson在Scala上該怎麼使用,在來談,為什麼不能這麼用
在 scala 2.8.0 上面,我碰上的問題是不能在annotation內使用 java enum 如:
在 scala 2.9.1 上,上面的問題消失了,不過碰上另一個問題,當一個 annotation 上面有兩個 java enum,scala compiler 及 java compiler 都不會吐出錯誤,但是annotations從bytecode中消失了,SI-5165中紀錄了這問題。
不管怎樣,在現在這時間,若是你想用Jackson + Scala來處理多型,那麼,最頂層類別,一定要用Java來寫,才不會碰上上述兩個問題。
首先,我先寫下理想中,Jackson在Scala上該怎麼使用,在來談,為什麼不能這麼用
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(Array(
new JsonSubTypes.Type(name = "flood", value = classOf[Flood]),
new JsonSubTypes.Type(name = "mountainslide", value = classOf[MountainSlide])
))
trait Disaster {}
case class Flood @JsonCreator()(
@BeanProperty @JsonProperty("event") event: String,
@BeanProperty @JsonProperty("address") address: String
) extends Disaster
case class MountainSlide @JsonCreator()(
@BeanProperty @JsonProperty("event") event: String,
@BeanProperty @JsonProperty("landmark") landmark: String
) extends Disaster
在 scala 2.8.0 上面,我碰上的問題是不能在annotation內使用 java enum 如:
use = JsonTypeInfo.Id.NAME 在 scala 2.9.1 上,上面的問題消失了,不過碰上另一個問題,當一個 annotation 上面有兩個 java enum,scala compiler 及 java compiler 都不會吐出錯誤,但是annotations從bytecode中消失了,SI-5165中紀錄了這問題。
不管怎樣,在現在這時間,若是你想用Jackson + Scala來處理多型,那麼,最頂層類別,一定要用Java來寫,才不會碰上上述兩個問題。
Data Modeling With Jackson Json and Scala
我用Scala有一年半左右,第一次看brianhsu介紹 case class ,就對 Scala 的簡潔著迷,當然,經過了一年半後,許多的面紗揭開後,Scala在與Java函式庫整合的小問題就跑出來了,接下來,我會寫寫怎麼用Jackson Json及Scala來設計一個Json WebService API的Model Objects.
Scala的case class在經過Scala compiler轉換後,就會生成一個POJO,例如這樣一個簡單的case class
會被轉換成如下的Java Code
這個POJO跟一般POJO的不同處在於
這在一般使用上,不是問題,反而是優點,然而,當於 Java binding framework 如 Jackson Json 或 JAXB 要做整合時,就變成問題了,原因在於,多數的 java binding frameworks 在將資料轉回Java Object時,多是呼叫沒代參數的default constructor,然後再呼叫setter(s)把物件的屬性傳進去。
另一個問題則是,多數的java binding frameworks在偵側那些屬性是serializable時,要麻是用java annotation一個個屬性去標,要不然就是用 refection 去找 getXxxx()
而好死不死的plain scala case class沒有辦法滿足上面兩個條件,因此,我們要想辦法繞過這兩個問題。第二個問題比較好解,只要在屬性上標上 @scala.reflect.BeanProperty ,Scala compiler就會自動幫你把你的屬性加上 getXxxx() 及 setXxxx(...)。
第一的問題,在Jackson Json的解法是用 @JsonCreator 去指定不代變數default constructor的替代品,然而,在這邊我們須要把每一個屬性的名稱,用 @JsonProperty("xxx") 標在 constructor 的參數上,這是因為,Java compiler在編譯時,會把 parameter 的名稱擦去,如 public Disaster(String event) 在經過編譯後的 bytecode 中只剩下 public Disaster(String p1),因此我們須要用java annotation來強制幫參數取個名稱,這樣,當Jackson在把 Json String 轉成Object時,才知道該把那個json object的屬性對到java object的那個屬性之上。
當我們的Scala case class已經是Jackson Json Serializable後,我們要怎麼樣把scala case class object轉成json string再轉回來呢,請看底下的例子。
這邊我們會碰上Scala較Java語法上比較沒有那麼漂亮的地方,在Java裡,若是函式支援generic且又有個Class的參數,那麼,我們不用把傳兩次進去這個函式,如
因此在Scala的這邊,我會寫個JsonSerializer來把 scala 這邊的語法弄漂亮些
最後,
case class
Scala的case class在經過Scala compiler轉換後,就會生成一個POJO,例如這樣一個簡單的case class
case class Disaster(event: String)
會被轉換成如下的Java Code
public class Disaster
implements ScalaObject, Product, Serializable
{
public String event()
{
return event;
}
public int hashCode()
{
return ScalaRunTime$.MODULE$._hashCode(this);
}
public String toString()
{
return ScalaRunTime$.MODULE$._toString(this);
}
public boolean equals(Object obj)
{
//removed
}
public Disaster(String event)
{
this.event = event;
super();
scala.Product.class.$init$(this);
}
private final String event;
}
這個POJO跟一般POJO的不同處在於
- immutable object,沒有辦法去更改這物件的屬性
- getter的命名不是Java通用的 getXxxx() 而是 xxxx()
這在一般使用上,不是問題,反而是優點,然而,當於 Java binding framework 如 Jackson Json 或 JAXB 要做整合時,就變成問題了,原因在於,多數的 java binding frameworks 在將資料轉回Java Object時,多是呼叫沒代參數的default constructor,然後再呼叫setter(s)把物件的屬性傳進去。
另一個問題則是,多數的java binding frameworks在偵側那些屬性是serializable時,要麻是用java annotation一個個屬性去標,要不然就是用 refection 去找 getXxxx()
而好死不死的plain scala case class沒有辦法滿足上面兩個條件,因此,我們要想辦法繞過這兩個問題。第二個問題比較好解,只要在屬性上標上 @scala.reflect.BeanProperty ,Scala compiler就會自動幫你把你的屬性加上 getXxxx() 及 setXxxx(...)。
case class Disaster(@BeanProperty event: String)
第一的問題,在Jackson Json的解法是用 @JsonCreator 去指定不代變數default constructor的替代品,然而,在這邊我們須要把每一個屬性的名稱,用 @JsonProperty("xxx") 標在 constructor 的參數上,這是因為,Java compiler在編譯時,會把 parameter 的名稱擦去,如 public Disaster(String event) 在經過編譯後的 bytecode 中只剩下 public Disaster(String p1),因此我們須要用java annotation來強制幫參數取個名稱,這樣,當Jackson在把 Json String 轉成Object時,才知道該把那個json object的屬性對到java object的那個屬性之上。
case class Disaster @JsonCreator()(
@BeanProperty @JsonProperty("event") event: String
)
當我們的Scala case class已經是Jackson Json Serializable後,我們要怎麼樣把scala case class object轉成json string再轉回來呢,請看底下的例子。
val disaster = new Disaster("88 typhoon")
val mapper = new org.codehaus.jackson.map.ObjectMapper()
val json = mapper.writeValueAsString(disaster)
assert(json === """{"event": "88typhon"}"""}
val copy = mapper.readValue[Disaster](json, classOf[[Disaster])
assert(copy === disaster)
這邊我們會碰上Scala較Java語法上比較沒有那麼漂亮的地方,在Java裡,若是函式支援generic
Disaster copy = mapper.readValue(json, Disaster.class)
因此在Scala的這邊,我會寫個JsonSerializer來把 scala 這邊的語法弄漂亮些
object JsonSerializer {
private val mapper = new ObjectMapper
def fromJson[T <: AnyRef](jsonString: String)(implicit m: Manifest[T]): T = {
mapper.readValue(jsonString, m.erasure).asInstanceOf[T];
}
}
// readValue the scala way.
val disaster = JsonSerializer.fromJson[Disaster](json)
最後,
@JsonCreator不一定是要標在constructor上面,他也可以標在static method上面,這樣一來,可以讓你做到scala constructor無法做到的一些處理case class Disaster(
@BeanProperty event: String,
location: Option[GeoPoint]) {
/** helper method for jackson json */
def getLocation = location.getOrElse(null)
}
object Disaster {
@JsonCreator
def newInstance(
@Property("event") event: String,
@Property("location") location: GeoPoint): Disaster = {
val locationOption = if (location == null) {
None
} else {
Some(location)
}
return new Disaster(event, locationOption)
}
}
下一回,我們會看,如何讓Jackson Json能夠對多型的物件做處理
台北市政府公開資料(三)- 台北積水地圖
花了三個禮拜,學習怎麼開發Android程式,在沒買開發機的狀況下,在模擬器上完成了我的第一個Android App - 台北積水地圖


資料來源一樣是從台北市政府所來,原始資料檔可從『臺北市歷年積水紀錄圖』下載。我把資料檔的座標系轉換(TW67 -> WSG84)後,就塞到跟前一回一樣的後端去做處理,剩下的就都是前端的工作了。
在前端UI開發上,依以前開發的習慣,先把wireframe(假UI)畫好才動工,找了找一些畫UI的工具,最後買了keynotopia出的工具,這傢伙也是個奇人,開start-up不到三小時就開始獲利;在畫假UI時就發現,傳統上我們用點來代表一個物件,在畫地圖(羞~)時,並不能合理的表現許多地理圖型。
然而,若是我們要把地理圖型直接畫在地圖上讓使用者直接點開,那麼,我們又要處理重疊的狀況,這在淹水地圖上就特別麻煩,因為常淹水的區塊會互相重疊多次,或像納利颱風,一次淹掉了一大塊的區塊,在處理點選時,我們要怎麼知道使用者點選的是那一層的資料?
而在顯示上,要怎麼處理重疊的區塊,讓使用者知道,某個地點曾淹水多次?一個選項是用顏色來做,重疊的部份用加深的顏色來顯式;最後,我們還是回歸傳統的點座標的方式,來表示淹水的區域,等使用者點開後,才用色塊畫出實際淹水的地區。等下次改版時,等我對Android 2D再熟一些時,或許會嘗試用顏色的方式來做呈現。
接下來,我會在有空的時候,再開發一個程式,把台北市的土地分區(住一、住二、商用...)資料,跟土地的地號資料,做成個台北都更地圖,若各位有類似的需求,請與我聯絡
資料來源一樣是從台北市政府所來,原始資料檔可從『臺北市歷年積水紀錄圖』下載。我把資料檔的座標系轉換(TW67 -> WSG84)後,就塞到跟前一回一樣的後端去做處理,剩下的就都是前端的工作了。
在前端UI開發上,依以前開發的習慣,先把wireframe(假UI)畫好才動工,找了找一些畫UI的工具,最後買了keynotopia出的工具,這傢伙也是個奇人,開start-up不到三小時就開始獲利;在畫假UI時就發現,傳統上我們用點來代表一個物件,在畫地圖(羞~)時,並不能合理的表現許多地理圖型。
然而,若是我們要把地理圖型直接畫在地圖上讓使用者直接點開,那麼,我們又要處理重疊的狀況,這在淹水地圖上就特別麻煩,因為常淹水的區塊會互相重疊多次,或像納利颱風,一次淹掉了一大塊的區塊,在處理點選時,我們要怎麼知道使用者點選的是那一層的資料?
而在顯示上,要怎麼處理重疊的區塊,讓使用者知道,某個地點曾淹水多次?一個選項是用顏色來做,重疊的部份用加深的顏色來顯式;最後,我們還是回歸傳統的點座標的方式,來表示淹水的區域,等使用者點開後,才用色塊畫出實際淹水的地區。等下次改版時,等我對Android 2D再熟一些時,或許會嘗試用顏色的方式來做呈現。
接下來,我會在有空的時候,再開發一個程式,把台北市的土地分區(住一、住二、商用...)資料,跟土地的地號資料,做成個台北都更地圖,若各位有類似的需求,請與我聯絡
Subscribe to:
Posts (Atom)
