Android第四次作业
一、团队成员
二、团队项目apk
拍卖会apk:APK
三、coding代码
安卓端:安卓端代码
服务器端:服务器端代码
数据库:数据库文件
说明文档:点击这里
四、团队项目介绍
本项目分为服务器端和安卓端,缺一不可。服务器端采用Tomcat7.0.9进行本地服务器搭建,使用MySQL作为数据库存储数据,使用Eclipse和VS Code进行代码编辑,使用谷歌浏览器进行调试。安卓端采用Android Studio开发。
在这里,我们把手机端、平板端和网页端统称为安卓端(因它们具有相同的功能),安卓端主要负责向服务器请求数据和处理服务器相应的json数据,服务器端负责处理安卓端请求以及和数据库交互并把交互数据封装成json数据返回给安卓端。
4.1 总体效果截图
安卓端:
- 目前数据库中已存在内置用户(用户名 | 密码):mysql | mysql 和 tomcat | tomcat。之后截图都是使用tomcat用户登录。
登录界面:
主界面:
各个功能界面:
4.2 实现功能
1.登录:
输入数据库中已经存在的用户“mysql”,密码也是“mysql”,可以进行成功登录:
2.点击“查看竞得物品”,会跳转到相应的界面:
3.点击“浏览流拍物品”,会跳转到相应的界面:
再点击其中存在的物品,可以看到相关信息:
4.点击“管理物品种类”,会跳转到相应的界面:
其中点击右上角的加号按钮可以添加物品的种类,在这里比如添加一个新的种类“TV”:
5.点击“管理物品”,会跳转到相应的界面:
点击其中的正在拍卖的物品,可以查看信息:
点击右上角的加号可以添加拍卖物品,并未拍卖物品定义拍卖信息:
6.点击“浏览拍卖物品”可以看到系统内所有正在拍卖的物品的种类信息:
点击其中任何一项都可以跳转到相应种类的物品清单下,比如点击“电脑硬件”:
点击“显卡”,即可跳转到竞价页面:
在这张页面上,你可以对该商品进行竞价。
7.点击“查看参与的竞标”,会跳转到用户参与竞标的物品清单界面:
点击其中的物品同样会显示物品信息:
五、关键代码
1.处理用户登录:
核心在于登录按钮的点击触发的用户名、密码核对的功能实现,validate()判断用户是否输入用户名和密码,loginPro()执行用户名和密码的核对功能。
整体核心代码:
bnLogin.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
if (validate())
{
if (loginPro())
{
Intent intent = new Intent(Login.this, AuctionClientActivity.class);
startActivity(intent);
finish();
}
else
{
DialogUtil.showDialog(Login.this, "用户名称或者密码错误,请重新输入!", false);
}
}
}
});
validate()函数:
private boolean validate()
{
String username = etName.getText().toString().trim();
if (username.equals(""))
{
DialogUtil.showDialog(this, "用户密码是必填项!", false);
return false;
}
String pwd = etPass.getText().toString().trim();
if (pwd.equals(""))
{
DialogUtil.showDialog(this, "用户名是必填项!", false);
return false;
}
return true;
}
loginPro()函数:
private boolean loginPro()
{
String username = etName.getText().toString();
String pwd = etPass.getText().toString();
JSONObject jsonObj;
try
{
jsonObj = query(username, pwd);
if (jsonObj.getInt("userId") > 0)
{
return true;
}
}
catch (Exception e)
{
DialogUtil.showDialog(this , "服务器响应异常,请稍后再试!", false);
e.printStackTrace();
}
return false;
}
2.查看流拍物品:
由于动态碎片的引入,因此会存在view重复利用的情况,这一功能界面就存在view重复利用,因此判断服务器请求的路径就是判断该界面最终呈现的效果的前提条件,因此在代码中的“action”即为服务器请求路径,若返回路径为“viewFail.jsp”即代表触发的事件为查看流拍物品。函数viewItemDetail实现的功能为点击流拍物品界面上listview后使相应物品的信息以对话框的形式弹出:
public class ViewItemFragment extends Fragment
{
Button bnHome;
ListView succList;
TextView viewTitle;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View rootView = inflater.inflate(R.layout.view_item, container , false);
bnHome = (Button) rootView.findViewById(R.id.bn_home);
succList = (ListView) rootView.findViewById(R.id.succList);
viewTitle = (TextView) rootView.findViewById(R.id.view_titile);
bnHome.setOnClickListener(new HomeListener(getActivity()));
String action = getArguments().getString("action");
String url = HttpUtil.BASE_URL + action;
if (action.equals("viewFail.jsp"))
{
viewTitle.setText(R.string.view_fail);
}
try
{
JSONArray jsonArray = new JSONArray(HttpUtil.getRequest(url));
JSONArrayAdapter adapter = new JSONArrayAdapter(getActivity(), jsonArray, "name", true);
succList.setAdapter(adapter);
}
catch (Exception e)
{
DialogUtil.showDialog(getActivity(), "服务器响应异常,请稍后再试!", false);
e.printStackTrace();
}
succList.setOnItemClickListener(new OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id)
{
viewItemDetail(position);
}
});
return rootView;
}
private void viewItemDetail(int position)
{
View detailView = getActivity().getLayoutInflater().inflate(R.layout.detail, null);
TextView itemName = (TextView) detailView.findViewById(R.id.itemName);
TextView itemKind = (TextView) detailView.findViewById(R.id.itemKind);
TextView maxPrice = (TextView) detailView.findViewById(R.id.maxPrice);
TextView itemRemark = (TextView) detailView.findViewById(R.id.itemRemark);
JSONObject jsonObj = (JSONObject) succList.getAdapter().getItem(position);
try
{
itemName.setText(jsonObj.getString("name"));
itemKind.setText(jsonObj.getString("kind"));
maxPrice.setText(jsonObj.getString("maxPrice"));
itemRemark.setText(jsonObj.getString("desc"));
}
catch (JSONException e)
{
e.printStackTrace();
}
DialogUtil.showDialog(getActivity(), detailView);
}
}
3.管理物品种类:
显示物品种类,和上一功能代码内容类似:
bnHome.setOnClickListener(new HomeListener(getActivity()));
bnAdd.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View source)
{
mCallbacks.onItemSelected(ADD_KIND , null);
}
});
String url = HttpUtil.BASE_URL + "viewKind.jsp";
try
{
final JSONArray jsonArray = new JSONArray(HttpUtil.getRequest(url));
kindList.setAdapter(new KindArrayAdapter(jsonArray, getActivity()));
}
catch (Exception e)
{
DialogUtil.showDialog(getActivity(), "服务器响应异常,请稍后再试!" ,false);
e.printStackTrace();
}
return rootView;
添加物品种类:
其中validate()是判断关于添加的种类的信息用户是否输入,如果输入,addKind函数实现和服务器的交互,将物品信息插入数据库。
bnCancel.setOnClickListener(new HomeListener(getActivity()));
bnAdd.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
if (validate())
{
String name = kindName.getText().toString();
String desc = kindDesc.getText().toString();
try
{
String result = addKind(name, desc);
DialogUtil.showDialog(getActivity(), result , true);
}
catch (Exception e)
{
DialogUtil.showDialog(getActivity(), "服务器响应异常,请稍后再试!" , false);
e.printStackTrace();
}
}
}
});
return rootView;
validate()函数:
private boolean validate()
{
String name = kindName.getText().toString().trim();
if (name.equals(""))
{
DialogUtil.showDialog(getActivity() , "种类名称是必填项!" , false);
return false;
}
return true;
}
addKind函数:
private String addKind(String name, String desc) throws Exception
{
Map<String , String> map = new HashMap<String, String>();
map.put("kindName" , name);
map.put("kindDesc" , desc);
String url = HttpUtil.BASE_URL + "addKind.jsp";
return HttpUtil.postRequest(url , map);
}
4.管理物品:
查看拍卖物品:
bnHome.setOnClickListener(new HomeListener(getActivity()));
bnAdd.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View source)
{
mCallbacks.onItemSelected(ADD_ITEM , null);
}
});
String url = HttpUtil.BASE_URL + "viewOwnerItem.jsp";
try
{
JSONArray jsonArray = new JSONArray(HttpUtil.getRequest(url));
JSONArrayAdapter adapter = new JSONArrayAdapter(getActivity(), jsonArray, "name", true);
itemList.setAdapter(adapter);
}
catch (Exception e)
{
DialogUtil.showDialog(getActivity()
, "服务器响应异常,请稍后再试!", false);
e.printStackTrace();
}
itemList.setOnItemClickListener(new OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id)
{ viewItemInBid(position);
}
});
return rootView;
其中的 mCallbacks.onItemSelected是实现了 定义的Callbacks接口,当Callbacks被调用时就会在相应的activity中实现接口函数onItemSelected,此碎片的activity中的函数实现如下:
public void onItemSelected(Integer id, Bundle bundle)
{
Intent i = new Intent(this , AddItem.class);
startActivity(i);
}
添加拍卖物品,其中的validate()函数仍然是判断用户是否输入信息,addItem是添加信息功能:
String url = HttpUtil.BASE_URL + "viewKind.jsp";
JSONArray jsonArray = null;
try
{
jsonArray = new JSONArray(HttpUtil.getRequest(url));
}
catch (Exception e1)
{
e1.printStackTrace();
}
JSONArrayAdapter adapter = new JSONArrayAdapter(getActivity() , jsonArray , "kindName" , false);
itemKind.setAdapter(adapter);
bnAdd = (Button) rootView.findViewById(R.id.bnAdd);
bnCancel = (Button) rootView.findViewById(R.id.bnCancel);
bnCancel.setOnClickListener(new HomeListener(getActivity()));
bnAdd.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
if (validate())
{
String name = itemName.getText().toString();
String desc = itemDesc.getText().toString();
String remark = itemRemark.getText().toString();
String price = initPrice.getText().toString();
JSONObject kind = (JSONObject) itemKind.getSelectedItem();
int avail = availTime.getSelectedItemPosition();
switch(avail)
{
case 5 :
avail = 7;
break;
case 6 :
avail = 30;
break;
default :
avail += 1;
break;
}
try
{
String result = addItem(name, desc, remark , price , kind.getInt("id") , avail);
DialogUtil.showDialog(getActivity(), result , true);
}
catch (Exception e)
{
DialogUtil.showDialog(getActivity(), "服务器响应异常,请稍后再试!" , false);
e.printStackTrace();
}
}
}
});
return rootView;
5.浏览拍卖物品:
选择物品种类,和上边的有关从数据库查找信息的功能原理相似:
long kindId = getArguments().getLong("kindId");
String url = HttpUtil.BASE_URL + "itemList.jsp?kindId=" + kindId;
try
{
JSONArray jsonArray = new JSONArray(HttpUtil.getRequest(url));
JSONArrayAdapter adapter = new JSONArrayAdapter(getActivity() , jsonArray , "name" , true);
succList.setAdapter(adapter);
}
catch (Exception e1)
{
DialogUtil.showDialog(getActivity() , "服务器响应异常,请稍后再试!" , false);
e1.printStackTrace();
}
viewTitle.setText(R.string.item_list);
succList.setOnItemClickListener(new OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id)
{
JSONObject jsonObj = (JSONObject) succList.getAdapter().getItem(position);
Bundle bundle = new Bundle();
try
{
bundle.putInt("itemId" , jsonObj.getInt("id"));
}
catch (JSONException e)
{
e.printStackTrace();
}
mCallbacks.onItemSelected(ADD_BID, bundle);
}
});
return rootView;
参与竞价:
bidPrice = (EditText)rootView.findViewById(R.id.bidPrice);
bnAdd = (Button)rootView.findViewById(R.id.bnAdd);
bnCancel = (Button)rootView.findViewById(R.id.bnCancel);
bnCancel.setOnClickListener(new HomeListener(getActivity()));
String url = HttpUtil.BASE_URL + "getItem.jsp?itemId="
+ getArguments().getInt("itemId");
try
{
jsonObj = new JSONObject(HttpUtil.getRequest(url));
itemName.setText(jsonObj.getString("name"));
itemDesc.setText(jsonObj.getString("desc"));
itemRemark.setText(jsonObj.getString("remark"));
itemKind.setText(jsonObj.getString("kind"));
initPrice.setText(jsonObj.getString("initPrice"));
maxPrice.setText(jsonObj.getString("maxPrice"));
endTime.setText(jsonObj.getString("endTime"));
}
catch (Exception e1)
{
DialogUtil.showDialog(getActivity(), "服务器响应出现异常!", false);
e1.printStackTrace();
}
bnAdd.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
try
{
double curPrice = Double.parseDouble(bidPrice.getText().toString());
if( curPrice < jsonObj.getDouble("maxPrice"))
{
DialogUtil.showDialog(getActivity(), "您输入的竞价必须高于当前竞价", false);
}
else
{
String result = addBid(jsonObj.getString("id"), curPrice + "");
DialogUtil.showDialog(getActivity(), result , true);
}
}
catch(NumberFormatException ne)
{
DialogUtil.showDialog(getActivity(), "您输入的竞价必须是数值", false);
}
catch(Exception e)
{
e.printStackTrace();
DialogUtil.showDialog(getActivity(), "服务器响应出现异常,请重试!", false);
}
}
});
return rootView;
其中的addBid函数实现了讲竞价插入数据库的功能:
private String addBid(String itemId , String bidPrice) throws Exception
{
Map<String , String> map = new HashMap<String, String>();
map.put("itemId" , itemId);
map.put("bidPrice" , bidPrice);
String url = HttpUtil.BASE_URL + "addBid.jsp";
return HttpUtil.postRequest(url , map);
}
六、团队排名
洪居兴组
一句话描述:这个组做的是一个旅游类app,实现了景区、酒店、美食、路线等旅游必备物品外,还实现了登录注册时向手机发送验证功能。
优点:最令我佩服得地方有,注册用户手机验证码功能,底部导航做的也比较好,个人信息查看和修改以及支持评论等功能;前端界面也相对完善。相对于用户来说,这些功能都是旅游需要用到的,具有很高的实用性。
缺点:缺点在于没有特色功能,目前该软件所实现的功能与市场上已存在旅游软件相比,没有特色的主打功能。如果真的发布,可能难以在市场上存活。
如果我来做:一是加入更多功能,二是,如果真要向市场发展,前期要有一定促销策略。
沈顺文组
一句话描述:这个组做的是一个为专业人士或编程兴趣爱好者开发的一个代码学习的软件,其中涵盖多种代码语言,并且有很多和编程相关的功能实现。
优点:在“代码闯关”这一功能上,代码语言的覆盖面很广,种类很多;个人简介的界面设计的比较好,贴近现实的使用习惯;还有一个亮点功能就是添加好友,可以实现通过多种第三方软件来添加。
缺点:查询功能只能查询软件自带的内容,比较局限,其次运行会有闪退现象。
如果我来做:会更加优化部分界面,使得整体界面设计更加协调,比如代码交流界面。
李钊组
一句话描述:这个组做的是一个乒乓球爱好者交流app,该软件不仅可以查看最新乒乓球类信息,还实现了世界排名,球员,视频等等。
优点:站在用户角度来说,如果该软件数据信息没有问题,确实是一个了解乒乓球赛事的比较好的app。除了查看运动员信息外,还支持比如赛事赛程等功能也做的比较有吸引力。
缺点:这个软件还可以做的更好,实现登录,评论功能等等。该软件UI也可以进一步升级改造。
如果我来做:第一步优化UI,第二步,加入一些新奇功能。
汤文涛组
一句话描述:这个组做的是一个二手交易的app,可以实现查看二手交易物品的信息,通过留言来使得出手者和购买者进行沟通。
优点:成品软件实现了登录,查看出售物品,留言,评论,查看个人信息并修改密码的功能,功能都可以较好地实现。修改密码是一个较好的功能,在实际应用中更有实用性。
缺点:此软件为二手交易软件,应该要有明确的设计目的,就是实现交易,但是此组设计的交易软件是通过留言来使得买家和卖家联系的,这样就大大降低了软件设计出来的使用度,从长远来看,实际应用性不强。
如果我来做:我们会增加一些使得买家和卖家直接交易通话的功能,不会设计成留言的形式,并且还会增加交易的种类。
贺鸿琨组
一句话描述:这个组做的是一个音乐播放器app,除了实现上一首下一首播放暂停等基本功能以外,还实现了歌曲进度条和图片的旋转。
优点:功能相对齐全,界面也比较美观。实现了歌曲和歌曲进度条的绑定,不仅支持实时查看歌曲进度,而且还支持通过进度条调整音乐,站在用户的角度来看,这是一个比较实用的功能。
缺点:站在用户的角度来看,对于音乐最不能缺少的就是“歌词”,和现有的酷狗音乐或qq音乐软件相比,功能相对局限。
如果我来做:在时间充裕的情况下,会更加着重于更加功能的实现。例如:歌词功能,歌单功能,用户登录头像等等。
以上排名不分先后。
七、开发难点及解决方法
刘宇莹:
问题1——SQL语句执行不起效果
问题描述: 使用服务器和数据库进行通信时,理论上来讲,采用insert, delete, update语句执行,但是,在本次作业中,多次调试一直没有报错,没有结果。
解决方法: 在查阅资料后,尝试通过采用把SQL语句换成HQL语句执行,问题终于得到解决。有关HQL语句和数据库交互代码,参考这篇:HQL操作数据库。
比如,采用HQL语句修改之后代码如下:(这段代码功能:查找kindId这一物品下的所有已拍卖物品)
public List<Item> findItemByKind(Integer kindId)
{
return find("from Item as i where i.kind.id=?0 and i.itemState.id=1", kindId);
}
问题2——乱码问题
问题描述: 确保数据库中存储中文,服务器响应设置编码gbk,安卓端代码设置编码gbk,代码中中文适配没问题,服务器返回json数据格式中文也ok,但响应就是乱码。模拟器界面截图如下:
解决方法: 在重新确保数据库中文正常,服务器响应的json数据正常后,还是没有进展。怀疑是ASjson数据解码有问题或是本身编码有问题。于是,采用将AS中编码有原来的UTF-8改为GBK,还是不行。再改,将改成GBK的代码在改回UTF-8。终于,搞定。解决后界面请往上翻!
孟鑫菲:
问题1
在AuctionClientActivity,一个关于主界面的Activity的程序代码中,有关于将Frangment替换然后重新加载的语句“replace(R.id.auction_detail_container, fragment)”,关于replace的使用一定要先将替换的程序放入返回栈,即“addToBackStack(null)”,然后一定要加“commit()”来重新提交和执行。单独使用replace会得不到替换的效果。
问题2
由于在程序设计时会有很多的Fragment的编写以及显示,如果每写一次不同界面下的Fragment,然后使之显示,会造成很多不必要的程序重复,因此就用了FragmentActivity来实现Fragment的显示功能,其中设置了显示Fragment的抽象函数,只要继承此类,即可重新编写抽象函数的具体实现代码,从而得到不同的显示效果,也得到了程序上的统一。
八、分工明细
姓名 | 分工 | 分工比例 | 分数 |
---|---|---|---|
刘宇莹 | 服务器端设计、安卓端设计、报告撰写 | 50% | 10分 |
孟鑫菲 | 服务器端设计、安卓端设计、博客撰写 | 50% | 10分 |