在2017年谷歌推出Android新的架构组件-一组可以帮助开发者设计强大的,可测试的和可维护的应用程序组件库。

下面我将重点介绍以下几个实用组件:

LifeCycle 

LiveData

ViewModel

官网地址:

https://developer.android.com/topic/libraries/architecture/

-----------------------------------------------------

生命周期感知组件-LifeCycle

1、LifeCycle能够做什么

支持生命周期的组件执行操作以响应另一个组件(例如Activity和Fragment)的生命周期状态更改。

这些组件可帮助你生成组织性更好,并且通常重量更轻的代码,这些代码更易于维护。

2、为什么需要Lifecycle组件

常见的模式是在Activity和Fragment的生命周期方法中实现依赖组件的操作。

但是,这种模式导致代码的组织不良以及错误泛滥,一堆看不懂的代码。

通过使用生命周期感知组件,你可以将相关组件的代码从生命周期方法中移出并移入组件本身。

下面来看个例子:

假如需要在生命周期中处理某些事情,可能会有两种方式,一、直接在Activity或者Fragment中的生命周期方法中写代码逻辑;二、写一个接口,定义类似生命周期的方法,在Activity或Fragment中调用相应的方法将逻辑剥离,不过也离不开Activity或Fragment。

/** * Main Presenter */public class MainPresenter implements IPresenter {        public MainPresenter(Context context){  }        @Override        public void onResume() {    }        @Override        public void onStop() {    }    }

 

public interface IPresenter {    void onResume();    void onStop();}

 

public class MainActivity extends AppCompatActivity {    private static final String TAG = "MainActivity";    private IPresenter mPresenter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        Log.d(TAG, "onCreate: ");        setContentView(R.layout.activity_main);        mPresenter = new MainPresenter(this);    }    @Override    protected void onResume() {        super.onResume();        Log.d(TAG, "onResume: ");        mPresenter.onResume();    }    @Override    protected void onStop() {        super.onStop();        Log.d(TAG, "onStop: ");        mPresenter.onStop();    }}

 

IPresenter中定义和生命周期相关的几个方法,然后在MainActivity中调用对应的方法。这样做会有个问题:在MainActivity中的每个生命周期方法中都要调用一次IPresenter中的接口。那有没有更好的办法呢,这时候LifeCycle就该上场了:

 

public class MainPresenter implements IPresenter {    private static final String TAG = "MainPresenter";    public MainPresenter(Context context){    }    @Override    public void onCreate(LifecycleOwner owner) {        Log.d(TAG, "onCreate: ");    }    @Override    public void onStart(LifecycleOwner owner) {        Log.d(TAG, "onStart: ");    }    @Override    public void onResume(LifecycleOwner owner) {        Log.d(TAG, "onResume: ");    }    @Override    public void onPause(LifecycleOwner owner) {        Log.d(TAG, "onPause: ");    }    @Override    public void onStop(LifecycleOwner owner) {        Log.d(TAG, "onStop: ");    }    @Override    public void onDestroy(LifecycleOwner owner) {        Log.d(TAG, "onDestroy: ");    }}

 

public interface IPresenter extends DefaultLifecycleObserver{}

 

public class MainActivity extends AppCompatActivity {    private static final String TAG = "MainActivity";    private IPresenter mPresenter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        Log.d(TAG, "onCreate: ");        setContentView(R.layout.activity_main);        mPresenter = new MainPresenter(this);        getLifecycle().addObserver(mPresenter);    }}

 

代码是不是很简单,尤其是Activity。

IPrestener 接口继承 DefaultLifecycleObserver 接口,然后 MainPresenter 实现 IPresenter 接口,在 MainPresenter 中,可以把 DefaultLifecycleObserver 中生命周期的方法全实现,也可以选择实现其中几个,比如你只关心 MainActivity 的 onCreate()  和 onDestroy() ,那么你就可以在 MainPresenter 中实现 onCreate() 和 onDestroy() 方法。借助实现 DefaultLifecycleObserver 接口可以让我们少写很多代码。

到这里大家可能就非常想知道Lifecycle组件实现的原理是什么?那么接着向下看。

 

3、LifeCycle实现原理

 

先来看一张图:

我们以V4包中的 Fragment(AppCompatActivity类似)为例,看下 Fragment 和 LifecycleOwner、LifecycleObserver、Lifecycle 之间的类关系图。

 

  • Lifecycle 组件成员Lifecycle被定义成了抽象类,LifecycleOwner、LifecycleObserver 被定义成了接口;

  • Fragment 实现了 LifecycleOwner 接口,该只有一个返回 Lifecycle 对象的方法 getLifecyle();

  • Fragment 中 getLifecycle() 方法返回的是继承了抽象类 Lifecycle 的 LifecycleRegistry。

  • LifecycleRegistry 中定义嵌套类 ObserverWithState,该类持有 GenericLifecycleObserver 对象,而 GenericLifecycleObserver 是继承了 LifecycleObserver 的接口。

 

再来看一张时序图:

 

  • 我们在 Fragment(AppCompatActivity也一样)中调用 getLifecycle() 方法得到 LifecycleRegistry 对象,然后调用 addObserver() 方法并将实现了 LifecycleObserver 接口的对象作为参数传进去。这样一个过程就完成了注册监听的过程。

  • 后续就是 Fragment 生命周期变化时,通知 LifecycleObserver 的过程: Fragment的performXXX()、onXXX() 方法; LifecycleRegistry 的 handleLifecycleEvent() 方法; LifecycleObserver 的 onXXX() 方法。

  • 如果你细心点看上面的时序图,你会发现 Fragment 中 performCreate()、performStart()、performResume() 会先调用自身的 onXXX() 方法,然后再调用 LifecycleRegistry 的 handleLifecycleEvent() 方法;而在 performPause()、performStop()、performDestroy() 中会先 LifecycleRegistry 的handleLifecycleEvent() 方法 ,然后调用自身的 onXXX() 方法。

 

可以看看Fragment源码

 

4、细节

 

我们要做的内容主要是实现 LifecycleObserver 接口,然后通过实现了 LifecycleOwner 接口的对象进行注册,以监听其生命周期,后续就是坐等通知了。

 

5、实现LifecycleObserver接口

 

从上面的类关系图,我们可以看到有三个接口 GenericLifecycleObserver、FullLifecycleObserver、DefaultLifecycleObserver 都直接或者间接继承了 LifecycleObserver。然而 GenericLifecycleObserver 是隐藏的,我们用不了。那我们该怎么实现 LifecycleObserver 接口呢,有两种方式:

 

  • 实现 DefaultLifecycleObserver 接口,然后重写里面生命周期方法(如上);

  • 直接实现 LifecycleObserver 接口,然后通过注解的方式来接收生命周期的变化;

 

class MyLocationListener implements LifecycleObserver {    private boolean enabled = false;    public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {       ...    }    @OnLifecycleEvent(Lifecycle.Event.ON_START)    void start() {    }    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)    void stop() {    }}

 

对于这两种形式,Lifecycle.java 文档中是建议使用第一种方式,因为文档中说明了,随着Java8成为主流,注解的方式会被弃用。

 

6、添加观察者

 

实现 LifecycleOwner 后,最后一步,我们直接看添加观察者( addObserver(实现 LifecycleObserver 的观察者class) )

 

生命周期感知组件的最佳实践

 

  • 尽可能保持你的UI控制器(Activity或Fragment)尽可能精简。他们不应该试图获取他们自己的数据;相反,使用 ViewModel 来做到这一点,并观察一个 LiveData 对象,以反映到视图的变化。

  • 尝试编写数据驱动的用户界面,其中你的用户界面控制器的职责是在数据更改时更新视图,或将用户操作通知给用户 ViewModel

  • 把你的数据逻辑放在你的 ViewModelclass 上。ViewModel 应该充当你的UI控制器和其他应用程序之间的连接器。但要小心,ViewModel 不是用来获取数据的(例如,从网络)。相反,ViewModel 应该调用适当的组件来获取数据,然后将结果提供给UI控制器。

  • 使用 Data Binding  在视图和UI控制器之间保持干净的界面。这使你可以使你的视图更具说明性,并最大限度地减少需要在 Activity 和 Fragment 中编写的更新代码。如果你喜欢用 Java 编程语言来做到这一点,可以使用像 Butter Knife 这样的库来避免样板代码并且有更好的抽象。

  • 如果你的UI很复杂,请考虑创建一个  presenter 类来处理UI修改。这可能是一项艰巨的任务,但它可以使你的UI组件更易于测试。

  • 避免在你的 ViewModel 中引用一个 View 或 Activity 上下文。如果 ViewModel比 Activity 活的更长(在配置更改的情况下),Activity 就会发生泄漏并且垃圾收集器无法妥善处理。

 

-----------------------------------------------------

 

LiveData

 

1、LiveData是什么?

 

LiveData是一个可观察的数据持有者类。

与常规可观察性不同,LiveData具有生命周期感知能力,这意味着它尊重其他应用程序组件(例如Activities,Fragments或Services)的生命周期。这种意识确保LiveData只更新处于活动生命周期状态的应用程序组件观察者。

 

如果LiveData的Observer生命周期处于STARTEDRESUMED状态,LiveData将认为由该类表示的观察者处于活动状态。LiveData仅通知活跃的观察员关于更新。LiveData未注册观察对象的

非活动观察者不会收到有关更改的通知。

总结为以下三点:

 

  • 数据可以被观察者订阅;

  • 能够感知组件(Fragment、Activity、Service)的生命周期;

  • 只有在组件出于激活状态(STARTED、RESUMED)才会通知观察者有数据更新;

 

2、LiveData的优点

 

  • 保证数据和UI统一

LiveData遵循观察者模式。Observer当生命周期状态改变时,LiveData会通知对象。 你可以合并代码以更新这些Observer对象中的UI 。 每次应用程序数据更改时,你的观察者都可以在每次更改时更新UI,而不是每次更新UI。

 

  • 减少内存泄漏

这是因为LiveData能够感知到组件的生命周期,当组件处于DESTROYED状态时,观察者对象会被清除掉。
  • Activity停止时不会发生崩溃

如果观察者的生命周期处于非活动状态,例如在后退堆栈中的Activity,不会收到任何LiveData事件。
  • 不需要额外的手动处理来响应生命周期的变化

UI组件只是观察相关数据,不会停止或重复观察。

LiveData自动管理所有这些,因为它在观察时意识到相关的生命周期状态更改。

  • 始终保持最新的数据

如果生命周期变为非活动状态,它将在再次变为活动状态时收到最新数据。

例如,在请求数据时应用退到后台,此时不会向控件中加载数据,当应用回到前台时,控件会立即收到最新数据。

  • 针对configuration change时,不需要额外的处理来保存数据

我们知道,当你把数据存储在组件中时,当configuration change(比如语言、屏幕方向变化)时,组件会被recreate,然而系统并不能保证你的数据能够被恢复的。当我们采用LiveData保存数据时,因为数据和组件分离了。当组件被recreate,数据还是存在LiveData中,并不会被销毁。
  • 资源共享

通过继承LiveData类,然后将该类定义成单例模式,在该类封装监听一些系统属性变化,然后通知LiveData的观察者。任何需要该资源的观察者都可以观察该LiveData对象。这个在继承LiveData中会看到具体的例子。

 

3、LiveData的使用

 

在了解LiveData定义和优点后,那它到底怎么应用呢?LiveData有几种使用方式:

 

  • 使用LiveData

  • 继承LiveData

  • 转换LiveData

 

1)使用LiveData

 

使用LiveData对象主要有三个步骤:

 

  1. 创建LiveData对象

  2. 观察LiveData对象

  3. 更新LiveData对象

 

创建LiveData

 

Android文档中建议LiveData配合ViewModel使用。LiveData是一个包装器,可用于任何数据,包括实现的对象Collections,例如List。一个 LiveData对象通常存储在一个ViewModel 对象中,并通过getter方法访问。

 

public class NameViewModel extends ViewModel {// Create a LiveData with a Stringprivate MutableLiveData mCurrentName;    public MutableLiveData getCurrentName() {        if (mCurrentName == null) {            mCurrentName = new MutableLiveData();        }        return mCurrentName;    }// Rest of the ViewModel...}

 

观察LiveData

 

大多数情况下,Activity或Fragment的onCreate方法是观察LiveData的正确位置,原因有以下两点:

 

  • 确保系统不会从Activity或Fragment的onResume()方法进行多余的调用。

  • 确保Activity或Fragment具有一旦它变为活动状态即可显示的数据。只要应用程序组件处于该 STARTED 状态,它就会从LiveData它所观察的对象中接收最新的值。只有当LiveData要观察的对象已被设置时才会发生这种情况。

 

通常,LiveData仅在数据更改时传递更新,并且仅传递给活动观察者。

此行为的一个例外是,观察者在从非活动状态变为活动状态时也会收到更新。

此外,如果观察者第二次从非激活状态变为激活状态,则只有在自上一次变为活动状态以来该值发生变化时才会收到更新。

 

public class NameActivity extends AppCompatActivity {    private NameViewModel mModel;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        // Other code to setup the activity...        // Get the ViewModel.        mModel = ViewModelProviders.of(this).get(NameViewModel.class);        // Create the observer which updates the UI.        final Observer nameObserver = new Observer() {            @Override            public void onChanged(@Nullable final String newName) {                // Update the UI, in this case, a TextView.                mNameTextView.setText(newName);            }        };        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.        mModel.getCurrentName().observe(this, nameObserver);    }}

 

更新LiveData

 

LiveData没有公开可用的方法来更新存储的数据。MutableLiveData 类公开 setValue(T) 和 postValue(T) 方法,如果你需要编辑存储在LiveData对象中的值,你必须使用这些方法。

 

mButton.setOnClickListener(new OnClickListener() {    @Override    public void onClick(View v) {        String anotherName = "Google";        mModel.getCurrentName().setValue(anotherName);    }});

 

LiveData提供了两种改变数据的方法:setValue()和postValue()。区别是setValue()要在主线程中调用,而postValue()既可在主线程也可在子线程中调用。

 

2)继承LiveData

 

除了直接使用LiveDatad对象外,我们还可以通过继承LiveData类来定义适合特定需求的LiveData。下面继承LiveData类的例子,验证下LiveData的其中一个优点——资源共享。

 

public class MyLiveData extends LiveData {    private static final String TAG = "MyLiveData";    private static MyLiveData sData;    private WeakReference mContextWeakReference;    public static MyLiveData getInstance(Context context){        if (sData == null){            sData = new MyLiveData(context);        }        return sData;    }    private MyLiveData(Context context){        mContextWeakReference = new WeakReference<>(context);    }    @Override    protected void onActive() {        super.onActive();        registerReceiver();    }    @Override    protected void onInactive() {        super.onInactive();        unregisterReceiver();    }    private void registerReceiver() {        IntentFilter intentFilter = new IntentFilter();        intentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);        mContextWeakReference.get().registerReceiver(mReceiver, intentFilter);    }    private void unregisterReceiver() {        mContextWeakReference.get().unregisterReceiver(mReceiver);    }    private BroadcastReceiver mReceiver = new BroadcastReceiver() {        @Override        public void onReceive(Context context, Intent intent) {            String action = intent.getAction();            Log.d(TAG, "action = " + action);            if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {                int wifiRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);                int wifiLevel = WifiManager.calculateSignalLevel(                        wifiRssi, 4);                sData.setValue(wifiLevel);            }        }    };}

 

MyLiveData是个继承了LiveData的单例类,在onActive()和onInactive()方法中分别注册和反注册Wifi信号强度的广播。然后在广播接收器中更新MyLiveData对象。在使用的时候就可以通过MyLiveData.getInstance()方法,然后通过调用observe()方法来添加观察者对象,订阅Wifi信息强度变化。

 

3)转换LiveData

 

LiveData 还支持简单的数据变换。目前在 Transformations 类中有 map 和 switchMap 两个变换函数,如果属性 RxJava 则对这两个函数应该不陌生:

 

  • map 是把一个数据类型变换为另外一个数据类型。

  • switchMap 是把一个数据变化为另外一个 LiveData 。

 

public class LiveDataActivity extends LifecycleActivity {    private TextView mTextView;    private TextView mCountryTV;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mTextView = (TextView) findViewById(R.id.text);        mCountryTV = (TextView) findViewById(R.id.country);        demoTransformation();    }    private void demoTransformation() {        LocationLiveData data = LocationLiveData.getsIns(this);        LiveData stringLatLon = Transformations.map(data,                location -> location.getLatitude() + ", " + location.getLongitude());        stringLatLon.observe(this, latLon -> {            mTextView.setText(latLon);        });        Transformations.switchMap(data, location -> getCountryName(location))            .observe(this, country -> mCountryTV.setText(country));    }    // 返回当前位置所在的国家名字    private LiveData getCountryName(Location loc) {        // 只是为了演示,实际中需要把转换的逻辑封装到自定义的 LiveData 中,        // 和 LocationLiveData 类似        MutableLiveData ld = new MutableLiveData<>();        ld.postValue("中国");        return ld;    }}

 

使用 Transformation 的好处是可以共享 Lifecycle。如果 Lifecycle 处于未激活状态,则转换函数不会执行。

当你在 ViewModel 中需要一个 Lifecycle 的时候,则可能这个时候就需要使用 Transformation 了。

例如,你现在有个让用户输入地址的界面,当用户输入地址后,去获取对应的邮编,下面是一种天真的 ViewModel 实现方式:

 

class MyViewModel extends ViewModel {    private final PostalCodeRepository repository;    public MyViewModel(PostalCodeRepository repository) {       this.repository = repository;    }    private LiveData getPostalCode(String address) {       // 不要这样干!!!       return repository.getPostCode(address);    }}

 

上面实现的问题是,当每次调用 getPostalCode() 函数的时候, UI 都需要从前一个 LiveData 中取消注册然后重新注册到 getPostalCode() 函数返回的 LiveData 中。如果 UI 重新创建了,甚至会导致 repository.getPostCode() 重新被调用而不是使用之前缓存的值。

可以使用 Transformation 的 switchMap 来解决上面的问题,把输入的地址当做一个 LiveData,然后根据地址的变化去获取邮编:

 

class MyViewModel extends ViewModel {    private final PostalCodeRepository repository;    private final MutableLiveData addressInput = new MutableLiveData();    public final LiveData postalCode =            Transformations.switchMap(addressInput, (address) -> {                return repository.getPostCode(address);             });    public MyViewModel(PostalCodeRepository repository) {        this.repository = repository    }    private void setInput(String address) {        addressInput.setValue(address);    }}

 

注意 上面的 postalCode 变量由于无需变化,所以声明为 final 的。postalCode 定义了一个 Transformation 把 addressInput 转换为 邮编。当地址发生变化的时候,如果有活动的 Observer 则会触发 repository.getPostCode() 函数去请求邮编,如果没有活动的 Observer ,则该函数不会执行,当有活动的 Observer 后会继续执行。

 

有十几种不同的特定转换可能在你的应用中很有用,但它们不是默认提供的。为了实现你自己的转换,你可以使用这个MediatorLiveData 类来监听其他 LiveData对象并处理它们发出的事件。MediatorLiveData正确地将其状态传播到源LiveData对象。要了解更多关于这种模式的信息,请参阅Transformations 该类的参考文档 

 

-----------------------------------------------------

 

ViewModel

 

1、ViewModel是什么

 

ViewModel,从字面上理解的话,我们也能想到它肯定是跟视图(View)以及数据(Model)相关的。正像它字面意思一样,它是负责准备和管理和UI组件(Fragment/Activity)相关的数据类,也就是说ViewModel是用来管理UI相关的数据的。同时ViewModel还可以用来负责UI组件间的通信,这一点后面我们会有例子说明。

 

2、ViewModel能够做什么

 

  • 保存(获取)数据

当Activity因为配置更改重新创建时,Activity必须重新获取数据。对于简单的数据,Activity可以使用 onSaveInstanceState()方法并从数据包中恢复其数据 ,但是此方法仅适用于可以序列化然后反序列化的少量数据,而不适用于潜在的大量数据,如用户列表或位图,而由于ViewModel贯穿了Activity的整个生命周期,且recreate时仍然存在,则可以用于保存/获取数据
  • 异步获取数据

UI控制器经常需要进行异步调用,这可能需要一些时间才能返回。UI控制器需要管理这些调用,并确保系统在销毁后清理它们以避免潜在的内存泄漏或崩溃。而通过ViewModel,可以拥有跟随Activity或Fragment的生命周期,无需关心内存泄漏和崩溃。
  • UI组件(Fragment)之间的通信

 

public class SharedViewModel extends ViewModel {    private final MutableLiveData selected = new MutableLiveData();    public void select(Item item) {        selected.setValue(item);    }    public LiveData getSelected() {        return selected;    }}public class MasterFragment extends Fragment {    private SharedViewModel model;    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);        itemSelector.setOnClickListener(item -> {            model.select(item);        });    }}public class DetailFragment extends Fragment {    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);        model.getSelected().observe(this, item -> {           // Update the UI.        });    }}

 

3、ViewModel的基本使用

在LiveData中已经使用了ViewModel,大家不记得的可以回头去看 LiveData

4、ViewModel生命周期

先来看下官网的一张图

上图是用Activity作为例子,左侧表示Activity的生命周期状态,右侧绿色部分表示ViewModel的生命周期范围。当屏幕旋转的时候,Activity会被recreate,Activity会经过几个生命周期方法,但是这个时候ViewModel还是之前的对象,并没有被重新创建,只有当Activity的finish()方法被调用时,ViewModel.onCleared()方法会被调用,对象才会被销毁。这张图很好的描述了是当Activity被recreate时,ViewModel的生命周期。

 

  另外,有个注意的地方:在ViewModel中不要持有Activity的引用。为什么要注意这一点呢?从上面的图我们看到,当Activity被recreate时,ViewModel对象并没有被销毁,如果Model持有Activity的引用时就可能会导致内存泄漏。那如果你要使用到Context对象怎么办呢,那就使用ViewModel的子类 AndroidViewModel吧。

5、实现原理

类图

 

  • ViewModelProviders是ViewModel工具类,该类提供了通过Fragment和Activity得到ViewModel的方法,而具体实现又是有ViewModelProvider实现的。ViewModelProvider是实现ViewModel创建、获取的工具类。在ViewModelProvider中定义了一个创建ViewModel的接口类——Factory。ViewModelProvider中有个ViewModelStore对象,用于存储ViewModel对象。

  • ViewModelStore是存储ViewModel的类,具体实现是通过HashMap来保存ViewModle对象。

  • ViewModel是个抽象类,里面只定义了一个onCleared()方法,该方法在ViewModel不在被使用时调用。ViewModel有一个子类AndroidViewModel,这个类是便于要在ViewModel中使用Context对象,因为我们前面提到是不能在ViewModel中持有Activity的引用。

  • ViewModelStores是ViewModelStore的工厂方法类,它会关联HolderFragment,HolderFragment有个嵌套类——HolderFragmentManager。

 

时序图

 

在使用ViewModel的例子中,也许你会察觉得到一个ViewModel对象需要的步骤有点多啊,怎么不直接new一个出来呢?在你看到ViewModel的类图关系后,你应该就能明白了,因为是会缓存ViewModel对象的。下面以在Fragment中得到ViewModel对象为例看下整个过程的时序图。

 

时序图看起来比较复杂,但是它只描述了两个过程:

  • 得到ViewModel对象。

  • HolderFragment被销毁时,ViewModel收到onCleared()通知。

大家也可以结合时序图分析一下源码!

更多相关文章

  1. “罗永浩抖音首秀”销售数据的可视化大屏是怎么做出来的呢?
  2. Nginx系列教程(三)| 一文带你读懂Nginx的负载均衡
  3. 不吹不黑!GitHub 上帮助人们学习编码的 12 个资源,错过血亏...
  4. Android辅助功能AccessibilityService自动全选择文字粘贴模拟输
  5. Android开发艺术探索 - 第10章 Android的消息机制
  6. Android(安卓)OpenGL ES 开发中的Buffer使用
  7. 源码分析--xutil3 网络源码分析
  8. Android:消息机制
  9. 对hint 设置大小及颜色,可用此方法对字符串设置不同颜色或大小

随机推荐

  1. 相对布局中取值为其他控件id 的属性及说
  2. 阿里巴巴Android开发手册
  3. Android(安卓)动画——Frame Animation与
  4. Android自适应不同屏幕几种方法
  5. android 使用contentobserver监听数据库
  6. 开发 Standalone Android(安卓)Java 应用
  7. Android(安卓)Frameworks系列(一) startS
  8. Android的消息机制(java层)
  9. Android推送通知指南
  10. 一个资深的Android开发者需要掌握哪些技