开源中国项目,看懂了,就能得到很多启发,包括框架、设计模式等等。
http://www.oschina.net/question/213217_60071
大总结:
【网络框架】
通过OSChinaApi、ApiHttpClient、AsyncHttpClient三个类来实现,哪么这三个类是如何
进行关联呢?
【开源中国中用到的自定义Widget,有可能是开源组件。】
1)BadgeView:主要是在代码里动态的标记在某个View上,如消息提示的个数等等。
/**
* A simple text label view that can be applied as a "badge" to any given
* {@link android.view.View}. This class is intended to be instantiated at
* runtime rather than included in XML layouts.
*
* @author Jeff Gilfelt
*/
http://www.xuebuyuan.com/1964298.html
部分核心源码――――标记View是怎么绑定到目标View上去的。
private void applyTo(View target) { LayoutParams lp = target.getLayoutParams(); ViewParent parent = target.getParent(); FrameLayout container = new FrameLayout(context); if (target instanceof TabWidget) { // set target to the relevant tab child container target = ((TabWidget) target).getChildTabViewAt(targetTabIndex); this.target = target; ((ViewGroup) target).addView(container, new LayoutParams( LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); this.setVisibility(View.GONE); container.addView(this); } else { // TODO verify that parent is indeed a ViewGroup ViewGroup group = (ViewGroup) parent; int index = group.indexOfChild(target); group.removeView(target); group.addView(container, index, lp); container.addView(target); this.setVisibility(View.GONE); container.addView(this); group.invalidate(); } }
2)CircleImageView:圆形图片
Android ImageView圆形头像 图片完全解析
http://blog.csdn.net/feixiangdexin123087/article/details/42076987
3)MyQrodeDialog:自定义对话框,有点类似透明Activity的那种Dialog。
【开源中国中用到的特殊数据类型】
1)枚举(enum):可列举性
SimpleBackPage:对即将要放入SimpleBackActivity的Fragment的封装,通过value值来作为
Fragment存取的唯一标识。
2)Android中WebView与js及css交互
【开源中国中的工具类】
1)XmlUtils:对XStream的封装,将xml流转换成bean实体类,当然也可以将其它的类型转换成bean实
体类。
2)QrCodeUtils:根据字符串生成二维码,内部采用ZXING库中的MultiFormatWriter类根据字符串生
成矩阵
3)DialogHelp:得到不同格式的对话框,包括确认对话框、进度对话框等等。
4)
【一般一个应用必须要有的一些类】
1)Application的子类:用于作一些全局的初始化操作,包括网络、登录
2)AppConfig:保存一些配制信息,一般采用Properties进行封装。也可以采用其它的存储方式
3)
■2015年10月8日晚---核心基类
BaseApplication类
继承了Application,在onCreate()方法里初始化了Context和Resources,其它的就是一些静态方法:sp存储与判断、获取屏幕的宽高、单例自定义视图的吐司。
这个类可以应用于几乎所有的应用的全局Application类的父类。
BaseActivity类
继承自ActionBarActivity,并实现了DialogControl,BaseViewInterface等接口,DialogControl
用于控制进度状态的显示,BaseViewInterface用于初始化View和Data。
SimpleBackActivity 继承自BaseActivity(****************************)
用于展示一些Fragment,并且Activity是具有回退按钮。
-------------------------------------------------
复写了布局指定方法
解析Intent传过来的参数,得到SimpleBackPage封装的Fragment及其class等信息,
并且利用反射+枚举技术实例化Fragment并填充到容器中
@Override protected int getLayoutId() { return R.layout.activity_simple_fragment; } @Override protected boolean hasBackButton() { return true; } @Override protected void init(Bundle savedInstanceState) { super.init(savedInstanceState); if (mPageValue == -1) { mPageValue = getIntent().getIntExtra(BUNDLE_KEY_PAGE, 0); } initFromIntent(mPageValue, getIntent()); } protected void initFromIntent(int pageValue, Intent data) { if (data == null) { throw new RuntimeException( "you must provide a page info to display"); } SimpleBackPage page = SimpleBackPage.getPageByValue(pageValue); if (page == null) { throw new IllegalArgumentException("can not find page by value:" + pageValue); } setActionBarTitle(page.getTitle()); try { Fragment fragment = (Fragment) page.getClz().newInstance(); //传到Activity的数据再设置给参数中的Fragment实例 Bundle args = data.getBundleExtra(BUNDLE_KEY_ARGS); if (args != null) { fragment.setArguments(args); } FragmentTransaction trans = getSupportFragmentManager() .beginTransaction(); trans.replace(R.id.container, fragment, TAG); trans.commitAllowingStateLoss(); mFragment = new WeakReference<Fragment>(fragment); } catch (Exception e) { e.printStackTrace(); throw new IllegalArgumentException( "generate fragment error. by value:" + pageValue); } }
再来看看值是怎么传到Activity的
public static void showSimpleBack(Context context, SimpleBackPage page) { Intent intent = new Intent(context, SimpleBackActivity.class); intent.putExtra(SimpleBackActivity.BUNDLE_KEY_PAGE, page.getValue()); context.startActivity(intent); }
枚举仅仅是作为传给Activity的参数而已,Activity通过
public final static String BUNDLE_KEY_PAGE = "BUNDLE_KEY_PAGE"; public final static String BUNDLE_KEY_ARGS = "BUNDLE_KEY_ARGS";
来判断传递的参数的类型,是枚举呢,还是一些额外的参数。
DetailActivity:继承自BaseActivity(****************************)
详情页面Activity
//发表评论的Fragment public KJEmojiFragment emojiFragment = new KJEmojiFragment(); //工具栏Fragment public ToolbarFragment toolFragment = new ToolbarFragment();
注意init方法的填充Fragment方式与SimpleBackActivity的区别,是用工厂模式创建,而
SimpleBackActivity是通过反射的方式来生成Fragment的。
@Override protected void init(Bundle savedInstanceState) { super.init(savedInstanceState); int displayType = getIntent().getIntExtra(BUNDLE_KEY_DISPLAY_TYPE, DISPLAY_NEWS); BaseFragment fragment = null; int actionBarTitle = 0; switch (displayType) { case DISPLAY_NEWS: actionBarTitle = R.string.actionbar_title_news; fragment = new NewsDetailFragment(); break; 。。。 default: break; } setActionBarTitle(actionBarTitle); FragmentTransaction trans = getSupportFragmentManager() .beginTransaction(); trans.replace(R.id.container, fragment); trans.commitAllowingStateLoss(); if (fragment instanceof OnSendClickListener) { currentFragment = (OnSendClickListener) fragment; } else { currentFragment = new OnSendClickListener() { @Override public void onClickSendButton(Editable str) { } @Override public void onClickFlagButton() { } }; } }
CacheManager:缓存管理类
包括存储对象和读取对象,以及判断缓存是否存在,判断缓存是否失效。
注意一下API:
Context.openFileInput("文件名",存储模式) 得到data/data/包名/文件名的输入流
Context.openFileOutput("文件名",存储模式) 得到data/data/包名/文件名的输出流
ObjectOutputStream、ObjectInputStream 对象序列化与反序列化流
File getFileStreamPath(String name):得到用openFileOutput存储的文件的绝对路径
Returns the absolute path on the filesystem where a file created with
openFileOutput(String, int) is stored.
■2015年11月2日晚
突然之间,好像思路全断了一样。只有从清单里挑着看了。
AppContext extends BaseApplication
AppContext:全局应用程序类:用于保存和调用全局应用配置及访问网络数据
定义了一个自身的引用instance,在onCreate方法中进行了初始化 instance = this;
onCreate方法里做了4个操作
1.init()方法:初始化网络请求
-----------------------------------------------
Android Asynchronous Http Client-Android异步网络请求客户端接口
http://blog.csdn.net/hil2000/article/details/13949513
PersistentCookieStore 用于存储Cookie,持久化存储登录状态。和AsyncHttpClient同
属于一个包中
[原]Android持久化保存cookie
http://www.tuicool.com/articles/IRBZjm
ApiHttpClient.setHttpClient(client); //为ApiHttpClient设置了AsyncHttpClient实例
TLog OSchina自带的带有开关的打印日志的工具类
2.initLogin()方法:初始化登录
---------------------------------------------------
利用AppConfig类读取Properties文件,获得用户的配置信息(如id,名称,位置等等)
3.指定异常处理器
4.发送通知广播
用户的登录和注销操作方法也在这个类里
AppConfig类 用于保护用户相关的消息和配置,保存的方式是通过Properties(继承于
Hashtable,所以就是键值对)
默认存放图片的路径 DEFAULT_SAVE_IMAGE_PATH
默认存放文件下载的路径 DEFAULT_SAVE_FILE_PATH
get和set方法
Android中关于内部存储的一些重要函数
http://blog.csdn.net/hudashi/article/details/8037076
登录与注销就是用的Properties来保存的
――――――――――――――――――――――――
登录与注销的逻辑(***************)
――――――――――――――――――――――――
1)登录初始化
2)注销登录
――――――――――――――――――――――――
AppException 应用程序异常:用于捕获异常和提示错误信息
定义异常的类型和状态码
获取APP异常崩溃处理对象
自定义异常处理:收集错误信息&发送错误报告(将异常存储到SD卡
捕获未处理的异常信息
UIHelper.sendAppCrashReport(context);【为什么这行代码放在Looper.prepare()
和Looper.loop()之间???】
|
通过DialogHelp(对话框辅助类)来提示用户程序发生了异常
)
应用有自己的异常处理框架,显得十分地专业。
AppStart 应用启动界面
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Android应用开发框架 KJFrameForAndroid
http://www.androidchina.net/1663.html (强烈推荐看看!)
在《开源中国》项目中,用到的类如下:
PreferenceHelper 就是对原生SP的封装,可以读写任意的SP文件。
KJAsyncTask 也是这个库中一个异步类,使用了线程池管理线程,弥补了原生的AsyncTask的
缺陷
FileUtils 和文件操作相关的类
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
TDevice 和Android设备相关的工具类
如获取手机屏幕的信息、判断手机是否有相机、有wifi以及网络类型等功能、px与dp单位互换
、获取应用的版本信息、判断设备有哪些应用(Manager层的封装)
AppStart所做的操作:
1.比对缓存(sp)里的应用版本号与应用当前版本号,如果缓存的版本号小于当前版本号,则将当
前版本号覆盖缓存版本号,并且清空图片缓存(异步操作使用KJAsyncTask)。
2.动画结束的时候,重定向操作:
1)创建并启动Service,如果BUG信息存储文件不为空,上传BUG信息到服务器,上传成功,删
除BUG信息存储文件。
无论哪种情况,最后都要关闭Service。
2)跳转到主界面
■2015年11月22日晚
》》MainActivity类
注意这个类是没有继承BaseActivity的,是直接继承自ActionBarActivity的。
ui设计
onCreate()方法:1)判断sp,切换夜间或白天主题。
2)initView()初始化控件(********************)
DoubleClickExitHelper双击退出帮助类
。。。
广播、服务
是否第一次启动,检查更新。
3) 将当前Activity压入栈中
4)handleIntent(getIntent());处理传进来的Intent
MainTab Tab选项卡的枚举,管理主界面的四大模块的Fragment。
DataCleanManager 数据删除工具类 UpdateManager 更新管理类
主界面的代码实现
》》BaseFragment 碎片基类
BaseViewPagerFragment 带有导航条的Fragment
PagerSlidingTabStrip 自定义的滑动选项卡(********************)
以NewsViewPagerFragment(综合模块)来说明作者的重构思路:
对于“带有导航条的Fragment”这一小框架的抽取,我与作者的思路简单就是如出一辙,甚至毫
不夸张的说,我的实现方式可能比作者的代码更加简单。
其实很多时候不要一开始就想代码怎么写,而是去思考事物的本质。如“带有导航条的Fragment",
很自然的想到ViewPager的item肯定要抽象化,而ViewPager的item是由Adapter来决定的。作者就将导
航条基类的Adapter只是new了出来,进行了一些初始化,对Adapter的具体操作是由不同的子类作不同
的实现(代码中表现的是addTab的个数和参数不同)。
而我对“带有导航条的Fragment”小框架的实现,也是通过Adapter入手,只是将Adapter的抽象
逐层向外转移,最终将Adapter的抽象转换成Fragment的抽象。这样用户在使用我这个框架的时候就
不需要去了解Adapter的抽象,只需要关注Fragment的抽象即可。我突然感觉这像某一种设计模式?
不知我的感觉对不对,我感觉重构其实就像是一种哲学。(大家也给评评我想法对吗?)
》》EmptyLayout(维护加载中、加载数据失败等状态的自定义布局类)
这种多个状态自定义布局类,是几乎每个应用都需要做的一种抽取。
然后通过两个方法来切换状态
■2015年11月23日晚
》》主界面模块之“我”-MyInformationFragment
ui设计(做Android,其实ui设计也是一门学问,不是吗?)
原则1:复杂ui模块化
头像区ui设计,包括登录和未登录2种状态(一般都是采用FrameLayout来完成的)
MyInformationFragment核心代码解析:
在onCreate方法里注册广播
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); IntentFilter filter = new IntentFilter(Constants.INTENT_ACTION_LOGOUT); filter.addAction(Constants.INTENT_ACTION_USER_CHANGE); getActivity().registerReceiver(mReceiver, filter); }
在onDestroy方法里注销广播
@Override public void onDestroy() { super.onDestroy(); getActivity().unregisterReceiver(mReceiver); }
在onCreateView方法里填充View并初始化
@Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_my_information, container, false); ButterKnife.inject(this, view); initView(view); //父类初始化View的接口 return view; }
在onViewCreated方法里请求网络数据并设置给View
@Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); requestData(true); mInfo = AppContext.getInstance().getLoginUser(); fillUI(); }
initView(View view)方法详解:为各个子View添加点击监听
requestData(true)方法详解:请求网络数据
可见作者的判断是十分的准确的(注意要加深理解作者的数据存储及缓存是怎么设计的)
作者将缓存的存储和读取都用AsyncTask来封装,读写文件属于耗时操作。
缓存存储类:
private class SaveCacheTask extends AsyncTask<Void, Void, Void> { private final WeakReference<Context> mContext; private final Serializable seri; private final String key; private SaveCacheTask(Context context, Serializable seri, String key) { mContext = new WeakReference<Context>(context); this.seri = seri; this.key = key; } @Override protected Void doInBackground(Void... params) { CacheManager.saveObject(mContext.get(), seri, key); return null; } }
缓存读取类:
private class CacheTask extends AsyncTask<String, Void, User> { private final WeakReference<Context> mContext; private CacheTask(Context context) { mContext = new WeakReference<Context>(context); } @Override protected User doInBackground(String... params) { Serializable seri = CacheManager.readObject(mContext.get(), params[0]); if (seri == null) { return null; } else { return (User) seri; } } //读取数据成功之后就填充UI @Override protected void onPostExecute(User info) { super.onPostExecute(info); if (info != null) { mInfo = info; // mErrorLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); // } else { // mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); fillUI(); } } }
■2015年11月30日晚
》》LoginActivity类
一般登录的逻辑:
1)登录成功之后,为什么要发送广播?-----因为登录之后,要请求登录之后的数据刷新另外一个界
面的UI,这时就要用到广播传递登录成功的消息及数据。
关于第3方登录,不得不明白一个东西OpenId。
什么是OpenID?OpenID概念、原理和案例http://www.uegeek.com/ue/openid_intro
QQ第3方登录的逻辑:
???????关于LoginActivity这一块,以后再仔细研究?????????
WX第3方登录的逻辑:
》》BaseListFragment类
带下拉刷新的Fragment,作者采用的是SwipRefreshLayout+ListView的组合方式。
在类的成员位置,一开始作者就定义了一个静态常量,用来分类标识Fragment。
public static final String BUNDLE_KEY_CATALOG = "BUNDLE_KEY_CATALOG";
填充的View:
@Override protected int getLayoutId() { return R.layout.fragment_pull_refresh_listview; }
初始化View方法:
@Override
public void initView(View view)
... mAdapter = getListAdapter(); //不同的页面的List展示的数据不一样,需要抽取出来。 ... protected abstract ListBaseAdapter<T> getListAdapter();
解析任务类:
class ParserTask extends AsyncTask<Void, Void, String> { private final byte[] reponseData; private boolean parserError; private List<T> list; public ParserTask(byte[] data) { this.reponseData = data; } @Override protected String doInBackground(Void... params) { try { ListEntity<T> data = parseList(new ByteArrayInputStream( reponseData)); new SaveCacheTask(getActivity(), data, getCacheKey()).execute(); list = data.getList(); if (list == null) { ResultBean resultBean = XmlUtils.toBean(ResultBean.class, reponseData); if (resultBean != null) { mResult = resultBean.getResult(); } } } catch (Exception e) { e.printStackTrace(); parserError = true; } return null; } @Override protected void onPostExecute(String result) { super.onPostExecute(result); if (parserError) { readCacheData(getCacheKey()); } else { executeOnLoadDataSuccess(list); executeOnLoadFinish(); } } }
executeOnLoadDataSuccess(list);将解析后的List设置给子类的适配器,通过Adapter刷新到
ListView上
【理解父类与子类的MVC是怎么相互衔接起来!!!!!!!!!!
其实还是逻辑的问题,到底哪些代码应该写在父类中,哪些代码要抽象让子类去实现。
将父类的戏做足,那么子类的代码也就简洁了。
】
写好了BaseListFragment,那么它的子类只需要指定Adapter,请求接口让父类Handler处理即可。
再来看作者的ListBaseAdapter类(********非常核心的一个类************)
复写getCount方法,根据是否有脚布局来决定是否要在条目的数目之上加1:
复写getItem和getItemId方法:
@Override public T getItem(int arg0) { if (mDatas.size() > arg0) { return mDatas.get(arg0); } return null; } @Override public long getItemId(int arg0) { return arg0; }
下面是对数据data的增删改查,同时进行刷新:
public int getDataSize() { return mDatas.size(); } public void setData(ArrayList<T> data) { mDatas = data; notifyDataSetChanged(); } public ArrayList<T> getData() { return mDatas == null ? (mDatas = new ArrayList<T>()) : mDatas; } public void addData(List<T> data) { if (mDatas != null && data != null && !data.isEmpty()) { mDatas.addAll(data); } notifyDataSetChanged(); } public void addItem(T obj) { if (mDatas != null) { mDatas.add(obj); } notifyDataSetChanged(); } public void addItem(int pos, T obj) { if (mDatas != null) { mDatas.add(pos, obj); } notifyDataSetChanged(); } public void removeItem(Object obj) { mDatas.remove(obj); notifyDataSetChanged(); } public void clear() { mDatas.clear(); notifyDataSetChanged(); }
动态设置脚布局Text的内容:
public void setLoadmoreText(int loadmoreText) { _loadmoreText = loadmoreText; } public void setLoadFinishText(int loadFinishText) { _loadFinishText = loadFinishText; } public void setNoDataText(int noDataText) { _noDateText = noDataText; }
复写适配器最重要的方法,将脚布局的情况判断出来,具体条目的getView方法抽取出来:
public View getView(int position, View convertView, ViewGroup parent) { if (position == getCount() - 1&&hasFooterView()) {// 最后一条 // if (position < _data.size()) { // position = getCount() - 2; // footview // } if (getState() == STATE_LOAD_MORE || getState() == STATE_NO_MORE || state == STATE_EMPTY_ITEM || getState() == STATE_NETWORK_ERROR) { this.mFooterView = (LinearLayout) LayoutInflater.from( parent.getContext()).inflate(R.layout.list_cell_footer, null); if (!loadMoreHasBg()) { mFooterView.setBackgroundDrawable(null); } ProgressBar progress = (ProgressBar) mFooterView .findViewById(R.id.progressbar); TextView text = (TextView) mFooterView.findViewById(R.id.text); switch (getState()) { case STATE_LOAD_MORE: setFooterViewLoading(); break; case STATE_NO_MORE: mFooterView.setVisibility(View.VISIBLE); progress.setVisibility(View.GONE); text.setVisibility(View.VISIBLE); text.setText(_loadFinishText); break; case STATE_EMPTY_ITEM: progress.setVisibility(View.GONE); mFooterView.setVisibility(View.VISIBLE); text.setText(_noDateText); break; case STATE_NETWORK_ERROR: mFooterView.setVisibility(View.VISIBLE); progress.setVisibility(View.GONE); text.setVisibility(View.VISIBLE); if (TDevice.hasInternet()) { text.setText("加载出错了"); } else { text.setText("没有可用的网络"); } break; default: progress.setVisibility(View.GONE); mFooterView.setVisibility(View.GONE); text.setVisibility(View.GONE); break; } return mFooterView; } } if (position < 0) { position = 0; // 若列表没有数据,是没有footview/headview的 } return getRealView(position, convertView, parent); } //将不确定的具体列表View抽取出来,部分抽象方法模块化。 protected View getRealView(int position, View convertView, ViewGroup parent) { return null; }
那么,任何一个BaseListFragment的子类的适配器,只要继承ListBaseAdapter并复写它的getView方
法就可以了。
》》CommonDetailFragment类
通用的详情Fragment
ui设计:
@Override protected int getLayoutId() { return R.layout.fragment_news_detail; }
代码设计:
结构设计和BaseFragment的直接子类BaseListFragment很像