还记得第一次实现安卓与数据库的交互是做的登录功能,当时因为PHP好上手而且环境也很好搭,所以后台就用了PHP+MySql,回想已经过了一两年了,当时实现了与服务端数据库的交互的确对自己来说是一个很不错的进步,但直到现在做的东西还是建立在当时的那种交互方式,这就说明了之后本人在安卓与服务端交互这块的学习非常匮乏。此篇是关于课程查询的实现,当然还是用的Android+PHP+MySql来实现。
做这个的目的是为了帮助同学完成他们的近期要结题的一个科创项目,正好借此复习之前的一些的知识,以下是他们要实现的功能(时间非常紧急各种投机取巧哈哈):
1.通过课程名或课程号查询课程信息(课程代码,授课老师,上课地点时间,课程介绍)
2.通过课程名查看同上这节课的学生信息(学号,姓名,班级)
3.在 1 的基础上点击教师名称可以查看ta的评价(简单实现只有一条评价)
4.在 2 的基础上可以实现一种简单的交流比如留言发消息这样的(本来想做即时通讯但难度挺大的所以改用了 5 的方式)
5.实现了一个简单的论坛,发表内容并可以@某一楼的人(该功能的实现也是“巧妙”)
6.要有该app发出的通知栏(这个功能原本是要实时监测到课程信息发生变化后通知),点击通知栏会显示全部课程。
以上是全部要实现的功能,下面就开启过程:
本质来说,这些功能其实就是对数据库中数据的操作,数据库设计不是很规范但都为了功能设计:
1.数据库中第一张表是微论坛数据表,之后的发表内容其实就是往这张表中插入数据,fromwho表示是谁发的,towho就表示这个内容是@了谁,time是发送时间,mseg则是内容
2.第二张表是课程表,查询课程信息 是通过id(课程号)和cname(课程名),tname是授课老师,time是上课时间,site是上课地点
3.第三张表是学生表,这张表主要存储学生的信息,这里简单起见就只有学生编号(id)、姓名(name)、班级(grade)
4.第四张表是学生选课表,就是哪个学生(sname)选了哪个课程(cname)这张表可以和上一张表联系起来,这样就可以在知道课程名的情况下查询到选这门课程的学生信息。
5.最后一张表就是教师评价表,这里只放了每个老师(tname)所对应的一条综合评价(comment)
后台是利用php和mysql交互,php一句代码就可以直接连接数据库(要不说php上手快),再根据安卓端发来的请求(参数),对数据库进行相应的操作,查询的数据输出json格式给安卓端解析(如图)。
上代码:
1.得到请求的参数(比如搜索的关键词)
$req_type=$_REQUEST['sel_type'];
$req_key=$_REQUEST['sel_key'];
2.连接数据库
$connection =new mysqli("localhost","root","密码","数据库名");
$connection->query("SET NAMES UTF8");
3.查询课程信息
function lessonSel($sql) {
$result=$connection->query($sql);
if($result===FALSE) echo 'fail';
else{
$row=$result->fetch_assoc();
echo json_encode(array("cname"=>$row['cname'],"tname"=>$row['tname'],"site"=>$row['site'],"des"=>$row['desc'],"time"=>$row['time'],"id"=>$row['id']));//编码成json并输出
}
}
(1)lessonSel("select * from lesson where cname like '{$req_key}%' or id='{$req_key}' or tname='{$req_key}');
//like匹配关键词或者通过课程号查询课程信息
(2)lessonSel("select * from lesson");
//查询所有课程信息
4.查询同课的学生信息
function stuSel(){
$sql="select * from student where name in (select sname from stusel where cname like '{$req_key}%' or cname in (select cname from lesson where id='{$req_key}'))";
//利用嵌套查询(结合学生表和学生选课表)通过课程号或课程名的匹配来查询到同课同学
$result=$connection->query($sql);
if($result===FALSE) echo 'fail';
else{
$i=0;
while($row=$result->fetch_assoc()){//关联数组
$arr[$i++]=array("id"=>$row['id'],"name"=>$row['name'],"grade"=>$row['grade']);
}
$arr[$i]=array("id"=>"当前搜索","name"=>"key:$req_key","grade"=>"$i 个同课同学");
echo json_encode($arr);//数组转换为JSON数组字符串
}
}
5.查询老师的评价
function teaCom(){
$sql="select * from teacher where tname='{$req_key}'";
$result=$connection->query($sql);
if($result===FALSE) echo 'fail';//查询失败则输出fail
else{
$row=$result->fetch_assoc();
echo $row['comment'];
}
}
6.论坛数据操作
(1)查询数据
function forumSel(){
$sql="select * from forum";
$result=$connection->query($sql);
if($result===FALSE) echo 'fail';
else{
$i=0;
while($row=$result->fetch_assoc()){
$arr[$i++]=array("id"=>$row['id'],"from"=>$row['fromWho'],"to"=>$row['toWho'],"time"=>$row['time'],"msg"=>$row['mseg']);
}
echo json_encode($arr);
}
}
(2)插入数据
function forumIns(){
$sql="insert into forum(fromWho,toWho,time,mseg) values('{$_REQUEST['name']}','{$_REQUEST['toWho']}',now(),'{$_REQUEST['msg']}')";
$result=$connection->query($sql);
f($result===FALSE) echo 'fail';
else echo 'success';
}
7.关闭连接
$connection->close();
整个app包含初始化页面,主页面,微论坛,选课前查询,选课后查询,所有课程
首先是ui的设计,整体是一个listview,每个item中有发送者,发送内容,接收者,时间及楼号(对应数据库forum中的内容)。
1.首先是写数据model,各种set和get(bean类)
public class Messg {
String mesg,from,to,time,floorId;
public Messg(String msg, String from, String to, String time, String floorId){
mesg=msg;
this.from=from;
this.to=to;
this.time=time;
this.floorId=floorId;
}
/*public void setTypeCode(int code){
this.code=code;
}
public int getTypeCode(){
return code;
}*/
public void setMsg(String mes){
mesg=mes;
}
public String getMsg(){
return mesg;
}
public void setFrom(String from){
this.from=from;
}
public String getFrom(){
return from;
}
public void setTo(String to){
this.to=to;
}
public String getTo(){
return to;
}
public void seTime(String time){
this.time=time;
}
public String getTime(){
return time;
}
public void setFloorId(String floorId){
this.floorId=floorId;
}
public String getFloorId(){
return floorId;
}
}
2.再去创建listview的adapter,主要就是继承BaseAdapter重写它的几个方法,将上面的model利用泛型限制为arraylist对象的类型,用viewholder类来与item布局形成控件的映射,再用convertview的setTag()和getTag()处理item缓存。
public class MesgAdapter extends BaseAdapter{
private Context mcontext;
private ArrayList arrayList;
private LayoutInflater inflater;
public MesgAdapter(Context context, ArrayList messageArrayList){
mcontext=context;
arrayList= messageArrayList;
inflater=LayoutInflater.from(context);
}
@Override
public int getCount() {
return arrayList.size();
}
@Override
public Object getItem(int position) {
return arrayList.get(position);
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Holder holder=null;
if (convertView==null){
convertView=inflater.inflate(R.layout.msg_item,null);
holder=new Holder();
holder.msg=convertView.findViewById(R.id.textMsg);
holder.time=convertView.findViewById(R.id.textTime);
holder.from=convertView.findViewById(R.id.textFrom);
holder.to=convertView.findViewById(R.id.textTo);
holder.floor=convertView.findViewById(R.id.textFloor);
convertView.setTag(holder);
}else {
holder= (Holder) convertView.getTag();
}
Messg messg=arrayList.get(position);
holder.msg.setText(messg.getMsg());
holder.floor.setText(messg.getFloorId()+"楼");
holder.from.setText(messg.getFrom());
holder.to.setText("@"+messg.getTo());
holder.time.setText(messg.getTime());
return convertView;
}
private class Holder{
TextView msg,time,floor,from,to;
}
}
3.然后就是添加数据(可以手动添加或从网络请求数据)创建listview设置它的adapter
private void createInfoList(){
arrayList=new ArrayList<>();
//自己添加固定的测试数据
arrayList.add(new Messg("现在网络好像有点问题呢","匿名用户","@未知用户","2018-05-08 23:23:12","1"));
arrayList.add(new Messg("是啊,垃圾学校网","匿名用户","@匿名用户","2018-05-08 24:21:10","2"));
adapter=new MesgAdapter(this,arrayList);
listView.setAdapter(adapter);
//这句是网络请求后更新arraylist进而更新listview
SeltActivity.getJSON(SeltActivity.base_url+"msgSel",mhandler,"nullStr");
}
先是发起http请求,请求到(json)数据再从handler里面解析,然后更新arraylist
具体的 网络请求和handler的处理如下:
public static void getJSON(final String url, final Handler handler, final String opStr){
new Thread(new Runnable() {
@Override
public void run() {
try {
HttpURLConnection urlConnection= (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setDoOutput(true);
urlConnection.setDoInput(true);
nputStream is=urlConnection.getInputStream();
if (urlConnection.getResponseCode()==200){
Log.e("网络连接:"+url,"成功");
}
if (opStr!=null){
BufferedReader reader=new BufferedReader(new InputStreamReader(is));
String rline="";
StringBuilder builder_result=new StringBuilder();
while ((rline=reader.readLine())!=null){
builder_result.append(rline);
// Log.e("读取到数据",rline);
}
Message message=new Message();
message.obj=builder_result.toString();
handler.sendMessage(message);
}
//is.close();
//urlConnection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
Handler mhandler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what==0) {
progressBar.setVisibility(View.VISIBLE);
String json_data = null;
son_data = msg.obj.toString();
Log.e("成功读取json数据", json_data);
arrayList.clear();
try {
JSONArray jsonArray = new JSONArray(json_data);
Log.e("解析json数据", json_data);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject object = jsonArray.getJSONObject(i);
String fromWho = object.getString("from");
String toWho = object.getString("to");
String mesg = object.getString("msg");
String time = object.getString("time");
String floorId = object.getString("id");
arrayList.add(new Messg(mesg, fromWho, toWho, time, floorId));
}
adapter.notifyDataSetChanged();
Toast.makeText(ForumActivity.this,"内容已更新",Toast.LENGTH_SHORT).show();
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(ForumActivity.this, "解析网络数据异常", Toast.LENGTH_SHORT).show();
}
progressBar.setVisibility(View.GONE);
}
}
};
可以看到下图成功显示了mysql中forum表的内容
然后是几个主要的监听事件事件,像点击按钮发出消息插入数据到数据库并更新listview,listview长按可以在edittext中的hint显示 @选中item的发送者名字,双击toolbar可以更新listview内容。
下面贴出代码
1.listview长按事件
private int curTagPos=-1;//这个变量用来记录item的postion
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
String name=arrayList.get(position).getFrom();
text.setHint("@"+name);
curTagPos=position;
return false;
}
});
2.sendButton监听事件
sendBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String myword=text.getText().toString();
if (myword!=""&& !TextUtils.isEmpty(myword)){
//在第一次打开app时会要求写入自己的名字并保存在SharedPreferences中
preferences=getSharedPreferences("username",MODE_PRIVATE);
name=preferences.getString("name","");
//发起网络请求,插入数据
SeltActivity.getJSON(SeltActivity.base_url+"msgInsert&name="+name+"&msg="+myword+"&toWho="+(curTagPos==-1?name:arrayList.get(curTagPos).getFrom()),mhandler,null);
text.setHint("请输入你要发表的内容");
text.setText("");
//重新获取数据并更新listview
SeltActivity.getJSON(SeltActivity.base_url+"msgSel",mhandler,"nullStr");
Toast.makeText(ForumActivity.this,"发表成功",Toast.LENGTH_SHORT).show();
hidekeyboard(ForumActivity.this);//收起键盘的方法
}
else {
text.setHint("内容不能为空!");
}
}
});
public static void hidekeyboard(Activity activity){
InputMethodManager inputMethodManager= (InputMethodManager) activity.getSystemService(INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromInputMethod(activity.getWindow().getDecorView().getWindowToken(),0);
}
3.toolbar双击事件
private final int DOUBLE_TAP_TIMEOUT = 200;
private MotionEvent mCurrentDownEvent;
private MotionEvent mPreviousUpEvent;
//重写OnTouchListener,判断两次按下手指和抬起手指的时间来判断是否doubleclick
toolbar.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (mPreviousUpEvent != null && mCurrentDownEvent != null && isConsideredDoubleTap(mPreviousUpEvent, event)) {
progressBar.setVisibility(View.VISIBLE);
SeltActivity.getJSON(SeltActivity.base_url+"msgSel",mhandler,"nullStr");
}
mCurrentDownEvent = MotionEvent.obtain(event);
} else if (event.getAction() == MotionEvent.ACTION_UP) {
mPreviousUpEvent = MotionEvent.obtain(event);
}
return true;
}
});
private boolean isConsideredDoubleTap(MotionEvent firstUp, MotionEvent secondDown) {
if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT) {
return false;
}
int deltaX = (int) firstUp.getX() - (int) secondDown.getX();
int deltaY = (int) firstUp.getY() - (int) secondDown.getY();
return deltaX * deltaX + deltaY * deltaY < 10000;
}
界面是在toobar里面加了一个searchview,将搜索的关键词通过get请求的url传到php中处理,php再从mysql中获得所需数据返回到android端进行处理。对于网络数据(json)的处理和上面是一样的方式所以不再重述,下面按功能是:搜索框的监听事件,listview单击后给某同课同学留言(选课后),点击教师名会显示评价对话框(选课前)
1.首先是searchview的查询事件
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
if (query.length()>0) {//因为默认会先提交一次query,所以要判断query不为空
if (strMode.equals("after")) {//选课前
getJSON(base_url + "stuSel&sel_key=" + query, mhandler, "stulist");
}else if(strMode.equals("before")) {//选课后
getJSON(base_url + "lessonSel&sel_key=" + query, mhandler, "lesinfo");
}
searchView.setIconified(true);
showProDia("搜索中", isFinishing(),SeltActivity.this);
}
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
return false;
}
});
如下图是选课后和选课前的查询结果页面:
2.然后是listview的item单击事件
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
final String name=list.get(position).getName();
LinearLayout layout= (LinearLayout) getLayoutInflater().inflate(R.layout.chatdia,null);
final AlertDialog dialog=new AlertDialog.Builder(SeltActivity.this).setTitle("MSG To "+name).setIcon(R.drawable.chaticon).setView(layout).create();
dialog.show();//弹出给同课同学留言的对话框
Button sendBtn=layout.findViewById(R.id.send_button);
final EditText contentMsg=layout.findViewById(R.id.msgEdit);
sendBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
preferences=getSharedPreferences("username",MODE_PRIVATE);
basename=preferences.getString("name","");
getJSON(base_url+"msgInsert&name="+basename+"&msg="+contentMsg.getText().toString()+"&toWho="+name,null,null);
//消息数据通过网络请求传到数据库
dialog.dismiss();//发送成功后销毁对话框
Toast.makeText(SeltActivity.this,"已发送",Toast.LENGTH_SHORT).show();
ForumActivity.hidekeyboard(SeltActivity.this);//收起键盘
}
});
}
});
3.最后是tname(TextView)的点击事件
//设置监听事件,发起网络请求
textTname.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getJSON(base_url+"teacherCom&sel_key="+textTname.getText().toString(), mhandler,"techcom");
}
});
//在handler里面得到数据之后,弹出对话框
if (!TextUtils.isEmpty(json_data))
commentStr=json_data;
new AlertDialog.Builder(SeltActivity.this).setTitle("评价").setMessage(commentStr).show();
1.创建和初始化通知栏(包含点击通知跳转到全部课程界面以及解决安卓8.0以后无法通知的问题)
public void initNotif(){
Intent intent = new Intent(context, SelallActivity.class);
NotificationManager manager = (NotificationManager)context.getSystemService(NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){//安卓8.0 以后需要加上channelId 才能正常显示
String channelId = "default";
String channelName = "默认通知";
manager.createNotificationChannel(new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH));
}
//设置TaskStackBuilder,作用是打开跳转页面点击返回时回到指定页面
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addParentStack(SelallActivity.class);//点击通知栏可以跳转到全部课程页面
stackBuilder.addNextIntent(intent);
PendingIntent pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new NotificationCompat.Builder(context, "default").
setSmallIcon(R.drawable.icon).setContentTitle("课程通知").setContentText("点击即可查看所有课程信息")
.setAutoCancel(true).setDefaults(Notification.DEFAULT_ALL).setWhen(System.currentTimeMillis()).setContentIntent(pendingIntent) .build();
manager.notify(1, notification);
}
2.创建定时器timer并设置一个(每5分钟通知一次)的计划任务timertask,注意最后要关闭任务否则会引起内存泄漏
public void startNotifc(){
timer=new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
if (preferences.getString("isNoti","").equals("loopNotif"))
initNotif();
}
},1000,30*1000);
}
public void stopNotifc(){
timer.cancel();
}//终止任务
下图是显示的通知栏(本人手机是安卓9.0.1),以及点击通知栏后跳转的全部课程页面:
所有课程中用的是gridview(网格布局),实现上和listview差不多,在xml布局文件中的定义如下
android:horizontalSpacing="1.5dp"
android:numColumns="2">
之后就和listview一样,给它设置好adapter,加载网络数据和上面的listview也是相同的形式。
附上安卓端源码
这样所有的功能就差不多全部总结完了,归根结底还是安卓端通过PHP和数据库的交互获取和操作数据库的数据,几乎每块功能都会用到这样的一种操作方式。虽说功能是大体实现,但都用了速成的方法,就是为了完成而完成,这就导致了这个项目没有技术上的创新同时也反映了个人学习方法的有待优化以及当前水平的急需提升。还有最主要的就是总结过后发现该app的核心功能不明确,所以这次的项目就当作一次加深理解以往知识的复习。