2015年9月20日 星期日

Trunk Based Developement 概述

Trunk Based Development

最近看到了google 的一些文章,裡面提到了google & facebook 都使用了Trunk Based Development,覺得很有趣,所有就去查了一些相關的文章
Trunk Based Development最特別的地方就是,新的功能並不是開一個新的feature branch做開發,而是直接在trunk 上做開發,當然依然要遵守,每筆commit都不能太過於龐大的守則
由於Trunk Based Development 會使多個功能同時在同一個branch上開發,這也就衍生了另一個問題,假設某些功能是v1.0要進,某些功能是v1.1才要進,那要怎麼處理?
根據martin fowler的說法,我們必須實作feature toogle,講白了點就是類似開關的功能,根據設定檔開啟或關閉該功能,舉個例子來說,某個版本要進v1.1,但是我們的功能在v0.1 的時候就已經開始開發,所以我們必須實作一個開關,避免v1.0的版本會出現這個不應該出現的功能,接著我們可以在v1.1的時候將開關打開,然後在v1.2的時候將開關這個功能拔除並且移除舊的功能.
又或者,我們可以實作branch by abstraction的方法,其實也是一個開關的概念或者是說switch的概念,當沒有切換的時候就使用舊版的作法,當切換的時候則改成新版的做法,至於為什麼要使用abstraction的關鍵字?
其實這也對應到了 OO的概念
針對介面來撰寫程式,而不要針對實作
所以在實作這個功能的時候,首先必須先將共同的邏輯抽離抽成interface or abstract,然後舊版則實作這個interface or abstract,新版也實作這個interface or abstract,然後最重要的是使用者只針對interface or abstract 做動作,然後我們就可以去做switch的動作

TBD的好處

  • 降低test的成本
    • 如果有多個feature branch也就代表有多個branch要執行test,這相對也提升了test 成本
  • always是可以release 的codebase,並且可以根據需求,修改需要release的功能
    • 可以利用開關開啟或關閉某項功能
    • 可以利用開關使用新版或舊版功能
  • 減少merge conflict的機會
    • 由於所有人都在同一個trunk上開發,只要所有的人都保證commit的code並不是過於龐大,就算遇到conflict也可以快速解決
    • 適合多人同時開發的專案

結論

講了那麼多,其實我也還沒有機會實際run過這個流程,不過根據目前的理解,TBD講白了就是把所有的功能都放在同一個branch上開發,然後使用featuretoggle & branch by abstraction來隔開尚未完成的實作,很合理…但是同時也覺得會有很多坑啊!!

reference

http://www.alwaysagileconsulting.com/organisation-pattern-trunk-based-development/
http://martinfowler.com/bliki/FeatureBranch.html
http://www.martinfowler.com/bliki/FeatureToggle.html
http://paulhammant.com/blog/branch_by_abstraction.html

2015年9月15日 星期二

MarkDown

其實這篇文章是想要稍微推薦一下markdown
markdown其實是個很輕量的語法,由於用google blog的介面寫blog真的很麻煩

  1. 沒有code block
  2. 排版十分不易
  3. 沒辦法即時預覽

這些使用markdown語法 + stackedit 之後所有的問題都迎刃而解啊!!
我想應該會使用這個editor寫一陣子吧!!

順便熟悉一下新editor的用法

Android 開發(一百零四) what's new in andorid studio 1.4 preview

先看一下下面這張圖


這代表什麼?
代表或許在android studio 1.4 正式release 之後,或許就可以支援
使用vector 產生圖檔了!!

有興趣的人可以先去下載新版的android studio 1.4 beta
然後將build.gradle的 gradle版本改成1.4.0-beta1

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.4.0-beta1'
        // NOTE: Do not place your application dependencies here; they belong        // in the individual module build.gradle files    }
}

然後就可以使用這項功能了,記得在import圖檔之後要sync一下,接著檔案就會產生了
這對UI真的是一大福音啊!!,之後只要出一張vector就好了!!

不過這項功能目前還沒發佈,可以再期待一下囉!!

Android 開發(一百零三) dex-method-counts

今天要講的是app 的method counts

為什麼我們要注意method counts?
因為android 有65536的method count的限制

如果app的method數超過了65536就會無法build成功,
所以為了提早發現提早治療,所以我們必須常常觀察method數是否超出預期

從前並沒有方便的工具去觀察method數,不過最近觀察到github上有人提供了簡單的檢驗方式 https://github.com/mihaip/dex-method-counts

小弟依照了上面的方式稍微檢驗了我們家的app

其實已經快爆了XD

不過如果你檢驗了app後發現遇到跟我類似的情形的話,其實不太需要擔心
因為google 已經想到了這個,所以他提出了 https://developer.android.com/tools/building/multidex.html

multidex,只要利用multidex就可以解決掉這個問題

根據google 的文件我們首先必須import multidex的lib

dependencies {
  compile 'com.android.support:multidex:1.0.0'
}
接著必須在build.gradle裡加上multidexEnabled

    defaultConfig {
        
        multiDexEnabled true

    }
我們必須將原本的application改成繼承MultiDexApplication

public class MyApp extends MultiDexApplication  {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }

這樣就完成了

不過注意一下,multidex還是有一些限制的,
例如必須要在版本14以上,還有可能會造成build的速度變慢之類的問題

最後,如果有寫test case 的人,而且有用AndroidJUnitRunner的話
可能會想問要怎麼在multidex版本上面正常測試

方法很簡單,首先必須在test的folder裡自建一個runner
這個runner必須必得要使用multidex.install

public class MyRunner extends AndroidJUnitRunner {

    @Override
    public void onCreate(Bundle arguments) {
        MultiDex.install(getTargetContext());
        super.onCreate(arguments);
    }
}

接著必須在build.gradle裡加上testInstrumentationRunner

    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 22

        testInstrumentationRunner "com.MyRunner"
        multiDexEnabled true

    }

這樣就完成了,就可以使用舊有的annotation了







2015年6月13日 星期六

Android 開發(一百零二) 利用annotation 處理 enum

在java 中我們常常會使用enum處理多種type,不過android 官方說這種寫法是非常expensive (可能是效率或memory其實我沒有多做研究)所以官方推薦我們利用另外一種寫法,使用annotation來處理

先讓我們看一下下面的程式碼

public class MyMode {
    @IntDef({SUCCESS, NETWORK_FAIL, DATA_ERROR})
    @Retention(RetentionPolicy.SOURCE)
    public @interface APIMode {}

    public static final int SUCCESS = 0;
    public static final int NETWORK_FAIL = 1;
    public static final int DATA_ERROR = 2;

    private int mode = SUCCESS;
    public int getMode(){
        return mode;
    }

    public void setMode(@ APIMode int _mode){
        mode = _mode;
    }

}

我定義了一個MyMode 並且定義了
SUCCESS = 0
NETWORK_FAIL = 1
DATA_ERROR = 2

接著我利用了
@IntDef({SUCCESS, NETWORK_FAIL, DATA_ERROR})
    @Retention(RetentionPolicy.SOURCE)
    public @interface APIMode {}

定義了一個自定義的annotation APIMode 而這個annotation只有在setMode的時候會被用到,這個annotation有什麼功用呢?

主要是在寫程式的時候,我們常常會這樣寫 mymode.setMode(1);
這樣寫程式是可以work的,但是其實很不好讀,對於其他的developer他們還必須進來查看才知道原來setMode(1)是在說 NETWORK_FAIL ,
所以我們該如何避免developer為了方便,而使用這種寫法呢?

我們使用了剛剛的annotation時,如果有人寫同樣的code,我們就可以在android studio上面看到如下圖的提示

而且數字下方會有紅字顯示,這樣developer就比較會去修改它,可惜的是,這種寫法似乎沒有辦法強制build不過(或許使用lint就行)

最後,我覺得這種寫法的確是蠻不錯的,but對於實際在使用上面,感覺大部份的人還是會為了方便而使用enum,不過官方都這麼推薦了,還是可以考慮用看看囉~

Android 開發(一百零一) google play Service Invite friend Api

google play service 在新版本推出了invite friend api ,讓你可以邀請google 上的朋友,
做法其實很簡單,

        Intent intent = new AppInviteInvitation.IntentBuilder(getString(R.string.invitation_title))
                .setMessage(getString(R.string.invitation_message))
                .setDeepLink(Uri.parse("http://tw.91mai.com/ref/0"))
                .build();
        startActivityForResult(intent, REQUEST_INVITE);

使用上面的方式將intent丟出,就會開啟邀請好友的選單,在邀請完成後利用onActivityResult去取得邀請的ids

 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Log.d(TAG, "onActivityResult: requestCode=" + requestCode + ", resultCode=" + resultCode);

        if (requestCode == REQUEST_INVITE) {
            if (resultCode == RESULT_OK) {
                // Check how many invitations were sent and show message to the user
                // The ids array contains the unique invitation ids for each invitation sent
                // (one for each contact select by the user). You can use these for analytics
                // as the ID will be consistent on the sending and receiving devices.
                String[] ids = AppInviteInvitation.getInvitationIds(resultCode, data);
                showMessage(getString(R.string.sent_invitations_fmt, ids.length));
            } else {
                // Sending failed or it was canceled, show failure message to the user
                showMessage(getString(R.string.send_failed));
            }
        }
    }

當你將邀請送出之後,你的朋友就會收到邀請下載的信,

out16.gif
out16.gif

如上方兩張圖,左邊是送出邀請的功能
右邊是好有收到邀請時,安裝後開啟時我們可以從intent中取得相關的link and id

為了要能夠在安裝時取得google play store送給我們的廣播,我們必須註冊receiver

    <receiver
           android:name="com.google.android.gms.samples.appinvite.ReferrerReceiver"
            android:exported="true"
            tools:ignore="ExportedReceiver">
            <intent-filter>
                <action android:name="com.android.vending.INSTALL_REFERRER" />
            </intent-filter>
        </receiver>

接著我們可以利用下方的程式碼取得相關的資訊

if (!AppInviteReferral.hasReferral(intent)) {
            Log.e(TAG, "Error: Intent does not contain App Invite");
           
// Extract referral information from the
String invitationId = AppInviteReferral.getInvitationId(
String deepLink = AppInviteReferral.getDeepLink(intent);

如果我們不需要track 成效的話,邀請的功能在這邊已經結束了
接下來要講的就是,我們可以利用google play service新的api 來trace我們的成效

在使用朋友贈送的信件開啟我們的app之後,我們可以利用google play service來track成效,

        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .enableAutoManage(this, 0, this)
                .addApi(AppInvite.API)
                .build();

利用AppInvite.API  在api connect成功之後


  String invitationId = AppInviteReferral.getInvitationId(intent);

        // Note: these  calls return PendingResult(s), so one could also wait to see
        // if this succeeds instead of using fire-and-forget, as is shown here
        if (AppInviteReferral.isOpenedFromPlayStore(intent)) {
            AppInvite.AppInviteApi.updateInvitationOnInstall(mGoogleApiClient, invitationId);
        }

        // If your invitation contains deep link information such as a coupon code, you may
        // want to wait to call `convertInvitation` until the time when the user actually
        // uses the deep link data, rather than immediately upon receipt
        AppInvite.AppInviteApi.convertInvitation(mGoogleApiClient, invitationId);

我們可以取得inviteationId ,然後利用 updateInvitationOnInstall 告訴google analytics這個user已經成功安裝.

假設這個邀請是希望新安裝的user可以使用優惠券,那我們可以利用
convertInvitation告訴google analytics有多少使用者安裝後並且成功使用優惠券
也就是可以取得轉換率(我是這樣理解的XD)



最後,由於我是直接拿google 的sample就直接附上相關的網址吧,
有興趣的人可以去那邊做更深入的了解
https://developers.google.com/app-invites/android/guides/app?configured=true

然後下面的網址是appinvite設定的小幫手,在裡面跟著步驟設定之後,你google developer console的設定也會設定完成,個人覺得這個新界面真的是好用很多,大家可以參考看看
https://developers.google.com/mobile/add?platform=android&cntapi=appinvites&cnturl=https:%2F%2Fdevelopers.google.com%2Fapp-invites%2Fandroid%2Fguides%2Fapp%3Fconfigured%3Dtrue%23add-config&cntlbl=Continue%20Adding%20App%20Invites



2015年6月10日 星期三

Android 開發 (一百) What's New in M Permission

在M的版本關於permission做了一個很大幅度的修改,原本的permission在下載時會出現如下方的提示

Screenshot_2015-06-09-22-52-56.png

使用者看到這個提示一定會問的問題是,為什麼需要那麼多權限,你是不是拿了這些權限去做了其他奇怪的事情?
這對app的下載造成了一定的阻礙

還有另一個問題就是,假設新版本比舊版本多了一個權限那麼app就無法自動更新下載,必須使用者確認才有辦法更新app

舉個例子來說
我新增了一個很強大的功能,而且我也如期完成了這個功能,我們行銷打算開始跑新功能的活動,由於我新增了一個權限造成這個版本的普及率要多一個月甚至更久才能普及,那行銷是否就無法如期去跑這個活動.

這其實對許多公司來說都是很大的痛點,明明功能就已經完成了,卻由於沒有良好的普及率造成活動無法如期舉行.

so...針對這兩個問題

android M 提出了新的解決方案

out16.gif

上面的圖示你可以注意到,下載的時候不再有permission的視窗跳出,

其實你不需要擔心,permission並不是不見了,而是只有在需要的時候才會跳出
讓我們再看看下面的圖片

out16.gif

假設上面的按鈕需要使用到phone calls and camera的權限,我可以在使用者點擊的時候才去檢查是否擁有這些權限,如果沒有android會跳出如上的提示視窗跟使用者要求權限,只有在permission 被允許之後,我們才能繼續做後續的事情,例如錄影...

其實這對使用者的經驗是一大提升,因為使用者可以很確切地知道你的權限是用在哪個地方,當然前提是你沒有一打開app就把所有權限一次拿完XD

不過由於這些使用者經驗的提升,也造成了程式碼上一定幅度的修改

首先以上面的例子來說當button click我必須做檢查我是否擁有這個權限

 if (checkSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {

如果沒有權限我們可以利用requestPermissions來取得我們需要的權限
requestPermissions(new String[]{Manifest.permission.READ_PHONE_STATE},
                            PERMISSION_REQUEST_PHONE);


requestPermissions之後會跳出上方的提示視窗,必須等使用者按下allow or deny之後才會產生callback

  @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

我們可以在onRequestPermissionsResult處理相關的動作,例如當permission成功取得時應該顯示啥,或者當沒有成功取得權限時應該怎麼辦之類的動作

其實以一個開發者的角度來說,雖然有那麼多的好處,但是這其實提升了程式的複雜度,讓原本的流程又多出了一個callback 判斷時間點,真是有利必有弊啊

不過目前這個功能並不是強制性的,所以舊app還是可以走原本的邏輯,也就是下載時讓使用者看到所有個權限,並且一次性的取得所有權限

但是如果要使用新的功能則必須將下面三個設定都使用新的設定才行
compileSdkVersion 'android-MNC'
minSdkVersion "MNC"
targetSdkVersion "MNC"

如果使用了MNC版本請記得一定要做permission check 否則就會出現

螢幕快照 2015-06-03 下午9.56.53.png


最後還是要附上sampel code
https://github.com/nightbear1009/M-Permission