分類
Android Uncategorized

Generated binding classes (產生綁定類別)

前言

Data Binding 會產生綁定類別(binding class),用來存取佈局變數(layout’s variable)和視圖(View)。
以下描述了如何建立及客製化綁定類別。
綁定類別將佈局變量與佈局中的視圖連結起來,綁定類別的名稱和 package 可以自行定義。所有的綁定類別都繼承自 ViewDataBinding 類別。
每個 layout file 都會有一個對應的綁定類別。預設,類別的名稱會根據佈局文件的名稱,將其轉換為 Pascal 大小寫並添加 Binding。
若 layout 的名稱為 activity_main.xml,則對應的綁定類別為 ActivityMainBinding。
此類別包含佈局屬性(如user variable)到佈局視圖的所有綁定內容,並知道如何為綁定表達式指定值。
 

Create a binding object

在對佈局進行填充之後,應該快速的創建綁定對象(binding object),以確保在綁定到佈局中具有表達式的視圖之前不會修改視圖層次結構。
將對象綁定到佈局的最常用方法是使用綁定類別上的靜態方法。
可以透過使用綁定類別的 inflate 方法來擴展視圖層次結構並將對象綁定到該層次結構。如下

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater());
}

 
除了 LayoutInflater 對象之外,還有一個替換的 inflate 方法,它接受 ViewGroup對象,如下

MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater(), viewGroup, false);

如果使用不同的機制對佈局進行填充,則可以單獨綁定,如下

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有時候沒辦法事先知道綁定類型。在這種情況下,可以使用 DataBindingUtil 類創建綁定,如下

View rootView = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bind(viewRoot);

若開發者是在 Fragment, ListView, RecyclerView adapter 中使用 data binding item
則可以使用綁定類別或 DataBindingUtil 的 inflate方法。如下

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
// or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

 

具有 ID 的視圖

Data Binding 會在綁定類別中建立一個不可變的欄位,該欄位會對應於 layout 檔案中每個具有 id 的 View。如下 Data Binding 會建立 firstName 和 lastName 欄位

<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
        android:id="@+id/firstName"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
        android:id="@+id/lastName"/>
   </LinearLayout>
</layout>

Data Binding 會在單次傳遞中從視圖層次結構中提取具有 ID 的視圖。這個機制比為佈局中的每個視圖調用 findViewById 方法都要快。ID 對於 Data Binding 並不是必要的,但仍有些實體需要從 code 存取視圖。
 

Variables

Data Binding 會為每個宣告在 layout 中的變數建立存取方法(setter and getter)。如下,將會為 user, image, note 變數建立存取方法。

<data>
   <import type="android.graphics.drawable.Drawable"/>
   <variable name="user" type="com.example.User"/>
   <variable name="image" type="Drawable"/>
   <variable name="note" type="String"/>
</data>


ViewStubs

與普通視圖不同,ViewStub 物件從一個不可見的視圖開始。
當它們被顯示或被明確告知要填充時,它們會通過填充另一個佈局來替換自己的佈局。
由於 ViewStub 基本上會從視圖層次結構中消失,因此綁定對像的視圖也必須消失以允許垃圾回收聲明。
因為視圖是不可變的,所以 ViewStubProxy 對象取代了生成綁定類中的 ViewStub,使您可以在 ViewStub 存在時訪問它,並在 ViewStub 填充時訪問視圖層次結構
當填充另一個佈局時,必須為該佈局建立綁定。因此 ViewStubProxy 必須監聽  ViewStub,並在需要時建立綁定。在同一時間內只能有一個監聽器存在,ViewStubProxy 予許開發者設定 OnInflateListener,該監聽器將在建立綁定之後被呼叫。
 

Immediate Binding

當變量或可觀察對象發生變化時,綁定將會被排程更改在下一幀之前。但是,有時必須立即執行綁定。要強制執行,可以使用 executePendingBindings 方法。
 
 

進階綁定

動態變數

動態變數適用於無法得知特定的綁定類別的時候。如當一個 RecyclerView.Adapter 對非特定的佈局進行操作時並不知道特定的綁定類別,但它還是必須在呼叫 onBindViewHolder 方法時指定綁定値。
在下面的範例中,RecyclerView 綁定的所有佈局都有個 item 變數。而 BindingHolder 物件具有 getBinding 方法,該方法可以回傳 ViewDataBinding 基本類別。

public void onBindViewHolder(BindingHolder holder, int position) {
    final T item = mItems.get(position);
    holder.getBinding().setVariable(BR.item, item);
    holder.getBinding().executePendingBindings();
}

Data Binding 在 module package 會產生一個名為 BR 的類別,該類別包含用於數據綁定的資源的 ID。在上一個範例中 BR.item 是自動產生的。
 

背景執行緒

開發者可以在背景執行緒中更改數據模型(data model),只要它不是集合即可。Data Binding 會判斷每個變數/屬性以避免任何並發問題。
 

客製化綁定類別名稱

在預設情況下綁定類別的名稱是根據其相關佈局名稱而來,主要規則是將佈局名稱的底線去除並讓首字改為大寫,最後再加上 Binding,而綁定類別的位置會放置於 module package 的 databinding 資料夾下。 如佈局名稱為 contact_item.xml,其綁定類別為 ContactItemBinding。若 module package 為 com.example.my.app 則綁定類別的位置為 com.example.my.app.databinding。
可以透過 data 元素的 class 屬性來改變綁定類別的名稱或位置。
如下面的內容將會產生名為 ContactItem 綁定類別。

<data class="ContactItem">
…
</data>

也可以使用完整名稱來指定綁定類別的位置。如下
將產生 ContactItem 綁定類別,其位置為 com.example

<data class="com.example.ContactItem">
…
</data>

 

分類
Android Uncategorized

Splash Activity

1.編輯 value/styles.xml 加入以下內容

<resources>
...
    <style name="AppTheme.Launcher">
        <item name="android:windowBackground">@drawable/launch_screen</item>
        <!-- Optional, on Android 5+ you can modify the colorPrimaryDark color to match the windowBackground color for further branding-->
        <!-- <item name="colorPrimaryDark">@android:color/white</item> -->
    </style>
...
</resources>

其中 @drawable/launch_screen 目前還沒有,下一步製作。

2.在 drawable 新增 launch_screen.xml,內容如下

<?xml version="1.0" encoding="utf-8"?>
<!-- The android:opacity=”opaque” line — this is critical in preventing a flash of black as your theme transitions. -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:opacity="opaque">
  <!-- The background color, preferably the same as your normal theme -->
  <item android:drawable="@android:color/white"/>
  <!-- Your product logo - 144dp color version of your app icon -->
  <item>
    <bitmap
      android:gravity="center"
      android:src="@drawable/your_logo"/>
  </item>
</layer-list>

其中 @drawable/your_logo 就是你想顯示的 logo 圖示

3. SplashActivity

該 Activity 除了在 onCreate 作了特殊處理之外,並沒有其它不同。如下

public class LogoActivity extends Activity {
...
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    setTheme(R.style.AppTheme);
    super.onCreate(savedInstanceState);
  }
...

 

4. 在 AndroidManifest.xml 讓 LogoActivity 套用 AppTheme.Launcher

    <activity
      android:theme="@style/AppTheme.Launcher"
      android:name=".logo.LogoActivity">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>

 

分類
Android

把安裝在手機的 apk 複製到本地端

0.開啟目前平台提供的最基本的Terminal tool
windows使用power shell(不要使用git bash),ubuntu使用bash,並移動到android sdk/platform-tools/
1.列出所有安裝的 package
(以下以Twitter app為範例,請確認已安裝Twitter)

adb shell pm list packages

Note:
在列出的 package 中尋找目標 apk 的 package,通常 package 和目標 apk 會有關連
若使用 adb 出現 command not found, 請指定 adb 的絕對路徑, (e.g., /android-sdk/platform-tools/adb), 或參考這篇將 adb 加到環境變數中。

./adb.exe shell pm list packages
output:
...
package:com.google.android.accessibility.soundamplifier
package:com.twitter.android
package:com.android.musicfx
...

 
2.藉由 package 列出目標 apk 的完整路徑

adb shell pm path example.package_name

Note:
example.package_name 就是在第1步列出的目標 apk package

./adb.exe shell pm path com.twitter.android
output:
package:/data/app/com.twitter.android-rsWL7_udEQ4bnqYf6aGKXQ==/base.apk
package:/data/app/com.twitter.android-rsWL7_udEQ4bnqYf6aGKXQ==/split_config.arm64_v8a.apk
package:/data/app/com.twitter.android-rsWL7_udEQ4bnqYf6aGKXQ==/split_config.xxhdpi.apk
package:/data/app/com.twitter.android-rsWL7_udEQ4bnqYf6aGKXQ==/split_config.zh.apk

 
3.複製目標到本地端

adb pull /data/app/example.package_name /local/path

Note:
/data/app/example.package_name 就是在第2步找到的完整路徑
local/path/ 為想放置目標 apk 的本地端(目前操作的機器)路徑,路徑之後可以設定取出的Apk的名稱,如D:\tempAPK\twitter_temp.apk。

./adb.exe pull /data/app/com.twitter.android-rsWL7_udEQ4bnqYf6aGKXQ==/base.apk D:\tempAPK\twitter_temp.apk
output:
/data/app/com.twitter.android-rsWL7_udEQ4bnqYf6aGKXQ==/base.apk: 1 file pulled. 35.1 MB/s (17336308 bytes in 0.470s)

完成後在D:\tempAPK\twitter_temp.apk就是從裝置取出的Twitter apk

分類
Android

TouchUtils.clickView 對 button 無效

描述:

button 是一般的 android 預設的 button,xml 定義如下

        <Button
            android:id="@+id/showtime_btn"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.5"
            android:text="Show Time" />

被測程式 source code 使用如下

public class MainActivity extends Activity implements OnClickListener
{
    private Button mShowTimeButton;
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        initUIComponents();
    }
    private void initUIComponents()
    {
       mShowTimeButton = (Button) findViewById(R.id.showtime_btn);
       mShowTimeButton.setOnClickListener(this);
    }
    @Override
    public void onClick(View view)
    {
       int uiId = view.getId();
       switch(uiId){
           case R.id.showtime_btn:
               System.out.println("click show time button");
               break;
       }
    }
}

在直接執行被測程式的情況下, mShowTimeButton 可以點擊且點擊會出現click show time button。
測試程式如下

public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity>
{
    private static final String DEBUG = MainActivityTest.class.getSimpleName();
    private MainActivity mMainActivity;
    private Button mShowTimeButton;
    public MainActivityTest() {
        super("com.example.targettestproject", MainActivity.class);
    }
    protected void setUp() throws Exception
    {
        super.setUp();
        setActivityInitialTouchMode(true);
        mMainActivity = getActivity();
        initUIComponents();
    }
    private void initUIComponents()
    {
       mShowTimeButton = (Button) mMainActivity.findViewById(R.id.showtime_btn);
    }
    protected void tearDown() throws Exception
    {
        super.tearDown();
    }
    public void testPrecondition()
    {
        assertNotNull(mMainActivity);
        assertNotNull(mShowTimeButton);
    }
    @UiThreadTest
    public void testShowTimeByClickButton()
    {
        TouchUtils.clickView(this, mShowTimeButton);
    }
}

第15行經過測試有沒有加入 setActivityInitialTouchMode 沒有影響。
第37行經過測試有沒有加入 @UiThreadTest 沒有影響。
第40行沒有效果。
經過 google 相關搜尋沒有找到類似的問題及解決方案。
 

修正:

使用 @UiThreadTest 以及 Button.performClick() 來取代 TouchUtils.clickView,如下

    ...
    @UiThreadTest
    public void testShowTimeByClickButton()
    {
        mShowTimeButton.performClick();
    }

 

分類
Android Uncategorized

在 Eclipse 使用 Android Manager 更新 android sdk 25之後無法使用 adb 問題

開啟 eclipse 啟動 android manager 並更新到 android adk 25.0.2 之後,使用 adb 會出現以下錯誤訊息

platform-tools ./adb devices
zsh: 可執行檔格式錯誤: ./adb

root cause:unknow
workaround : 到 android repository 下載並解壓縮 platform-tools.zip (經過測試只有platform-tools_r20-linux.zip 可用),替換掉原本無法使用的platform-tools 資料夾即可。
 

分類
Android

使用 gcc 編譯 .o 以及 .so 檔

首先確定已經存在 .c 檔(這裡以 hello-jni 為範例,在預設的情況下,已經存在/project_root_path/jni/hello-jni.c。

Step 1.打開Terminal並移動到專案根目錄的 jni 資料夾(/project_root_path/jni/)。

 

Step 2.編譯 .o 檔

輸入

gcc -Wall -fPIC -c hello-jni.c -I /usr/lib/jvm/java-7-oracle/include/linux/ -I /usr/lib/jvm/java-7-oracle/include/

Note:
-Wall:產生所有警告訊息。
-fPIC:表示編譯後為位置獨立的代碼。
-c:產生.o檔案。
hello-jni.c:代表要編譯的檔案,若有多個檔案必須使用空格分開,也可以指定路徑。
-I /path_of_directory:指定額外.h檔的搜索路徑(/path_of_directory),這是用在編譯的檔案(.c)中出現
#include “file” 的時候 , gcc/g++會在目前目錄尋找.h檔,如果沒有找到,會回到預設的.h目錄尋找,如果使用-I指定目錄,gcc 會從指定的目錄尋找,然後順著一般的順序。
因為hello-jni.c 有 #include <jni.h>,jni.h的位置即為 /usr/lib/jvm/java-7-oracle/include/
編譯完成後會產生 hello-jni.o 檔案
 

Step 3.編譯 .so 檔

輸入

gcc -Wall -rdynamic -shared -o libhello-jni.so hello-jni.o

完成後會產生 libhello-jni.so 檔
 

以上 Step 2 和 Step 3 可以使用一句指令完成,省去了產生.o檔的步驟,e.g.

gcc hello-jni.c -fPIC -shared -I /usr/lib/jvm/java-7-oracle/include/linux/ -I /usr/lib/jvm/java-7-oracle/include/ -lm -o libhello-jni.so

 
 

分類
Android

Android.mk 和 Application.mk

Android.mk 為 android ndk project 必備的描述檔,為 GNU Makefile 格式。

它的位置必須在jni目錄中,主要是說明如何build source 和 shared libraries。
在 /ndk的根目錄/build/core/ 底下還有許多mk檔可以參考。
以下為 HelloJni(/android-ndk/samples/HelloJni) 的 Android.mk

# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)

首先#開頭的都為註解。
第15行規定為每個 Android.mk 起始行,不可替換成別的內容,其中 my-dir 為指出目前的路徑,也就是jni目錄的路徑。
第17行 CLEAR_VARS 會連結到 /ndk的根目錄/build/core/clear-vars.mk。
目的是清除所有除了LOCAL_PATH以外的 LOCAL_XXX 的變數,e.g. LOCAL_MODULE , LOCAL_SRC_FILES 等等。
Android.mk 在每一次的執行過程中可能會建立多個module,因為以LOCAL_ 開頭的都是全域變數,為了避免module之間的衝突,必須清除掉。
第19行為命名 module 的名稱,系統自動會加上前綴lib 以及後綴.so,最後會產生一個共享庫文件.so ,並名為 libhello-jni.so
第20行為用來建立這個module的來源檔,若有多個檔案,必須以空格分開。
第22行 BUILD_SHARED_LIBRARY 會連結到 /ndk的根目錄/build/core/build-shared-library.mk。
目的是將來源檔案轉換為共享庫文件的動作,其完整動作會描述在build-shared-library.mk 。
 

如何藉由 Android.mk 編譯 c/c++檔案:

1.開啟 Terminal,並移動到專案根目錄中。
2.呼叫 ndk-build,Terminal 輸入 ndk-build 絕對路徑(/path_of_android_ndk/ndk-build)。
3.編譯成功輸出

Android NDK: WARNING: APP_PLATFORM android-8 is larger than android:minSdkVersion 3 in ./AndroidManifest.xml
[arm64-v8a] Gdbserver      : [aarch64-linux-android-4.9] libs/arm64-v8a/gdbserver
[arm64-v8a] Gdbsetup       : libs/arm64-v8a/gdb.setup
[x86_64] Gdbserver      : [x86_64-4.9] libs/x86_64/gdbserver
[x86_64] Gdbsetup       : libs/x86_64/gdb.setup
[mips64] Gdbserver      : [mips64el-linux-android-4.9] libs/mips64/gdbserver
[mips64] Gdbsetup       : libs/mips64/gdb.setup
[armeabi-v7a] Gdbserver      : [arm-linux-androideabi-4.8] libs/armeabi-v7a/gdbserver
[armeabi-v7a] Gdbsetup       : libs/armeabi-v7a/gdb.setup
[armeabi] Gdbserver      : [arm-linux-androideabi-4.8] libs/armeabi/gdbserver
[armeabi] Gdbsetup       : libs/armeabi/gdb.setup
[x86] Gdbserver      : [x86-4.8] libs/x86/gdbserver
[x86] Gdbsetup       : libs/x86/gdb.setup
[mips] Gdbserver      : [mipsel-linux-android-4.8] libs/mips/gdbserver
[mips] Gdbsetup       : libs/mips/gdb.setup
[arm64-v8a] Install        : libhello-jni.so => libs/arm64-v8a/libhello-jni.so
[x86_64] Install        : libhello-jni.so => libs/x86_64/libhello-jni.so
[mips64] Install        : libhello-jni.so => libs/mips64/libhello-jni.so
[armeabi-v7a] Install        : libhello-jni.so => libs/armeabi-v7a/libhello-jni.so
[armeabi] Install        : libhello-jni.so => libs/armeabi/libhello-jni.so
[x86] Install        : libhello-jni.so => libs/x86/libhello-jni.so
[mips] Install        : libhello-jni.so => libs/mips/libhello-jni.so
➜

編譯成功後在專案根目錄會多出 obj , libs 資料夾,.so會產生在libs資料夾內。
 
進階用法:
1.建立多個共享庫文件。

LOCAL_PATH := $(call my-dir)
#第1個模組
include $(CLEAR_VARS)
LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)
#第2個模組
include $(CLEAR_VARS)
LOCAL_MODULE    := module2
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)

 
2. 建立靜態函式庫。
可將第3方的模組轉換為靜態函式庫,並加到原始函式庫上

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := third_party_lib
LOCAL_SRC_FILES := third.c
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE    := module
LOCAL_SRC_FILES := module.c
LOCAL_STATIC_LIBRARIES := third_party_lib
include $(BUILD_SHARED_LIBRARY)

 
3.多個模組使用同一個靜態函式庫

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := third_party_lib
LOCAL_SRC_FILES := third.c
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE    := module1
LOCAL_SRC_FILES := module.c
LOCAL_STATIC_LIBRARIES := third_party_lib
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE    := module2
LOCAL_SRC_FILES := module2.c
LOCAL_STATIC_LIBRARIES := third_party_lib
include $(BUILD_SHARED_LIBRARY)

參考官網:https://developer.android.com/ndk/guides/android_mk.html#over

Application.mk 為描述應用程序需要哪些模組,也定義模組間的通用變數,必須位於 jni 目錄。

以下為 HelloJni 的 Application.mk

APP_ABI := all

APP_ABI 表示平台,而 all 代表為所有支援的平台建立二建制文件。
參考官網:https://developer.android.com/ndk/guides/application_mk.html
 
 

分類
Android

android ndk 主要元件以及結構

android ndk 主要的元件

  • 交叉編譯器(cross compiler):ARM , x86 , MIPS
  • 構建系統
  • Java native interface .h 檔案
  • C 函式庫
  • Math 函式庫
  • POSIX 執行緒
  • 小型的 C++ 函式庫
  • ZLib函式庫
  • 動態連結函式庫
  • Android Log 函式庫
  • Android NDK native APIs
  • OpenGL ES 3D
  • OpenSL ES
  • OpenMAX AL

 

android ndk 結構中一些比較重要的目錄以及腳本

  • ndk-build:為 ndk 構件系統的起始點。
  • ndk-gdb:可以使用 GNU Debugger 測試原生元件。
  • ndk-stack:原生組件崩潰時的追蹤堆棧。
  • build 目錄:包含所有 ndk 的 module。
  • platforms 目錄:包含不同 android 版本的 .h 檔以及 library
  • samples 目錄:範例
  • sources 目錄:共享 modules
  • toolchains 目錄:交叉編譯器,目前支援 ARM , X86 , MIPS

 

android ndk 專案重要的目錄

  • jni 目錄:為NDK項目的目錄。包含原生組件的 source code 以及 android.mk 檔。
  • libs 目錄:在 android ndk 建立過程中產生,包含指定的平台的獨立子目錄 e.g. ARM的armeabi。本目錄會打包在APK中。
  • obj 目錄:包含編譯 source code 之後所產生的目標文件。
分類
Android Uncategorized

查表法(表驅動法)(Table-Driven-Methods )

查表法(Table-Driven-Method)主要是藉由表格的維度和對應的數值來替代冗長的判斷式(if-else or switch)
查表法根據不同的查表方式,可分為三種:
直接存取(direct access):可從表格中直接讀取數值。
索引存取(indexed access):先透過索引取出key值,再從key值取出數值。
階梯存取(stair-step access):對於不同的數值範圍有效。
 


 
直接存取:
如果要查詢月份對應的天數,最直接的寫法 e.g.,

    private int getDaysOfMonth(int month)
    {
        if (month == 1) {
            return 31;
        } else if (month == 2) {
            return 28;
        } else if (month == 3) {
            return 31;
        } else if (month == 4) {
            return 30;
        } else if (month == 5) {
            return 31;
        } else if (month == 6) {
            return 30;
        } else if (month == 7) {
            return 31;
        } else if (month == 8) {
            return 31;
        } else if (month == 9) {
            return 30;
        } else if (month == 10) {
            return 31;
        } else if (month == 11) {
            return 30;
        } else if (month == 12) {
            return 31;
        } else {
            throw new IllegalArgumentException();
        }
    }

可以藉由以下表格來取代

private static final int[] DAYS_OF_MONTH = {
            31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

取值如下,第7行為使用查表法的動作。

    @Test
    public void testGetDayOfMonth()
    {
        int[] months = {1,2,3,4,5,6,7,8,9,10,11,12};
        for(int month : months){
            int resultBySwitch = getDaysOfMonth(month);
            int resultByTable = DAYS_OF_MONTH[month - 1];
            assertEquals(resultBySwitch, resultByTable);
        }
    }

 


 
索引存取:
如果條件判斷式的變數型態不是int , 就無法使用陣列來建表,必須改用Map,而 Map 的 key 值即是索引值。e.g.

    private int getDayOfMonthByName(String nameOfMonth)
    {
        if (nameOfMonth.equals("Jan")) {
            return 31;
        } else if (nameOfMonth.equals("Feb")) {
            return 28;
        } else if (nameOfMonth.equals("Mar")) {
            return 31;
        } else if (nameOfMonth.equals("Apr")) {
            return 30;
        } else if (nameOfMonth.equals("May")) {
            return 31;
        } else if (nameOfMonth.equals("Jun")) {
            return 30;
        } else if (nameOfMonth.equals("Jul")) {
            return 31;
        } else if (nameOfMonth.equals("Aug")) {
            return 31;
        } else if (nameOfMonth.equals("Sep")) {
            return 30;
        } else if (nameOfMonth.equals("Oct")) {
            return 31;
        } else if (nameOfMonth.equals("Nov")) {
            return 30;
        } else if (nameOfMonth.equals("Dec")) {
            return 31;
        } else {
            throw new IllegalArgumentException();
        }
    }

使用Map取值,第1到10行為初始化Map , 第14行為使用Map取值。

        Map<String, Integer> DAY_OF_MONTH = new HashMap<String,Integer>();
        String[] nameOfMonth = new String[] {
                "Jan", "Feb", "Mar", "Apr", "May","Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
        };
        Integer[] numberDayOfMonth = {
                31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
        };
        for (int i = 0; i < nameOfMonth.length; ++i) {
            DAY_OF_MONTH.put(nameOfMonth[i], numberDayOfMonth[i]);
        }
        for(String name : nameOfMonth){
            int resultByMethod = getDayOfMonthByName(name);
            int resultByMap = DAY_OF_MONTH.get(name);
            assertEquals(resultByMethod, resultByMap);
        }

 


 
階梯存取:
假設現在要對不同分數給予等級的判斷, e.g.

    private String getNameOfGradeByMethod(int score)
    {
        if (score >= 90) {
            return "A";
        } else if (score >= 80 && score < 90) {
            return "B";
        } else if (score >= 70 && score < 80) {
            return "C";
        } else if (score >= 60 && score < 70) {
            return "D";
        } else if (score < 60) {
            return "F";
        } else {
            throw new IllegalArgumentException();
        }
    }

需要建立2個對應的table , 以及求值的函式(getNameOfGradeByTable)。 e.g.

    private static final String NAME_OF_GRADE_LEVEL[] = {
            "A", "B", "C", "D", "F"
    };
    private static final int NUMBER_OF_GRADE_LEVEL[] = {
            90, 80, 70, 60
    };
    private String getNameOfGradeByTable(int score)
    {
        int gradeLevel = 0;
        while (NAME_OF_GRADE_LEVEL[gradeLevel] != NAME_OF_GRADE_LEVEL[NAME_OF_GRADE_LEVEL.length - 1]) {
            if (score < NUMBER_OF_GRADE_LEVEL[gradeLevel]) {
                ++gradeLevel;
            } else {
                break;
            }
        }
        return NAME_OF_GRADE_LEVEL[gradeLevel];
    }

雖然整體看起來比判斷式來的長,但若是接下來需要增加更多的判斷如 60, 50, 40, 30, 20, 10。
對於查表法只要在 table 加入相對應數值即可。
也適合用於沒有規則變化的求值。


 
事實上查表法無法去除掉原判斷式的邏輯,它只是把邏輯搬移到表中。表格的複雜度會跟著原判斷式的邏輯成正比。
另外也需要提供空間儲存表格。
查表法可以減少程式碼的長度,但無法簡化程式碼。
對可讀性的幫助並不大,反而需要完全理解表格才能修改原功能或是增加新功能。
因此最適合的情況為冗長但判斷邏輯簡單的條件式。
更複雜的查表法範例 ref
 

分類
Android

Android Error : Dx Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/strangeberry/jmdns/tools/Browser;

Description:

引入外部 Library , 啟動 android app 時出現。

[2016-03-04 10:20:58 - SmackForXep0174] Dx Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/strangeberry/jmdns/tools/Browser;
[2016-03-04 10:20:58 - SmackForXep0174] Dx Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/strangeberry/jmdns/tools/Browser$1;
[2016-03-04 10:20:58 - SmackForXep0174] Dx Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/strangeberry/jmdns/tools/Browser$2;
[2016-03-04 10:20:58 - SmackForXep0174] Dx Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/strangeberry/jmdns/tools/Browser$3;
[2016-03-04 10:20:58 - SmackForXep0174] Dx Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/strangeberry/jmdns/tools/Browser$ServiceTableModel;
[2016-03-04 10:20:58 - SmackForXep0174] Dx Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/strangeberry/jmdns/tools/Main;
[2016-03-04 10:20:58 - SmackForXep0174] Dx Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/strangeberry/jmdns/tools/Main$SampleListener;
[2016-03-04 10:20:58 - SmackForXep0174] Dx Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/strangeberry/jmdns/tools/Responder;
[2016-03-04 10:20:58 - SmackForXep0174] Dx Uncaught translation error: java.lang.IllegalArgumentException: already added: Ljavax/jmdns/package-info;
[2016-03-04 10:20:58 - Dex Loader] Unable to execute dex: Too many errors
[2016-03-04 10:20:58 - SmackForXep0174] Conversion to Dalvik format failed: Unable to execute dex: Too many errors

即使把所有變動都移除,讓專案回復未引用 Library 再重新 build,錯誤訊息一樣產生。
必須把 eclipse 重新啟動,錯誤訊息才會消失。
 

Solution:

從錯誤訊息去尋找相關線索。
一連串的錯誤訊息提示都和 jmdns有關,在app中 也引用了 jmdns Library。
StackOverFlow 提示 ref: http://stackoverflow.com/questions/11697979/android-build-with-jmdns-fails
看起來是SourceForge 上檔案有問題,根據 StackOverFlow 提示改換 maven 上的 Library
app 成功啟動!!