2013年10月26日 星期六

ARM vs x86 - 關於 Android 的硬體平台和相關的屬性設定


自 Android 問世以來,ARM 架構 CPU 一直是這個領域裡翹楚。不管是 Qualcomm, nVidia, MTK 還是 Samsung 的 Exynos CPU,幾乎都是以 ARM 架構所開發的。而 PC/NB 的老大 Intel,這幾年來看著 PC 和 NB 市場慢慢的走下坡,一直想重返 Mobile Device 這塊市場,也積極的推出新的產品,想要搶食這塊被啃食已久的大餅。尤其今年的 BayTrail 系列 Processor 來勢洶洶,砸下重金的招兵買馬,想和一直以來 PC/NB 的好朋友們一起合作推 Intel Inside 的平板。到底市場會有甚麼樣的反應頗令人期待,不過研發人員倒是已經為了 x86 架構的平台和 Android Market App 的相容性而大傷腦筋。


Instruction Set - ARM and x86

資工人學過 Computer Architecture 的人都知道,CPU Instruction set (指令集) 一直有 RISC 和 CISC 之爭

RISC = Reduced Instruction Set
CISC = Complex Instruction Set

RISC 界的代表就是 ARM,代表有 Qualcomm, nVidia, and MTK
CISC 界的代表就是 x86,代表有 Intel and AMD

通常簡化的說法是
CISC 的指令集複雜但功能比較強大,但也比較耗電
RISC 的指令集簡單但相對來說較省電
這樣的說法在這裏我們先不討論 (因為...  我也不是很清楚,之後再來找相關資料)
但有一點是非常清楚的,不同架構的 CPU 是不相容的
例如,以 ARM 指令集編譯的 binary,是無法在 x86 架構的 CPU 上執行

而 ARM 架構的 CPU 也因為不斷的演進,32-bit ARM 指令集又可大致分成
  • ARMv5
  • ARMv6
  • ARMv7
特別值得一提的是 ARMv7 開始加入了 FPU 硬體,也就是浮點運算的功能,
在 v5 和 v6 必須靠軟體的方式處理浮點運算,因此 v7 在浮點運算上的效能相對於 v5 和 v6 增進很多。
另外 v7 也加入了對 multi-cores 的 support。

每個版本又有不同的 variants,可參考下表:

ArchitectureBit
width
Cores designed by ARM HoldingsCores designed by 3rd partiesCortex profile
ARMv5
32
ARM7EJ, ARM9E, ARM10EXScale, FA626TE, Feroceon, PJ1/Mohawk
ARMv6
32
ARM11
ARMv6-M
32
ARM Cortex-M0, ARM Cortex-M0+, ARM Cortex-M1Microcontroller
ARMv7-M
32
ARM Cortex-M3
Microcontroller
ARMv7E-M
32
ARM Cortex-M4
Microcontroller
ARMv7-R
32
ARM Cortex-R4, ARM Cortex-R5, ARM Cortex-R7
Real-Time
ARMv7-A
32
ARM Cortex-A5, ARM Cortex-A7, ARM Cortex-A8,
ARM Cortex-A9, ARM Cortex-A12, ARM Cortex-A15
Krait, Scorpion, PJ4/Sheeva, Apple A6 / A6X (Swift)
Application
ARMv8-A
64
ARM Cortex-A53, ARM Cortex-A57X-Gene, Denver, Apple A7 (Cyclone)
Application
*上表的資料來源為 wiki 網站介紹 Arm Architecture 所截錄


Houdini -  A arm-to-x86  Translator

目前 Android Market 上還是以支援 ARM 的 App 居多
可能有人以為 Android App 是以 Java 語言開發的,所以只要有 Dalvik runtime 不就可以跨平台嗎?為什麼以 Java 為基礎的 App 還是跟平台有關?這就得提到 JNI and Android NDK 了。

JNI (Java Native Interface) 是一個特殊介面,他提供了 Java 一種方式可以呼叫 Native library 所提供的 functions。一般來說,native code 即是針對執行平台的作業系統和指令集所編譯而成的。比起 Java,就是少了 Dalvik runtime system 要 translate Dalvik bytecode 的 overhead,所得到的優勢就是 Performance 較好。因此一些有效能考量的應用常常會以 native library 的方式實作,以避免 Java 的 overhead 而拖累效能。Android NDK 便是 Android 提供的開發工具,用以開發 Android App 所要呼叫的 Native library。

依此,我們可以把 Android App 分成三種
  1. Android App with pure Java (完全用 Java)
  2. Android App + ARM native library
  3. Android App + x86 native library
當然也有結合 (2) + (3) 的。不過,目前還是以 (1) and (2) 的比例較高。
而且,遊戲類的 App 當中 (2) 的比例是非常高。
因此逼得 Intel 必須提供 Runtime translator,讓 (2) 這類的 App 能夠在 Intel 平台上運作。Houdini 就是為了這樣的目的而產生的一種 Runtime translator,他會在收到 App JNI request 時載入 ARM 的 native library,並動態的將 ARM instruction set 轉成 x86 instruction set。


Performance Index

上一段提到 Houdini 是一個 Runtime translator,聰明的你應該就意會到,他多多少少會產生 overhead 而影響的系統的效能。下面列出的是 App with ARM native library 在 x86 平台上執行會影響的部分:

App compatibility
其實現階段的 Houdini 已經能夠支援幾乎大部分的 Anrdoid App,但現實社會就是這麼的不完美,總是會有一些難搞的 App 不相容。
(但即使是 ARM 平台的 Android 也沒辦法做到 100%啊)
    App performance
    既然是 Translator,就表示他必須花費 resource (CPU+memory) 去做 arm to x86 轉換的動作,不過 Houdini 經過 Intel 的調教下,目前看起來在同樣的 App 下,使用 x86 native library 和使用 arm native library 的效能差異幾乎看不太出來。

    App launch time
    Launch time 的影響是因為當 ARM Native Library 被呼叫時,Houdini必須先把 library 載入並 parsing。
    Launch time 也是目前看來影響最多的。

    Power consumption impact
    由於多做了一些額外的事情,就有可能多消耗系統的功率,不過實際測試並不明顯,大約 2 ~ 3% 左右。


    Related Android Properties and Settings
    • ro.product.cpu.abi
    • ro.product.cpu.abi2
    • os.arch
    ABI 是 Appplication Binary Interface,簡單說就是系統所支援的指令集。
    Android 裡可設定 Primary ABI (abi) 和 Secondary ABI (abi2) 兩種
    Primary ABI 代表系統運作時主要使用的 binary code,不可為空白。
    Secondary ABI 代表系統運作時次要使用的 binary code,可為空白。

    以最通行的 ARM 架構為例  通常的設定如下:

    Example of ARM platform
    ro.product.cpu.abi = armeabi-v7a
    ro.product.cpu.abi2 = armeabi
    os.arch = arm

    Example of Intel x86
    ro.product.cpu.abi = x86
    ro.product.cpu.abi2 = armeabi
    os.arch = x86

    abi 和 abi2 的設定非常重要,他影響了 Android Package Manager 安裝 APK 時 native library 的安裝。

    從剛剛的討論中可發現,其實大部分的時候只需要處理好 abi and abi2 即可,
    但我們發現有時候某些 app 還會 check os.arch,推測可能是 App 對於非 ARM 架構的平台有可能有問題,用來防堵 x86 架構。


    小 心 得

    在目前還是以 ARM 平台為主的嵌入式系統,Intel 試圖以他熟悉的 x86 架構重返行動裝置系統,看起來似乎正掀起一片片漣漪。然而,這個市場並不是比誰的口袋深而已,Qualcomm 一直在高端裝置上有著非常好的成績,手上握有的 3G/4G 專利可以讓他躺著幹;而 MTK 在中低階市場上已經站穩腳步,不只大陸白牌廠,甚至越來越多知名手機品牌已採用其方案。其實平台方案是否能對的準系統廠商的胃才是重點,否則如果系統廠商沒辦法用 Intel 的平台讓消費者買單,砸再多錢也只是投向大海,期待 Intel 能夠建立好自己的生態鏈,畢竟這樣受益的也是消費者。

    2013年6月7日 星期五

    如何在 Android 上開發使用 Google Map V2 的 App

    寫於 2013/06/06 

    因為想在 Android App 上玩玩看 Google Map 的應用,所以開始研究如何把 Google Map 嵌入自己的 AP裡,整理自己研究後的心得和步驟如下:

    1. 開發環境安裝

    除了標準的 IDE 環境外{Eclipse},你還需要以下兩個 extra packages才能在你的環境裡開發 Google Map V2 相關的App,這兩個 packages 都可以透過 Android SDK Manager 下載到你的開發環境裡
    • Android support library
    • Google Play Service
    請開啟 Android SDK Manager,勾選紅色框起的兩個項目安裝


    2. 開啟 Google Map API 服務及 Key 的申請和安裝

    你需要到 Google Console 上作兩件事情
    • 開啟 Google Map API V2 服務
    • 用你的憑證所產生的SHA-1註冊你的 App,並得到 Google Map API V2 的 Key
    用你的 google 帳號 login Google Console 之後,你檢視他的服務項目可以發現,已經不能再用 Google Map API V1 的服務了


    接下來註冊你的 App 並取得 Key這個步驟比較麻煩,首先你必須要準備一個 SHA-1 Hash ,而這個 Hash 必須是從用來 Sign 你的 APK 的憑證所產生的,看了一堆文件都說要用 Java JDK 的 Keytool 來產生 SHA-1 Hash。但如果你是使用 Eclipse ,這邊有一個簡單的方式:

    首先點選 "Window" -> "Preferences" 叫出 Preferences 對話視窗,在對話視窗左邊選擇 "Android" -> "Build"




    Debug Keystore 是指你用來 sign APK 的 Key 存放位置
    SHA-1 fingerprint 的內容即是你要拿去 Google Console 申請 Google Map API V2 的 Key 用的
    請注意,debug key 是 debug 階段拿來用的,一般正式出貨上架時會用另一組正式的 Key 來 Sign APK。因此,相對應的 API Key 也用 release Key 所產生的 SHA-1去申請註冊。

    3. 新增新的專案,並匯入Google Play Service

    開啟 Eclipse 已準備新增一個 Android Application project,這時候你必須要了解 API Level 和 Google Map API 版本之間的關係。在 API Level 12 之後才支援 V2,所以你必須考慮好你所支援的 Android 版本,當你的 App 僅支援 API Level 12 之後,基本上只需要考慮一種狀況。如果你必須考慮運行於 API Level 11或更早之前的系統,仍然是可以的,只是必須多一些考量,這在第四點撰寫程式的時候會提到。

    Okay,你的專案已經新增好了,接下來在寫程式前要做好下面兩件事:
    • Import Google Play Service
    • Add Google Play service library in your project build environment 

    Press "File" -> "Import..." 開啟 Import 的對話視窗:


    選取 import 的 type 是 "Existing Android Code into Workspace",按下 "Next" button 會跳出 import 視窗,按下 "Browse" button 選取你剛剛安裝的 Google Play Service library,其路徑是在你的 SDK 安裝目錄裡的 "\extras\google\google_play_services\libproject\google-play-services_lib"。


    選完之後,即會出現如上圖紅色框框裡的 Google Play Service專案,勾選之後按下 "Next" button 及完成了 Google Play Service 的匯入。


    Press "Project" -> "Properties" 開啟 Android project properties 如下圖,選擇 "Android"。
    在視窗左下角按"Add" button 選擇要加入的 library


    你會看到綠色勾勾,如果哪時候看到的是紅色叉叉就代表有問題。
    記住 "Is Library" 那個 option 千萬不能勾選,那代表你的專案最後是要 build 成 library。
    按下 OK button 之後,代表你的 project build 已經可以使用指定的 library了。

    4. 撰寫程式碼

    這邊只是談論如何初步的把 Google Map 嵌入到你的 App,其他進階的應用待以後有空的時候再摸索。
    基本上分成兩部分:
    • 在 AndroidManifest.xml 中宣告需要的 permission 和 Google Map API Key
    • 在 Activity Layout file 中宣告 Google Map element 的位置

    關於 AndroidManifest.xml,範例如下:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.test.Maptest"
        android:versionCode="1" android:versionName="1.0" >
        <uses-sdk
            android:minSdkVersion="12"
            android:targetSdkVersion="17" />
        <permission
            android:name="com.test.Maptest.permission.MAPS_RECEIVE"
            android:protectionLevel="signature"/>
        <uses-permission android:name="com.test.Maptest.permission.MAPS_RECEIVE"/>
        <uses-permission android:name="android.permission.INTERNET"/>
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
        <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
        <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
        <uses-feature
            android:glEsVersion="0x00020000"
            android:required="true"/>

        <application
            android:allowBackup="true"
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name"
            android:theme="@style/AppTheme" >
            <activity
                android:name="com.test.Maptest.MainActivity"
                android:label="@string/app_name" >
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <meta-data
                android:name="com.google.android.maps.v2.API_KEY"
                 android:value="YOUR_API_KEY"/>

        </application>
    </manifest>







    Layout file:

    <?xml version="1.0" encoding="utf-8"?>
    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/map"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    class="com.google.android.gms.maps.MapFragment"
    xmlns:map="http://schemas.android.com/apk/res-auto"
    map:cameraBearing="45"
    map:cameraTargetLat="25.033611"
    map:cameraTargetLng="121.565000"
    map:cameraTilt="0"
    map:cameraZoom="13"
    map:mapType="normal"
    map:uiCompass="true"
    map:uiRotateGestures="true"
    map:uiScrollGestures="true"
    map:uiTiltGestures="true"
    map:uiZoomControls="false"
    map:uiZoomGestures="true"/>



    Reference

    Google Developer - Google Maps Android API v2
    Showing current location in Google Maps using API V2 with SupportMapFragment

    後 記

    為了搞定 Google Map API V2,前前後後摸索了很久,大部分的時間都花在只為了搞定 "gms.maps.MapFragment" ClassNotFound exception 這個問題。看了非常多的論壇討論,發現所有該加的都加了,也做了檢查再檢查,還是無解。最後大絕招是重開一次專案把上面的 3 和 4 重做一次,發現 ClassNotFound 的問題解決了!!

    我推測應該是在一開始摸索的時候東試試、西試試的,把某些系統的設定檔搞壞了,
    導致 apk build 出來的時候相關 GMS Map class 的參考路徑是錯的...