Mike按:
前一段时间,在工作学习方面有点迷茫。请教了两个高人。分别给出了两个互补的建议,受益匪浅,感谢!一是学习开源项目,二是在应用的实际开发中学习东西(按照自己的思路写一个app)。最近主要是优化重构之前的代码,将开源项目中比较好的部分应用到自己的项目中。本文就是基于此。
(一) 应用场景:刷新数据,显示加载进度条,数据准备,数据准备完毕,进度条消失,显示结果。
可能出现的状况:
1, 无网络,无法获取数据,有网络,显示提示
2, 一切正常,显示数据
3, 搜索无结果,各种错误:协议错误,超时
之前的思路,先显示Dialog,新启线程加载数据,数据加载完毕后,Handler发送消息,UI中刷新,Dialog消失。结果的显示使用帧布局。功能实现上ok,但是封装上不好,感觉代码分散,混乱。
(二) Jamendo的处理
将这个处理过程封装成LoadingDialog(继承AsyncTask),结果的显示上使用ViewFlipper。思路非常清晰。
1, 加载处理的基类LoadingDialog
a) Jamendo将加载的处理均使用LoadingDialog的实现子类。相关继承树(extend tree)如下图所示:
b) LoadingDialog继承AsyncTask,这样的好处是容易控制流程的先后顺序,而且UI线程和其他线程的切换非常的平滑。其类的结构如下图所示:
c) 定义
泛型的使用,代码如下:
- public abstract class LoadingDialog<Input, Result> extends AsyncTask<Input, WSError, Result>
d) 流程
绘制其流程图如下:
i. onPreExecute()
- @Override
- public void onPreExecute() {
- String title = "";
- String message = mActivity.getString(mLoadingMsg);
- mProgressDialog = ProgressDialog.show(mActivity, title, message, true, true, new OnCancelListener(){
- @Override
- public void onCancel(DialogInterface dialogInterface) {
- LoadingDialog.this.cancel(true);
- }
- });
- super.onPreExecute();
- }
显示Dialog,这个创建方法第一次见到,Dialog可被cancel掉,代码如下:
- @Override
- public void onCancelled() {
- if( mActivity instanceof PlayerActivity)
- {
- PlayerActivity pa = (PlayerActivity)mActivity;
- pa.doCloseActivity();
- }
- failMsg();
- super.onCancelled();
- }
ii. doInBackground()-------抽象方法
这里是处理数据的地方,Jamendo在这里定义了抽象方法doInBackGround(),以应对不同的需求,子类各自实现即可。代码如下:
- @Override
- public abstract Result doInBackground(Input... params);
iii. onPostExecute()
代码如下:
- @Override
- public void onPostExecute(Result result) {
- super.onPostExecute(result);
- mProgressDialog.dismiss();
- if(result != null){
- doStuffWithResult(result);
- } else {
- if( mActivity instanceof PlayerActivity)
- {
- PlayerActivity pa = (PlayerActivity)mActivity;
- pa.doCloseActivity();
- }
- failMsg();
- }
- }
数据获取结束之后,Dialog dismiss掉。数据获取有两种情况:
l 获取数据正常
调用doStuffWithResult(Result result)方法处理数据,此方法为抽象方法,需要子类实现,按照自己的需求处理,代码如下:
- /**
- * Very abstract function hopefully very meaningful name,
- * executed when result is other than null
- *
- * @param result
- * @return
- */
- public abstract void doStuffWithResult(Result result);
l 获取数据异常
调用failMsg()方法。即Toast,代码如下:
- protected void failMsg(){
- Toast.makeText(mActivity, mFailMsg, 2000).show();
- }
iv. onProgressUpdate()
显然,doInBackGround()方法中,可以实时的将一些信息(错误信息即WSError的Message)publish到本方法 。一旦出现问题:首先,toast,其次,取消本次异步任务,最后,Dialog dismiss掉。代码如下:
- @Override
- protected void onProgressUpdate(WSError... values) {
- Toast.makeText(mActivity, values[0].getMessage(), Toast.LENGTH_LONG).show();
- this.cancel(true);
- mProgressDialog.dismiss();
- super.onProgressUpdate(values);
- }
2, 实现类SearchDialog(以此为例,说明其实现,只说明最重要的)
a) SearchActvity内容部分的布局,效果图,参看上面彩图。
布局虽是自定义ViewFlipper,我在实际使用中使用原生ViewFlipper也ok,Xml文件如下:
- <com.teleca.jamendo.util.FixedViewFlipper
- android:id="@+id/SearchViewFlipper"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:layout_weight="1"
- android:background="#fff" >
- <ListView
- android:id="@+id/SearchListView"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:divider="#000" />
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:text="@string/no_results" >
- </TextView>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:text="@string/search_list_hint" >
- </TextView>
- </com.teleca.jamendo.util.FixedViewFlipper>
b) 多态的使用
我是在这个类中深深体会多态的妙处。搜索有很多的分类,每一类可能对应不同的Adapter,那么对结果的处理就成了一个问题。在SearchDialog中定义BaseAdapter。对于所有的搜索结果的处理就smooth了。代码如下:
- AlbumAdapter albumAdapter = new AlbumAdapter(SearchActivity.this);
- albumAdapter.setList(albums);
- albumAdapter.setListView(mSearchListView);
- mAdapter = albumAdapter;
c) 错误信息的捕获
Jamendo自己定义了Throwable类WSError,捕获Exception之后(就是主要的网络连接错误:请求异常,连接超时,下载超时等),将错误信息封装之后,向上抛出。分两步处理方式(以AlbumSearch为例):
第一步:catch
第二步:publishProgress
代码如下:
- catch (JSONException e) {
- e.printStackTrace();
- } catch (WSError e) {
- publishProgress(e);
- this.cancel(true);
- }
d) 对结果的处理
多态的使用,使得ListView的数据填充很easy。代码很清晰,不用作解释,代码如下:
- @Override
- public void doStuffWithResult(Integer result) {
- mSearchListView.setAdapter(mAdapter);
- if(mSearchListView.getCount() > 0){
- mViewFlipper.setDisplayedChild(0); // display results
- } else {
- mViewFlipper.setDisplayedChild(1); // display no results message
- }
- // results are albums
- if(mSearchMode.equals(0) || mSearchMode.equals(1) || mSearchMode.equals(3)){
- mSearchListView.setOnItemClickListener(mAlbumClickListener);
- }
- // results are playlists
- if(mSearchMode.equals(2)){
- mSearchListView.setOnItemClickListener(mPlaylistClickListener);
- }
- }
3, 搜索状态的保存
Jamendo对搜索状态的保存,是基于其的搜索页面是Activity之间的跳转。所以有必要保存。不做解释了,代码如下:
- @SuppressWarnings("unchecked")
- @Override
- protected void onRestoreInstanceState(Bundle savedInstanceState) {
- mSearchMode = (SearchMode) savedInstanceState.getSerializable("mode");
- if(mSearchMode != null){
- if(mSearchMode.equals(SearchMode.Artist)
- || mSearchMode.equals(SearchMode.Tag)
- || mSearchMode.equals(SearchMode.UserStarredAlbums)){
- AlbumAdapter adapter = new AlbumAdapter(this);
- adapter.setList((ArrayList<Album>) savedInstanceState.get("values"));
- mSearchListView.setAdapter(adapter);
- mSearchListView.setOnItemClickListener(mAlbumClickListener);
- }
- if(mSearchMode.equals(SearchMode.UserPlaylist)) {
- PlaylistRemoteAdapter adapter = new PlaylistRemoteAdapter(this);
- adapter.setList((ArrayList<PlaylistRemote>) savedInstanceState.get("values"));
- mSearchListView.setAdapter(adapter);
- mSearchListView.setOnItemClickListener(mPlaylistClickListener);
- }
- mViewFlipper.setDisplayedChild(savedInstanceState.getInt("flipper_page"));
- }
- super.onRestoreInstanceState(savedInstanceState);
- }
- @Override
- protected void onSaveInstanceState(Bundle outState) {//保存上次的搜索结果,返回时还在
- if(mSearchMode != null){
- outState.putSerializable("mode", mSearchMode);
- if(mSearchMode.equals(SearchMode.Artist)
- || mSearchMode.equals(SearchMode.Tag)
- || mSearchMode.equals(SearchMode.UserStarredAlbums)){
- AlbumAdapter adapter = (AlbumAdapter)mSearchListView.getAdapter();
- outState.putSerializable("values", adapter.getList());
- }
- if(mSearchMode.equals(SearchMode.UserPlaylist)) {
- PlaylistRemoteAdapter adapter = (PlaylistRemoteAdapter)mSearchListView.getAdapter();
- outState.putSerializable("values", adapter.getList());
- }
- outState.putInt("flipper_page", mViewFlipper.getDisplayedChild());
- }
- super.onSaveInstanceState(outState);
- }
(三) 思考
前面的博文分析提到,Jamendo中有一个RequestCache,缓存10次请求。觉得意义不大,因为点开任意一个页面,滚动一下,很快就超过10个url请求。在Search这里,发现RequestCache的好处。搜索在短时间内,结果基本不会发生差别,所以阻止重复搜索很有必要。在实时性要求特别高的weibo里,RequestCache的使用也不用影响其刷新到最新数据,因为10次很容易超过,之前保留的刷新最新数据的老数据将被remove掉。
注:重构代码时,发现一个奇怪的问题,当前Activity有EditText(即有组件被focus到时),其继承的BaseActivity的无法显示showDialog()所对应的Dialog不会显示,但new出的Dialog可以显示。查看文档后,showDialog()方法不建议使用。文档如下: