code

2018年7月22日 星期日

Google IO 2018 - 用Android App Actions與Android OS深度整合

將現有的android app與android深度整合

Google assistant有個關鍵的entity叫做actions,那什麼是actions?



由於使用者非常少會再重複使用一個app (reengagement is low!!!),所以Google想要協助app developers把app的功能(actions)展現給使用者看,透過AI自動判斷目前使用者可能需要做的動作(Actions),而不只是App。

如果一個android app定義了自己的app actions之後,則這就是一個快速跟Google assistant app接口的方法,使用者可以透過Google Assistant做出task-based query,我們的android app actoins也能獲得展露頭角的機會。




 甚至是content-based query,google assistant會預測接下來使用者的動作,同樣也能讓我們的app actions浮現上來(例如以下查詢lady gaga的姓名,google assistant預測可能接下來的有許多買票或是訂購音樂相關的動作:)


也能在google search bar下方的action prediction出現:(android O之後)


還有就是一些OS深度整合(例如text selection預設action):



總之只要implement了app action,就有機會跟android平台(不只是手機OS) 做深度的整合。


Semantic Intents

Google已經幫我們把下圖中左邊的語意換成右邊的Intent with params:



我們programmer要做的就是在我們的apk中加入actions.xml,然後定義好裡面的fullfillments。


所謂intents,其實是一個semantic intents(注意這不是android intent! 這是Actions by Google這個平台定義的semantic intents),是需要被Google service分析過後的結過,而fulfillment是一個deep link直接指向我們app內可以提供內容或是action的頁面。以上兩者構成了actions.xml。

Semantic intents不是只有給android actions用而已,他可以在任何Google assistant能出現的地方,都被當作是一個intent。

這邊有一個可以integrate的東西叫做usage logging(透過Firebase app indexing api),這個可以幫助Android 將action suggestion 個人化(how?)



目前google 提供了不少built-in intents,當然還在持續增加中:





勁量使用built-in intents,因為這樣Google已經做好所有的semantic analysis,也不會有什麼ambiguity的空間,而且多國語言的語意分析也幫你弄好了,基本上只要專注在準備fulfillment就可以了。

App Actions基本上是黏著semantic intents和android intents的橋樑:



用URL當橋樑的好處是,這個資料型態還能符合很多其他的服務:




 Fulfillment

fulfillment可以有兩種model來形容:


 1. url template model: 基本上就是對那種app就能解決的問題來說適合的。此時actions.xml裡面定義的url是一個app中的deep link,而所謂的template就是語意分析引擎會把分析出來的參數填入url template中的place holder部分,這樣deep link到的頁面就能處理。

2. content-driven model: 這對content serving action有用,而且通常content會連結到網站上,所以此時的actions.xml中的url就是連結到某個我們有所有權的網站中的網頁,當然能夠符合structured data形式 (可以說semantic intent定義了動詞,而structured data定義了名詞或是受詞)是最好,這個要另外一篇來提了。



測試與提交actions.xml

這其實是把actions.xml送到 Actions on Google這個平台資料庫,這樣讓所有安裝google assistant的device (不限於android devices)都能使用。對android app來說,最簡單的提交方法就是放在apk裡面,然後submit到google play console:



不過在提交之前,要先測試吧!
Android studio有一個plugin叫做actions test tool可以用來測試actions.xml是否正常運作:



這個plugin能讓developer account單獨看到actions(即便submit到Actions on Google的database),其他使用者不會被影響到,接下來就是用Google Assistant來測試各種語言組合是否有正確連結到url defined in actions.xml。



URL Template-based example: Taxi app

這個叫車的app的actions.xml如下:


這個action使用的semantic intent為一個built-in intent ORDER_RIDE,使用者可能會說出以下的句子:

這個intent會被分析成以下的parameters,他們的type是schema.org/xxx:


可以看到在actions.xml中,fulfillment定義的urlTemplate是一個接受兩個參數placeholders(注意type) 的urls,不過這邊不一定要是http的schema。

如果在App Actions Test Tool中看到如下:






Content-driven example: Coursera app

coursera的android app已經做了這個app actions的深度整合,來看他們怎麼做的。

如果使用者說出以下:


Coursera app註冊了一個built-in intent,由於Google Assistant知道該怎麼parse這段語音成有意義的參數,所以這些參數會丟向Coursera app actions:



可以看到此TAKE_COURSE intent有定義一個parameter叫做course,那他的type是schema.org/Course,所以在每個Coursera app的網頁中,如果加上以下的strucutured data markup:


Google Assistant就能知道這個action要提供的url content是什麼,也就是一個fullfillment。

所以coursera app的actions.xml就是以下這麼簡單的幾行xml:



在test tool中看到會是這樣:




在Google Assistant中的互動中,take course就變成一個predictable actions,當然coursera app就會出現在此predicted action list中:




Usage logging with Firebase indexing APIs

整合這個的目的就是要讓我們的App Actions在Android的app suggestion中更能被看到。
Firebase提供api來記錄in-app action usages可以達到這個目的。


透過Slices與Android/Google App整合

Android 19+ 提供了Slices - 一個UI template有點類似widget,讓app的一些內容或功能呈現出來,可以被其他app所展現(例如Search, Gmail等)。

不過這又是另一個topic了。



如何與非Android devices整合 - Conversational Actions

以上說的利用actions.xml來定義app actions,這是只對android devices有用。那如果想要延伸到非android devices但是五億台google assistant enabled devices上呢?

基本上Google努力建置technology stack出來,而應用程式開發商就專注在experiences上:


由於這些非android devices共同的介面就是語音,所以就要定義另一個action介面:conversational actions。

待下回分解。

2018年6月21日 星期四

Android Architecture Components - Life Cycle Aware Components

簡介

這是Android官方推薦“正確寫一個Android App"的方法,這個官方推薦的方法由一些非常基本library以及pattern組成,稱為Architecture components,包括以下:

Lifecycles: 每個app必考慮的東西
Lifecycle-aware observables: 觀察lifecycles的方法
Lightweight ViewModel: 這是獨立於Activity和Fragment之外,可以單獨測試邏輯之處
Object mapping for sqlite


要解決什麼問題?

通常programmer把所有的邏輯(商業/UI/Data/與OS互動等)都寫在activity或是fragment中,不過activity事實上是設計來操縱UI與OS互動,越簡單越能避免life cycle造成的問題,主要是因為activity / fragment 這類型的class,programmer並沒有絕對掌控權,從instance creation到destruction都是OS決定,所以要盡量減少對這些class的dependency。

此外model應該永遠獨立於view之外,永遠不受到OS相關決定(回收記憶體之類)的影響。


Life-cycle Aware Components

我們通常會把某些邏輯或是object寫在一個activity或是fragment裡面,應且在life cycle callback中作出對應的動作。例如以下MyLocationListener instance會在MyActivity的onStart()和onStop() 時也去做start() / stop() 動作:

class MyLocationListener {
    public MyLocationListener(Context context, Callback callback) {
        // ...
    }

    void start() {
        // connect to system location service
    }

    void stop() {
        // disconnect from system location service
    }
}

class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    @Override
    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, (location) -> {
            // update UI
        });
    }

    @Override
    public void onStart() {
        super.onStart();
        myLocationListener.start();
        // manage other components that need to respond
        // to the activity lifecycle
    }

    @Override
    public void onStop() {
        super.onStop();
        myLocationListener.stop();
        // manage other components that need to respond
        // to the activity lifecycle
    }
}

但是如果有很多類似的component在這個activity裡面,那他的life cycle callbacks就會充滿這類code,變得複雜難maintain。

此外我們不能保證async operations會在我們要的life cycle callbacks回來:

class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, location -> {
            // update UI
        });
    }

    @Override
    public void onStart() {
        super.onStart();
        Util.checkUserStatus(result -> {
            // what if this callback is invoked AFTER activity is stopped?
            if (result) {
                myLocationListener.start();
            }
        });
    }

    @Override
    public void onStop() {
        super.onStop();
        myLocationListener.stop();
    }
}
可以看到在onStart()中,此myLocationListener要等待某個operation result回來為true才會啟動,但是有可能此Activity onStop()先發生了,然後此operation result才回來,所以又啟動了這個myLocationListener。這有可能造成了這個listener在我們未預期的狀況下活動。

android.arch.lifecycle提供了一些design pattern來幫助我們免於煩這些(鳥)事。


Life Cycle

LifeCycle這個class把某個有life cycle概念的component (e.g. Activity / Fragment / Service) 的life cycle給抽象化了,它有states和events:



所以對這個component life cycle有興趣的人,就可以implement LifeCycleObserver:

public class MyObserver implements LifecycleObserver {
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    public void connectListener() {
        ...
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    public void disconnectListener() {
        ...
    }
}

myLifecycleOwner.getLifecycle().addObserver(new MyObserver());


Life Cycle Owner

擁有LifeCycle的components必須implement LifeCycleOwner interface (AppCompatActivity的某個super class是implement這個interface的),所以最快的讓自己的activity或是fragment變成life cycle owner就是去繼承有implement這個interface的base class:
class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {
            // update UI
        });
        Util.checkUserStatus(result -> {
            if (result) {
                myLocationListener.enable();
            }
        });
  }
}
剛剛的MyLocationListener就可以implement LifeCycleObserver:

class MyLocationListener implements LifecycleObserver {
    private boolean enabled = false;
    public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
       ...
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    void start() {
        if (enabled) {
           // connect
        }
    }

    public void enable() {
        enabled = true;
        if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
            // connect if not connected
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    void stop() {
        // disconnect if connected
    }
}

可以看到MyLocationListener並沒有override什麼interface method,而是依賴於annotations。我們說MyLocationListener是一個life cycle aware object


如果不繼承已知的life cycle owner class的話,也可以自己去implement LifeCycleOwner interface,但是要透過以下的templates:

public class MyActivity extends Activity implements LifecycleOwner {
    private LifecycleRegistry mLifecycleRegistry;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mLifecycleRegistry = new LifecycleRegistry(this);
        mLifecycleRegistry.markState(Lifecycle.State.CREATED);
    }

    @Override
    public void onStart() {
        super.onStart();
        mLifecycleRegistry.markState(Lifecycle.State.STARTED);
    }

    @NonNull
    @Override
    public Lifecycle getLifecycle() {
        return mLifecycleRegistry;
    }
}


2018年6月10日 星期日

Google IO 2018 - Android Vitals

startup time

1. slow cold start: cold start超過五秒者:


定義為:
1. app 不在memory
2. app經過onCreate() -> Activity running

2. slow warm start: app已經在memory,重新叫起超過2秒者:


定義為:
1. app 在memory
2. app經過onCreate() -> Activity running

3. slow hot start: 某個已經launched activity又restart 到 onResume的時間的時間超過1.5秒者:


定義為:
1. app 在memory
2. app經過onRestart() -> Activity running

Detecting Crashes

Android Vitals是一個platform tool,所以可以在3rd party crash-detection sdk (e.g. Fabric Crashlytics)還沒起來前,就能偵測到crash!


Battery

這是最重要的效能指標,因為mobile device是很resource-limited。


1. 不要直接使用wake lock api,有可能因為bug造成螢幕/CPU永不關閉。改用:



2. 盡量少wake up

Permission

統計看來,user並不喜歡grant permissions:


Vitals會把這些permission列出來:


不過目前應該還是在beta吧?! 我們的app都看不到這些資訊。奇怪。


ANRs

這個是Android系統中惡名昭彰的代名詞,android vitals也是有偵測這個東西,這怎麼發生呢?

當main thread (UI thread) 被block住,無法處理render / ui input時,系統偵測到此狀態維持幾秒鐘之後,就會出現ANR警告。block的原因可能包括:

1. I/O operations: network, disk


上面這個function call看似沒有read/write operations,但實際上一但被instantiated,就馬上有read operations (SharedPreference implementation就是如此)。所以看似無害的code,實際上在main thread做了I/O operation了。

下面是另一個I/O example:


上面的code實際上在 access headerFields就會真的去讀取connection,甚至在某些版本的Android,連if ()中的URL equality comparison就會進行IO了!

不過developers哪有辦法知道所有這些眉角?????
還好Android提供一個debug api,可以讓我們在debug time抓出這些在main thread不該做的事:



penaltyDeath() 會造成runtime crash,所以記得不要在release版本時候打開!!!當違反strict mode crash之後,可以從log找到發生的行數:



2. long computation

這個就不用多說了,肯定是block。
要常態性使用Android Profiler來看哪些code有可能造成long computation。
Strict mode api也提供自己加入註解一段slow code,讓strict mode偵測到此slow code被main thread上執行時,可以penalty crash。

3. Interprocess communication (IPC)

既然另外一個process (通常是另外一個app)不受我們控制,我們當然不能在main thread去做IPC,否則有可能造成main thread上做一些blocking operations。

4. Multithreaded programming

使用一些synchronization primitives的時候,要很確定自己知道自己在幹嘛,否則有可能造成dead lock => ANR

android vitals的log可以幫助我們找出這類型的問題:



5. Slow BroadcastReceiver handling
我們宣告BroadcastReceiver在manifest中,如果一個broadcast進來,handler是在main thread上跑的! 所以不該做任何以上四點提到的operations。如果你的handler在10秒內沒exit,OS會殺掉整個process (app)。

所以要在handler中用另一個thread來做上面四點的事:




Crashes

我們常常為了怕某些狀況下會crash,而做一堆null check或是try-catch,不過目前Android官方建議全部使用Jetpack的架構來解決這些問題:



另外使用Kotlin的built-in nullity check,也是有幫助。

Google IO 2018 - What's new in Android TV

1. Hardware Adoption倍數成長




2. 還是稀少的apps,約3600個。


3. Google assistant 生態系一員:



有Google Assistant的幫助,大大降低操作的門檻,特別是search functionality。主要三個功能面向:

(1) Content Search
(2) Deep links
(3) Playback control


4. Content-first design

每個app有channel概念,可以拉出一部份重點放在Android TV首頁,而使用者也可以自己決定顯示哪些channel。



每個channel中可以有很多個"program",例如上面的每個電影是一個program。


看起來也沒太大改變。

2018年5月16日 星期三

Google IO 2018 - 跟Android有關的talk

Android

App performance:
Android vitals: debug app performance and reap rewards
What's new in Android Runtime
Drawn out: how Android renders
Understanding Android memory usage
Best practices using compilers in Android Studio
Best practices for text on Android
What's new with the Android build system
Don't let your app drain your users' battery



App development
What's new in Android development tools
The future of apps on Android and Google Play: modular, instant, and dynamic
Build the new, modular Android App Bundle
Protips: a fresh look at advanced topics for Android experts
Android Jetpack: what's new in Architecture Components
Effective ProGuard keep rules for smaller applications
What's new with ConstraintLayout and Android Studio design tools
Android Slices: build interactive results for Google Search
Building AR apps with the Sceneform SDK



App Testing
Frictionless Android testing: write once, run everywhere
Autonomous and customized pre-launch testing in the Google Play Console


App Security
What's new in Android security


App Release Management
Release management: successful launches and updates on Google Play


Android TV: What’s new with Android TV

Google Play
Analyze your audience and benchmark metrics to grow on Google Play
Google Play Instant: how app developers are finding success


Android Car
What’s new in automotive

Wear OS
What's new in Wear OS by Google