code

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。


看起來也沒太大改變。