您的位置:首页 > 其它

Jamendo学习笔记之七:搜索页面实现及数据加载

2012-06-02 11:27 579 查看
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()方法不建议使用。文档如下:
本文出自 “小新专栏” 博客,请务必保留此出处http://mikewang.blog.51cto.com/3826268/885431
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: