原文一开始是写在csdn上的,复制过来java
如下作法纯属我的习惯,欢迎讨论:Dandroid
一般,我会添加一个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()
中要干的工做。好比咱们有一些成员变量dataA
,dataB
,有一些会随之变化的View
,ViewA1
,ViewA2
,ViewB1
,ViewB2
……而后当数据dataA
改变时,咱们须要更改ViewA1
,ViewA2
的属性,当数据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
当咱们使用initView()
和updateView()
两个方法来变动View
的时候,要注意空指针的状况,由于调用updateView
的时机不是本身能控制的,updateView
多是在网络数据返回时调用,那么若是onCreate
的时候先请求数据,数据立刻返回了并调用updateView
方法,这个时候,initView
尚未执行,那么updateView
中对View
的操做就会报空指针异常。异步
咱们可使用一个boolean
值来解决这个问题。ide
当咱们写Activity
或Fragment
的时候须要考虑到这个页面可能会从哪些地方调过来。好比说,咱们要完成一个需求,这个需求是显示一个列表,列表里面有特定的数据,这个页面必需要本身全新写一个Activity
或Fragment
来完成,入口也只有一个,那么咱们几乎是能够“随心所欲”的实现这个页面,想怎么写就怎么写。
可是当需求发生了变化,好比其余地方也能够点击进入你这个页面,而且还显示了不同的数据,考虑到页面复用这一点,咱们应该经过传入不一样的参数,来改变这个页面的行为(应该显示怎么样的数据,或者UI上有哪些其余的变化)。
因此,在咱们全新写这个页面的时候,就应该有所收敛,要主动思考一下,由于这个页面若是是被复用的,那么通常来讲,是这个页面的样式,行为会被复用。不同的地方每每是数据,页面的复用,就要考虑到在onCreate
的时候能够传入不一样的参数,完成不一样的要求和显示。
咱们应该在Activity
或Fragment
中添加几个成员变量,用来标记状态,好比:
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
。
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
能够彻底的定义在要用它的内部,这样作真是又干净又漂亮。
上面几个话题,咱们讲了几个常见的套路作法,这样可使代码更加清晰,更加易于维护。
可是咱们习惯的套路中那些initView
,updateView
,obtainIntent
等方法,并不适合移动到父类去,由于这不是逻辑,若是你挪到父类中写成抽象方法,方法就是限定死了,全部的子类都要有这个initView
方法,这样是不合适的,不一样的人也许有不一样的代码习惯,所以将多余的流程挪到父类,就会造成对子类的约束。子类中若是有重复的逻辑,才是应该移动到父类的。
其实监听器和观察者模式,回调都是同样的东西,表面上看,它们就是一群叫OnXxxxx
的一群方法或者接口。
它们负责告诉你一些事件发生了,好比系统给你的onClick
,onTouch
,onSrcoll
……还能够是在新的线程发起一个网络请求,当请求结果返回时,告诉你,像onResult
,onPush
……这样的形式。
总之,当你理解了这个东西,你就能够熟练的使用,当你想写一个控件,这个控件要完成一个功能或者一些特性,你须要提供一些回调接口来供客户程序员使用。好比我以前写过一个底部有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的引用,可是页面却已是消失了,这样就形成了内存泄露。所以必定要严格的在onCreate
和onDestory
中调用注册与反注册方法。
这种网络请求套路也是最近才学习到的,感受很是的简单巧妙。
//发起一个请求检查一下数据是否有变动,若是有变动,会经过通知onChanged()告诉客户端,无参数无返回值 void check(); //通知,告知客户端数据有变动,要拉取最新数据须要另外一个接口,无参数,无返回值 void onChanged(); //经过网络拉取数据,无返回值,传入回调接口,由于是异步返回数据 void request(onRequestResult); //请求数据的回调接口,参数中是最新的数据 void onRequestResult(Data) //经过网络更新数据,无返回值,经过参数传入新数据和回调接口 void set(Data, OnSetResult); //更新数据的回调接口,参数表示有没有成功,以及最新的数据,同时也会调用onChanged()方法 void onSetResult(int, Data);
能够发现,数据变化的时候,老是会调用onChanged()
方法,而这仅仅是通知,获取数据须要本身手动去拉取一次。这样咱们有统一的时机能够获取最新的数据。
以上作法纯属我的习惯,欢迎讨论:D