Android开发套路收集整理与讨论

原文一开始是写在csdn上的,复制过来java

如下作法纯属我的习惯,欢迎讨论:Dandroid

initView()与updateView()

一般,我会添加一个initView()方法来初始化全部的View对象,在这个方法的具体实现中,可能会有两种不一样的细微差异。第一种是仅仅作findViewById()就行了,也就是仅仅是去找到每个View对象,而不去给它们设置属性,好比setText()之类的。另外一种则是在findViewById()后,顺便给它们设置初始值。git

我更倾向于第一种作法,由于若是你在initView()方法中给View设置一些属性,那么当一些数据变动时,你可能也须要去变动View的一些属性,你必然会有一个updateView()这样的方法。updateView()方法中,须要根据当前页面的状态和数据去给View设值,问题就在于,当需求发生变化的时候,你可能须要改两个地方,initView()updateView()。考虑到这一点。最佳的作法就是你须要一个initView()方法和一个updateView()方法。程序员

initView()方法只作初始化操做,也就是仅仅只会发生一次的操做,好比findViewById()setListener()之类的。而updateView()方法中,则是去作一些根据某些成员变量,flag,boolean值之类的去变动View的属性,会被反复调用的操做。github

关于updateView()方法,我又有两种不一样的思路,在此以前,先具体的说明一下updateView()中要干的工做。好比咱们有一些成员变量dataAdataB,有一些会随之变化的ViewViewA1ViewA2ViewB1ViewB2……而后当数据dataA改变时,咱们须要更改ViewA1ViewA2的属性,当数据dataB改变时,咱们要更改ViewB*的属性,因而,咱们一般写的updateView()方法是这样的。网络

private void updateView() {

    ...
    viewA1.setText(dataA.getContent());
    viewA2.setTextColor(dataA.getTextColor());
    
    viewB1.setImage(dataB.getImage());
    viewB2.setText(dataB.getTitle());
    ...
}

在咱们的Activity/Fragment比较简单的时候,这样写应该没有什么问题,可是当页面的逻辑因需求的变动而变得愈来愈复杂,咱们可能须要维持不少不少的成员变量(数据)和View。那么updateView()方法可能里面作了不少不少的工做,这样调用一次必然是效率低下的。所以,我认为另外一种比较好的方式是将数据A所关联的Views都封装成一个方法,数据B所关联的Views都封装成另外一个方法,像这样。app

private void updateAViews() {
    viewA1.setText(dataA.getContent());
    viewA2.setTextColor(dataA.getTextColor());
    ...
}

private void updateBViews() {
    viewB1.setImage(dataB.getImage());
    viewB2.setText(dataB.getTitle());
    ...
}

private void updateAllViews() {
    updateAViews();
    updateBViews();
    ...
}

显然,第二种方式是效率最好的一种方式,也是维护起来最麻烦的一种方式,但我我的仍是比较倾向于第二种写法。由于有一些View它的onDraw()方法自己真的会消耗比较长的时间,若是简单粗暴的更新全部的View,可能会让UI的流畅度大打折扣。ssh

使用boolean值来避免updateView()中的空指针异常

当咱们使用initView()updateView()两个方法来变动View的时候,要注意空指针的状况,由于调用updateView的时机不是本身能控制的,updateView多是在网络数据返回时调用,那么若是onCreate的时候先请求数据,数据立刻返回了并调用updateView方法,这个时候,initView尚未执行,那么updateView中对View的操做就会报空指针异常。异步

咱们可使用一个boolean值来解决这个问题。ide

提早考虑Activity和Fragment的复用

当咱们写ActivityFragment的时候须要考虑到这个页面可能会从哪些地方调过来。好比说,咱们要完成一个需求,这个需求是显示一个列表,列表里面有特定的数据,这个页面必需要本身全新写一个ActivityFragment来完成,入口也只有一个,那么咱们几乎是能够“随心所欲”的实现这个页面,想怎么写就怎么写。

可是当需求发生了变化,好比其余地方也能够点击进入你这个页面,而且还显示了不同的数据,考虑到页面复用这一点,咱们应该经过传入不一样的参数,来改变这个页面的行为(应该显示怎么样的数据,或者UI上有哪些其余的变化)。

因此,在咱们全新写这个页面的时候,就应该有所收敛,要主动思考一下,由于这个页面若是是被复用的,那么通常来讲,是这个页面的样式,行为会被复用。不同的地方每每是数据,页面的复用,就要考虑到在onCreate的时候能够传入不一样的参数,完成不一样的要求和显示。

咱们应该在ActivityFragment中添加几个成员变量,用来标记状态,好比:

public class DataListActivity extends Activity {

    public static final int DATA_TYPE_ALL = 1;
    public static final int DATA_TYPE_PART = 2;

    private int mDataType = DATA_TYPE_ALL;
    
    ...
}

这样,咱们内部获取数据的时候就根据这个mDataType来作具体的处理就行了。考虑到复用这一点,后面扩展的时候就会更游刃有余。而且这个mDataType也许会影响到UI上的一些表现,updateView系列方法可能也须要关心这个(些)变量的状况。

经过封装好的静态方法启动Activity

初学的时候,咱们老是是用下面相似的代码启动Activity

Intent i = new Intent();
i.setClass(context, TargetActivity.class);
context.startActivity(i);

可是,根据上一个小主题上面所说的,每每咱们须要告诉要启动的Activity一些特定的信息,而后展现出不一样的行为,通常有两种常见的写法。

方式A:

public class TargetActivity extends Activity {

    public static final String INTENT_KEY_DATA_TYPE = "INTENT_KEY_DATA_TYPE";
    
    public static final int DATA_TYPE_ALL = 1;
    public static final int DATA_TYPE_PART = 2;
    
    public static void start(Context c, int dataType) {
        Intent i = new Intent();
        i.setClass(c, TargetActivity.class);
        i.putExtras(INTENT_KEY_DATA_TYPE, dataType);
        c.startActivity(i);
    }
}

//in other Activity
TargetActivity.start(context, TargetActivity.DATA_TYPE_ALL);

方式B:

public class TargetActivity extends Activity {

    public static final String INTENT_KEY_DATA_TYPE = "INTENT_KEY_DATA_TYPE";
    
    public static final int DATA_TYPE_ALL = 1;
    public static final int DATA_TYPE_PART = 2;
    
    public static Intent obtainIntent(Context, int dataType) {
        Intent i = new Intent();
        i.setClass(c, TargetActivity.class);
        i.putExtras(INTENT_KEY_DATA_TYPE, dataType);
        return i;
    }
}

//in other Activity.
startActivity(TargetActivity.obtainIntent(this, TargetActivity.DATA_TYPE_ALL));

方式A更简洁,方式B更繁琐一些,可是方式B更好,由于有时候咱们须要启动的Activity结束时返回一些东西,那么咱们须要调用到startActivityForResult()方法来启动,在当前的Activity调用这个方法,必需要获取到Intent对象,因此,方式B的obtainIntent使用状况就更普遍了。

但在编写obtianIntent方法的时候,建议让它带上你须要传递的参数,当前的demo是只有一个int型的dataType,也许你还有不少其余的参数,但都请在obtainIntent方法中就给Intent填上,这样外面(其余)的Activity就不须要去填写这些额外的信息了,你的INTENT_KEY能够彻底的定义在要用它的内部,这样作真是又干净又漂亮。

父类应该减轻子类的负担,而不是给子类添加约束

上面几个话题,咱们讲了几个常见的套路作法,这样可使代码更加清晰,更加易于维护。
可是咱们习惯的套路中那些initViewupdateViewobtainIntent等方法,并不适合移动到父类去,由于这不是逻辑,若是你挪到父类中写成抽象方法,方法就是限定死了,全部的子类都要有这个initView方法,这样是不合适的,不一样的人也许有不一样的代码习惯,所以将多余的流程挪到父类,就会造成对子类的约束。子类中若是有重复的逻辑,才是应该移动到父类的。

监听器,观察者模式,回调

其实监听器和观察者模式,回调都是同样的东西,表面上看,它们就是一群叫OnXxxxx的一群方法或者接口。
它们负责告诉你一些事件发生了,好比系统给你的onClickonTouchonSrcoll……还能够是在新的线程发起一个网络请求,当请求结果返回时,告诉你,像onResultonPush……这样的形式。
总之,当你理解了这个东西,你就能够熟练的使用,当你想写一个控件,这个控件要完成一个功能或者一些特性,你须要提供一些回调接口来供客户程序员使用。好比我以前写过一个底部有loading的控件,滚动到底部的时候,会出现一个loading(转菊花),而后给你一个“时机”来让你请求数据,而后让adapter更新数据。这里有是具体的代码:BottomLoadListView.java in github

一般,咱们能够把这个回调接口都让Activity或者Fragment来实现,像这样:

public class MyActivity extends Activity implement OnClickListener, OnNetworkChangeListener, IOnRequestCallback{
    ...
}

这样,这个Activity内部的一些对象须要回调接口的时候,直接给它this便可,就不须要那么多匿名内部类了,而这些回调方法都放在Activity中,当它们被调用的时候,也能很好的控制整个Activity的行为,是很方便的。

多个页面共用数据与回调

一般,咱们某一个页面(Activity/Fragment)须要显示一些数据,这些数据的引用都是让Activity本身持有的,若是仅仅是一个页面须要这些数据,这么作没有什么问题,当咱们有两个页面须要对同一份数据进行操做的时候,这样作就不太方便了。一般能够写一个名为XxxxEngine的东西,xxx具体是什么跟所关联的业务逻辑有关,好比说是消息列表,那么就叫MessageEngine好了。

这个Engine通常会写成单例模式,而后让它来持有数据的引用,而两个或多个页面须要对这份消息列表(message list)进行操做的时候,就经过这个Engine来获取就好了。

使用Engine还有另外一个场景,就是两个页面都须要监听某一个网络push,好比说在多终端的状况下,咱们有一个我的信息页面,我的信息是能够在别的终端被修改的,那么咱们的页面就会收到一个通知,有时候,通知回调是不带数据的,咱们须要手动去拉去数据,就算带上了数据,若是两个页面都监听这个网络回调,也会有问题,由于这样就有两份数据,或者说有两个地方会对数据进行操做。我用来代码来演示。

public class ProfileActivity extends Activity implement OnProfileChangedListener, OnResultForProfileRequest {
    
    private Profile mProfile = null;
    
    //当别的终端更新了我的信息后调用这里
    @override
    public void onProfileChanged() {
        ProfileManager.getInstance().requestProfile(this);  //传入OnResultForProfileRequest接口
    }
    
    //当requestProfile()请求结果返回时调用
    @override
    public void onResult(Profile profile) {
        mProfile = profile;
        updateView();
    }
}

上面代码展现了一个页面收到数据变动的通知以及请求数据的状况,那么当咱们有两个页面都须要关心数据发生变化的时候,若是两个页面都像上面这样写,那么咱们就有两处来请求数据,这样是很差的,由于两个地方用的是同一份数据,这样根据上面说的,咱们须要一个ProfileEngine来维持这份数据的引用,另外一方面,咱们能够把profile changed的监听,放在ProfileEngine上,这样就只有它一个地方收到变化的通知,一个地方来拉取最新数据,更新好了以后,再通知两个(多个)页面经过单例来获取最新的数据。这种情形下,咱们须要定义一个本地的接口。

public class ProfileEngine implement OnRemoteProfileChangedListener, OnResultForProfileRequest {
    
    public interface OnLocalProfileChangedListener {
    
        void onLocalProfileChanged(Profile newProfile);
    }
    
    private Profile mProfile = null;
    
    //监听列表
    private ArrayList<OnLocalProfileChangedListener> mListeners = new ArrayList<>();
    
    //当别的终端更新了我的信息后调用这里
    @override
    public void onProfileChanged() {
        ProfileManager.getInstance().requestProfile(this);  //传入OnResultForProfileRequest接口
    }
    
    //当requestProfile()请求结果返回时调用
    @override
    public void onResult(Profile profile) {
        mProfile = profile;
    }
    
    //通知全部的页面,profile发生了变动,而且已经取好了最新的数据了,拿过去更新UI就行了
    private void notifyListener() {
        for (OnLocalProfileChangedListener l : mListeners) {
            l.onLocalProfileChanged(mProfile);
        }
    }
}

这个套路感受真的很简洁干练,但咱们须要注意一个问题就是本地的监听的注册与反注册。
单例一旦被建立就不会被销毁了,除非进程被干掉,或者咱们主动置空(null)而且GC。也就是说,这个单例一般状况下会一直在内存中的,也会一直监听remote的profile变化,而且会去拉去最新的数据,请注意这里的mListeners,里面存放的两个页面(Activity/Fragment),若是咱们没有在页面销毁(onDestory)的时候将本身从监听列表中移除,那么mListeners就会一直持有Activity的引用,可是页面却已是消失了,这样就形成了内存泄露。所以必定要严格的在onCreateonDestory中调用注册与反注册方法。

一种网络请求套路

这种网络请求套路也是最近才学习到的,感受很是的简单巧妙。

//发起一个请求检查一下数据是否有变动,若是有变动,会经过通知onChanged()告诉客户端,无参数无返回值
void check();

//通知,告知客户端数据有变动,要拉取最新数据须要另外一个接口,无参数,无返回值
void onChanged();

//经过网络拉取数据,无返回值,传入回调接口,由于是异步返回数据
void request(onRequestResult);

//请求数据的回调接口,参数中是最新的数据
void onRequestResult(Data)

//经过网络更新数据,无返回值,经过参数传入新数据和回调接口
void set(Data, OnSetResult);

//更新数据的回调接口,参数表示有没有成功,以及最新的数据,同时也会调用onChanged()方法
void onSetResult(int, Data);

能够发现,数据变化的时候,老是会调用onChanged()方法,而这仅仅是通知,获取数据须要本身手动去拉取一次。这样咱们有统一的时机能够获取最新的数据。

以上作法纯属我的习惯,欢迎讨论:D

相关文章
相关标签/搜索