2013年4月30日 星期二

Android Start Activity With ACTION_VIEW Cause ActivityNotFoundException

因為並不能保證所產生的 Intent 一定能夠找到可以用來處理的程式。
所以只要有用到 ACTION_VIEW 的都要加上 catch ActivityNotFoundException
防止程式當掉。

常見的如 WebView

2013年4月10日 星期三

Separate Production And Development/Test/Beta Environments

在 Android 上不像 iOS 一樣可以很方便的用 Application ID 能夠在同一支手機上面裝好幾個一樣的 App,來測試正式和測試環境。

但是網路上大家的說法都是修改 AndroidManifast.xml 中的 package 去產生成兩個 App。
確實這樣是變成兩個 App 沒錯,但是要做這件事情可是大工程,連帶有很多東西也都必須要跟著改。加上 Google 提供的工具也兩光兩光的。

如:R 檔都要全部重新 import、有用到自訂 Attr 的 xmlns 也都要改、Google Maps v2 API Key因為有對應到 package 也要改…

但是又不太想寫 shell script 去做這件事情,因此目前是想到可以用 activity-alias 加上 meta-data 來完成切換正式和測試環境。

不過這個方法也是有缺點:因為其實這樣還是同一個 App,所以如果有資料是存在 DB、SharedPreferences 或是其他地方也都會共用到,因此有時候可能需要清除資料才行。

流程如下:

  1. 程式每次按返回鍵完全關掉再去點另外一個環境,才會真的切換環境。因為我並不想讓人在程式執行中就能夠切換環境,怕搞混。
  2. activity-alias 如果沒有重新讀取桌面的話,暫時不會出現 / 消失,所以可能要整個移除或重開機才會看到他對應出現或消失。


程式碼如下:

在 AndroidManifest.xml 內


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example"
    android:versionCode="1"
    android:versionName="1.0.0">

    ...

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/application_name"
        android:theme="@style/Theme" >
        
        <activity android:name=".WelcomeActivity"
            android:screenOrientation="portrait" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <activity-alias
            android:enabled="true"
            android:name=".Welcome-Beta"
            android:label="Beta版"
            android:icon="@drawable/ic_launcher"
            android:targetActivity=".WelcomeActivity">
            <meta-data android:name="production" android:value="false"/>
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>

        ...

    </application>

</manifest>


在 Activity 內

public class WelcomeActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        boolean production = true;
        try {
            ActivityInfo info = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
            Bundle meta = info.metaData;
            if(null != meta) {
                production = meta.getBoolean("production", true);
            }
        } catch (NameNotFoundException e) {
            Log.i(TAG, "Use production as target");
        }
        ApplicationConfigs.setTarget(production ? Stage.PRODUCTION : Stage.BETA);

    }

}


在 ApplicationConfigs.java 內
public class ApplicationConfigs {
    public enum Stage {
        BETA,
        PRODUCTION
    }

    private static Stage stage = Stage.PRODUCTION;

    public static void setTarget(Stage target) {
        stage = target;
    }

    private static String getEndPoint() {
        switch(stage) {
            case BETA:
                return "http://beta.example.com";
            default:
            case PRODUCTION:
                return "http://www.example.com";
        }
    }



}