最近做一个类似QQ空间的发布状态并且可以让用户进行评论和回复的一个玩意儿。
1,功能如下:
(1)登录用户发布状态
(2)可以查看自己发布过的所有状态;
(3)可以查看登录用户的好友动态,即所有好友的所有状态列表;
(4)可以查看每条状态(包括登录用户和好友的)下对应的评论和回复;
(5)状态列表的显示按时间顺序由新到旧显示,回复和评论按时间顺序由旧到新显示;
(6)评论和回复以树的形式用列表显示,相当于论坛树;
(7)点击每条评论或者回复,可以让用户选择对其进行回复或者进入发布者的主页
2,具体业务流程,可以看我前面写的一篇博客,android平台——移动SNS(一)。
3,这里必须要提到的是一个界面的展示问题,通常我们用listview来显示列表,起初我是用activity继承ListActivity,但这样扩展性比较差,所以采用了如下方式,下面看看源代码。
msg_list.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/default_bg"
android:padding="5dip"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="我 的 状 态 "
android:textColor="#aa0000"
android:id="@+id/who_state"
/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/msgContent"
android:layout_weight="11"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<Button
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/msgSend_btn"
android:text="发表"
/>
</LinearLayout>
<ListView
android:id="@+id/listv"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
msglist_data.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/msg_user_icon"
android:layout_height="48dip"
android:layout_width="48dip"
android:layout_marginRight="5dip"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/msg_username"
android:textColor="#ffffd000"
android:textStyle="bold"
android:layout_weight="1.0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<TextView
android:id="@+id/msg_update_body"
android:textColor="@android:color/white"
android:autoLink="all"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/msg_timestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.msg_list);
//初始化组件
lv = (ListView)findViewById(R.id.listv);
//首次请求服务器的url地址
String path = HttpUtils.BASE_URL+"servlet/GetNewsServlet?uid="+uid+"&biz=getMsgs";
loadView(path); //加载界面,将服务器获取数据填充
}
// android delete avd -n tl3shi android create avd -n tl3shi -t 2 emulator -avd tl3shi
public void loadView(String path){
final String url = path;
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
//异步方式获取服务器数据
data = getData(url);
//开启UI线程,用于更新UI
MsgListActivity.this.runOnUiThread(new Runnable()
{
@Override
public void run()
{
// TODO Auto-generated method stub
SimpleAdapter adapter = new SimpleAdapter(MsgListActivity.this,data,R.layout.msglist_data,
new String[]{"title","info","time","img"},
new int[]{R.id.msg_username,R.id.msg_update_body,R.id.msg_timestamp,R.id.msg_user_icon});
lv.setAdapter(adapter);
}
});
}
}).start();
}
private List<Map<String, Object>> getData() {
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>() ;
String pathName = null;
try {
pathName = HttpUtils.downloadFile("http://192.168.1.164:8082/xiaomii_android_server/photo.png");
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Map<String, Object> map = new HashMap<String, Object>();
map.put("title", "G1");
map.put("info", "google 1");
map.put("time", "2011-2-22 11:11");
map.put("img", R.drawable.icon);
list.add(map);
map = new HashMap<String, Object>();
map.put("title", "G2");
map.put("info", "google 2");
map.put("time", "2011-2-22 11:11");
// map.put("img", R.drawable.icon);
map.put("img", pathName);
list.add(map);
map = new HashMap<String, Object>();
map.put("title", "G3");
map.put("info", "google 3");
map.put("time", "2011-2-22 11:11");
map.put("img", "/sdcard/xiaomii_android_client/notify/photo.png");
// try {
// msg_user_icon.setImageBitmap(BitmapFactory.decodeFile(pathName));
// } catch (Exception e) {
// TODO Auto-generated catch block
// e.printStackTrace();
// }//, "/xiaomii_android_client/notify/");
list.add(map);
return list;
}
4,数据库(MySQL)
CREATE TABLE msg (
mid int(11) AUTO_INCREMENT NOT NULL ,
uid int(11) NOT NULL ,
name varchar(30) NOT NULL,
facePic varchar(30) NOT NULL default '',
motion varchar(30) NOT NULL default '',
time datetime NOT NULL ,
tomid int(11) NOT NULL ,
touid int(11) NOT NULL ,
toname varchar(20) default NULL,
action varchar(30) default NULL,
sub varchar(30) default NULL,
par varchar(30) default NULL,
vis varchar(30) default NULL,
content Text default NULL,
PRIMARY KEY (mid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
5,需要解决的相关问题,如果各位有好的解决办法,可以指点一二:
(1),将消息内容按照时间顺序显示,进一步,则初步显示指定条数,用户通过增加滚动事件或者按钮事件来读取更 多条数的 消息内容
按时间顺序显示好说,order by time即可,滚动事件则需要写数据的分页,而且还要考虑性能
(2),用户头像显示问题(获取到头像后如何处理,是否缓存到客户端,又如何进行图片缓存)
这里我用了一个文件下载类,根据数据库存的服务器头像路径,将头像下载到SD卡中,为了使得用户具有更好的用户体验,存在SD卡上的图片应将其后缀去掉,显示头像时也无需将后缀加上(android是在linux系统基础上的,不以后缀来识别文件名),只需写对SD卡上文件路径即可
文件下载类中,当SD卡上有同名文件存在时,则无需从服务器中下载,这样性能更好些。
(3),显示消息的回复条数问题
<1>看到网上如下内容,决定用SELECT COUNT(*):
SELECT COUNT(*)通常是对主键进行索引扫描, 而SELECT COUNT(COL)就不一定, 如果在COL上有索引,那么效率和COUNT(*)应该差不多, 如果没有那就比较讨厌, 要进行全表扫描, 效率肯定差.
SELECT COUNT(*)和SELECT COUNT(COL)是不同的概念, 前者是统计表中的所有纪录总数, 而后者是计算表中COL<>NULL的纪录数. 所以得到的结果可能是不一样的. 具体用那个,还要看你的逻辑要求.
<2>从服务器请求状态列表时,则同时返回状态列表的每个状态的评论条数,可以将状态列表和评论条数都放在一个数据结构中,转成xml字符串,以逗号分割,返回到客户端,再将其取出,并解析xml.
(4),异步显示消息内容
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
//异步方式获取服务器数据
data = getData(url);
//开启UI线程,用于更新UI
MsgListActivity.this.runOnUiThread(new Runnable()
{
@Override
public void run()
{
// TODO Auto-generated method stub
SimpleAdapter adapter = new SimpleAdapter(MsgListActivity.this,data,R.layout.msglist_data,
new String[]{"title","info","time","img"},
new int[]{R.id.msg_username,R.id.msg_update_body,R.id.msg_timestamp,R.id.msg_user_icon});
lv.setAdapter(adapter);
}
});
}
}).start();
(5),只能评论无法回复的问题
Listview单击列表项弹出回复菜单即可,再次弹出一个编辑对话框,如下:
editmsg_dialog.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="250px"
android:layout_height="250px"
>
<EditText
android:id="@+id/msgedit_Content"
android:layout_weight="2"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
<Button
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:id="@+id/msgeditBtn"
android:text="回复"
/>
</LinearLayout>
final Dialog editdialog = new Dialog(MsgDetailActivity.this);
editdialog.setContentView(R.layout.editmsg_dialog);
Button msgeditBtn = (Button)editdialog.findViewById(R.id.msgeditBtn);
final EditText msgedit_Content = (EditText)editdialog.findViewById(R.id.msgedit_Content);
editdialog.setTitle("编辑回复内容");
editdialog.show();
(6),如何进一步减少与服务器端的交互次数,减少数据的读取时间,使得客户端性能达到最优化
(7)经常要实时更新数据