1、团队成员html
李怡龙 学号:1600802046 博客地址:https://www.cnblogs.com/lee-li/android
刘显云 学号:1600802048 博客地址:https://www.cnblogs.com/lxy-y/git
刘志祥 学号:1600802049 博客地址:https://www.cnblogs.com/love-love/github
2、APK下载地址redis
Android:https://github.com/leeli73/Windroid/releases/download/1.0/Windroid.apk数据库
PC:https://github.com/leeli73/Windroid_Server_PC/releases/download/1.0/Windroid_PC.exe安全
Server:https://github.com/leeli73/Windroid_Server_PC/releases/download/1.0/Windroid_Server.exe服务器
3、项目地址微信
Android APP:https://github.com/leeli73/Windroid.gitide
Server PC:https://github.com/leeli73/Windroid_Server_PC.git
4、项目介绍
名称:Windroid
功能:主要用于共享Windows系统和Android手机的剪切板,用户不用在经过QQ、微信发信息给PC端,手机复制的信息能够共享给PC,PC复制的信息亦能够共享给手机。
主要构成:Windows端应用程序、Android端程序、Server端程序
4.1 团队项目的整体效果截图
Android登陆界面
Android设置界面
PC登陆界面
PC工做界面
PC端当登陆成功后,便会自动隐藏窗口,在后台运行
Server工做界面
4.2 实现的功能及其效果的描述
登陆
当用户输入用户名密码后,点击登陆,验证经过后便可进入设置页面
点击注册后,便可进行注册
设置信息(目前测试有BUG、在某些状况下会闪退,好比快速上下滑动)
在点击容许修改的表项后,就会弹出下面的输入框
输入完成后,点击肯定便可更新数据
5、项目中的关键代码
HTTP请求
使用OkHttp3库进行请求,主要用于登陆、注册、数据交换
String url = "http://192.168.0.102:6888/SetData"; final OkHttpClient okHttpClient=new OkHttpClient(); RequestBody body = new FormBody.Builder() .add("UserID", Base64.encodeToString(StrUserID.getBytes(), Base64.DEFAULT)) .add("Data",Base64.encodeToString(Data.getBytes(),Base64.DEFAULT)) .build(); final Request request=new Request.Builder().url(url).post(body).build(); new Thread(new Runnable() { @Override public void run() { try { Response response=okHttpClient.newCall(request).execute(); if (response.isSuccessful()){ String body=response.body().string(); Log.i("test",body); } } catch (IOException e) { e.printStackTrace(); } } }).start();
listview生成
每行都有一个指定的view与其对应,方便修改数据等操做
AllInfo = findViewById(R.id.AllInfo); adapter = new BaseAdapter() { @Override public int getCount() { return 13; } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { LinearLayout linearLayout = new LinearLayout(body.this); TextView tital = new TextView(body.this); linearLayout.setOrientation(LinearLayout.VERTICAL); tital.setTextSize(25); switch (position) { case 0: tital.setText("用户信息"); tital.setGravity(LinearLayout.TEXT_ALIGNMENT_CENTER); tital.setTextSize(30); linearLayout.addView(tital); break; case 1: tital.setText("用户名ID"); linearLayout.addView(tital); linearLayout.addView(UserID); break; case 2: tital.setText("用户名"); linearLayout.addView(tital); linearLayout.addView(UserName); break; case 3: tital.setText("电子邮箱"); linearLayout.addView(tital); linearLayout.addView(Email); break; case 4: tital.setText("密码"); linearLayout.addView(tital); linearLayout.addView(PassWord); break; case 5: tital.setText("设置"); tital.setGravity(LinearLayout.TEXT_ALIGNMENT_CENTER); tital.setTextSize(30); linearLayout.addView(tital); break; case 6: tital.setText("最大数据长度/K"); linearLayout.addView(tital); linearLayout.addView(MaxDataLength); break; case 7: tital.setText("远程存储时间/s(<3600s)"); linearLayout.addView(tital); linearLayout.addView(RomoteDataSaveDate); break; case 8: tital.setText("本地存储时间/s(<3600s)"); linearLayout.addView(tital); linearLayout.addView(LocalDataSaveTime); break; case 9: tital.setText("局域网链接"); tital.setGravity(LinearLayout.TEXT_ALIGNMENT_CENTER); tital.setTextSize(30); linearLayout.addView(tital); break; case 10: tital.setText("自动扫描"); linearLayout.addView(tital); linearLayout.addView(LANAutoScan); break; case 11: tital.setText("局域网IP"); linearLayout.addView(tital); linearLayout.addView(LANIP); break; case 12: tital.setText("端口"); linearLayout.addView(tital); linearLayout.addView(LANPort); break; } return linearLayout; } }; AllInfo.setAdapter(adapter);
初始化数据
将asset目录下的配置读取并处理
try { InputStreamReader inputStreamReader = new InputStreamReader(getResources().getAssets().open("UserInfo.data")); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String line=""; while((line=bufferedReader.readLine())!=null) { String Temp[] = line.split(":"); if(Temp[0].equals("Username")) { StrUserName = new String(Temp[1]); } else if(Temp[0].equals("Password")) { StrPassWord = new String(Temp[1]); } } } catch (Exception e) { e.printStackTrace(); } try { InputStreamReader inputStreamReader = new InputStreamReader(getResources().getAssets().open("Setting.data")); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String line=""; while((line=bufferedReader.readLine())!=null) { String Temp[] = line.split(":"); if(Temp[0].equals("MaxDataLength")) { StrMaxDataLength = new String(Temp[1]); } else if(Temp[0].equals("LocalDataSaveTime")) { StrLocalDataSaveTime = new String(Temp[1]); } else if(Temp[0].equals("RemoteDataSaveTime")) { StrRomoteDataSaveDate = new String(Temp[1]); } else if(Temp[0].equals("UserID")) { StrUserID = new String(Temp[1]); } else if(Temp[0].equals("Email")) { StrEmail = new String(Temp[1]); } else if(Temp[0].equals("LANIP")) { StrLANIP = new String(Temp[1]); } else if(Temp[0].equals("LANPort")) { StrLANPort = new String(Temp[1]); } else if(Temp[0].equals("LANAutoScan")) { StrLANAutoScan = new String(Temp[1]); } } } catch (Exception e) { e.printStackTrace(); }
listview点击弹出提示
根据点击位置读取输入和判断是否容许修改
AllInfo.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, final int position, long id) { final EditText MyInput = new EditText(body.this); AlertDialog.Builder builder = new AlertDialog.Builder(body.this); builder.setTitle("请输入信息").setIcon(android.R.drawable.ic_dialog_alert).setView(MyInput).setNegativeButton("取消",null); builder.setPositiveButton("肯定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { switch (position) { case 0: //用户信息 break; case 1: //用户名ID Toast.makeText(body.this,"用户ID不容许修改",Toast.LENGTH_SHORT).show(); break; case 2: //用户名 Toast.makeText(body.this,"用户名不容许修改",Toast.LENGTH_SHORT).show(); break; case 3: //电子邮箱 StrEmail = MyInput.getText().toString(); Email.setText(StrEmail); Toast.makeText(body.this,"修改为功",Toast.LENGTH_SHORT).show(); break; case 4: //密码 StrPassWord = MyInput.getText().toString(); PassWord.setText(StrPassWord); Toast.makeText(body.this,"修改为功",Toast.LENGTH_SHORT).show(); break; case 5: //设置 break; case 6: //最大数据长度 StrMaxDataLength = MyInput.getText().toString(); MaxDataLength.setText(StrMaxDataLength); Toast.makeText(body.this,"修改为功",Toast.LENGTH_SHORT).show(); break; case 7: //远程存储时间/s(<3600s) StrRomoteDataSaveDate = MyInput.getText().toString(); RomoteDataSaveDate.setText(StrRomoteDataSaveDate); Toast.makeText(body.this,"修改为功",Toast.LENGTH_SHORT).show(); break; case 8: //本地存储时间/s(<3600s) StrLocalDataSaveTime = MyInput.getText().toString(); LocalDataSaveTime.setText(StrLocalDataSaveTime); Toast.makeText(body.this,"修改为功",Toast.LENGTH_SHORT).show(); break; case 9: //局域网链接 break; case 10: //自动扫描 StrLANAutoScan = MyInput.getText().toString(); LANAutoScan.setText(StrLANAutoScan); Toast.makeText(body.this,"修改为功",Toast.LENGTH_SHORT).show(); break; case 11: //局域网IP StrLANIP = MyInput.getText().toString(); LANIP.setText(StrLANIP); Toast.makeText(body.this,"修改为功",Toast.LENGTH_SHORT).show(); break; case 12: //端口 StrLANPort = MyInput.getText().toString(); LANPort.setText(StrLANPort); Toast.makeText(body.this,"修改为功",Toast.LENGTH_SHORT).show(); break; } } }); builder.show(); } });
本地剪切板监控线程(存在问题,彻底按照官方API编写的读写剪切板,可是会闪退,API11以前和以后的方法所有尝试,依旧没法解决)
启动一个线程,循环监控本地剪切板
new Thread(new Runnable() { @Override public void run() { try { while (true) { String url = "http://192.168.0.102:6888/GetData"; final OkHttpClient okHttpClient=new OkHttpClient(); RequestBody body = new FormBody.Builder() .add("UserID", Base64.encodeToString(StrUserID.getBytes(), Base64.DEFAULT)) .build(); final Request request=new Request.Builder().url(url).post(body).build(); new Thread(new Runnable() { @Override public void run() { try { Response response=okHttpClient.newCall(request).execute(); if (response.isSuccessful()){ String body=response.body().string(); String Temp[] = body.split("@"); if(Temp[0].equals("New")) { /*//获取剪贴板管理器: ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); // 建立普通字符型ClipData ClipData mClipData = ClipData.newPlainText("Label", Temp[1]); // 将ClipData内容放到系统剪贴板里。 cm.setPrimaryClip(mClipData);*/ Log.i("test","Get New Data "+ Temp[1]); } else { Log.i("test","No New Data"); } } else { Log.i("test","No New Data"); } } catch (IOException e) { e.printStackTrace(); } } }).start(); Thread.sleep(1000); } } catch (Exception e) { e.printStackTrace(); } } }).start();
远程服务器获取数据线程(存在问题,彻底按照官方API编写的读写剪切板,可是会闪退,API11以前和以后的方法所有尝试,依旧没法解决)
启动一个线程,循环监控远程剪切板
new Thread(new Runnable() { @Override public void run() { try { String OldData = ""; while (true) { Log.i("test","Set"); //ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); //String Data = cm.getText().toString().trim(); //Log.i("test",Data); String Data = "123"; if(!Data.equals(OldData)) { String url = "http://192.168.0.102:6888/SetData"; final OkHttpClient okHttpClient=new OkHttpClient(); RequestBody body = new FormBody.Builder() .add("UserID", Base64.encodeToString(StrUserID.getBytes(), Base64.DEFAULT)) .add("Data",Base64.encodeToString(Data.getBytes(),Base64.DEFAULT)) .build(); final Request request=new Request.Builder().url(url).post(body).build(); new Thread(new Runnable() { @Override public void run() { try { Response response=okHttpClient.newCall(request).execute(); if (response.isSuccessful()){ String body=response.body().string(); Log.i("test",body); } } catch (IOException e) { e.printStackTrace(); } } }).start(); } Thread.sleep(1000); } } catch (Exception e) { e.printStackTrace(); } } }).start();
更换返回键的功能为回到桌面
Intent intent = new Intent(); intent.setAction(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_HOME); startActivity(intent); Toast.makeText(body.this,"Windroid进入后台运行",Toast.LENGTH_SHORT).show();
6、心目中的前五名
一、季澈组 http://www.javashuo.com/article/p-myzmpmmy-ka.html
相似网易云音乐界面美观优雅的音乐播放器
项目优势:自动搜集本地音乐,有上一曲,下一曲,开始暂停,顺序播放,随机播放,单首播放,音量的控制,进度条,歌词,能够删除歌曲
项目缺点:没法加载云端的音乐,不能说是一个完美的音乐播放器。没有用户机制,没法保存本身的歌曲列表。
个人设想:支持播放云端的音乐,爬虫现有几个音乐播放器的资源。加入用户机制。
二、贺鸿琨组 http://www.javashuo.com/article/p-ycxizxoy-kg.html
相似QQ音乐界面,选择图片资源很优秀
项目优势:完成了歌曲列表与播放界面之间的切换,完成了播放过程当中图片旋转状态与歌曲播放状态的绑定,还完成了歌曲进度条与歌曲进度的绑定。
项目缺点:没法加载云端的音乐,没有用户机制
个人设想:支持播放云端的音乐,爬虫现有几个音乐播放器的资源
三、李凌龙组 http://www.javashuo.com/article/p-ereceufw-hq.html
对于我这样爱忘事者,这是一个刚需,简单使用
项目优势:功能齐全,界面简单
项目缺点:没有批量删除功能
个人设想:支持语音助手,一句话就能设定好
四、田光欣组 http://www.javashuo.com/article/p-nkglnrye-gy.html
简单使用,没有花里胡哨功能的记事本
项目优势:界面简单,功能齐全
项目缺点:数据存储在本地,更换手机后没法使用
个人设想:支持语音助手,一句话记下文本,支持图片、音频、视频的记录。将数据加密存储在服务器上。
五、李钊组 http://www.javashuo.com/article/p-nxdfkfxp-eq.html
功能齐全的乒乓球社区,乒乓球爱好者的必备
项目优势:功能齐全,不管是视频、排名、照片等都能一次性了解到
项目缺点:没法联网实时获取最新的数据
个人设想:APP自动去互联网上爬去最新的信息,向用户展现最新的数据
7、遇到的问题及解决方案
7.1 读写剪切板(任然未解决)
李怡龙 1600802046
使用最新的API,程序运行至此处会闪退
//获取剪贴板管理器: ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); // 建立普通字符型ClipData ClipData mClipData = ClipData.newPlainText("Label", Temp[1]); // 将ClipData内容放到系统剪贴板里。 cm.setPrimaryClip(mClipData);
换用老版API,程序依旧闪退
cm.setText()
7.2 乱码问题
李怡龙 1600802046
咱们发如今传输中文的过程当中,会出现乱码的问题,由于咱们采用POST的形式,传输数据,若是有&等符号也会出现问题
因此咱们决定对全部通信过程当中的数据进行BASE64编码
Android端
RequestBody body = new FormBody.Builder() .add("UserID", Base64.encodeToString(StrUserID.getBytes(), Base64.DEFAULT)) .build();
PC端
Base64 base64; Username = base64.encode(Username.toLatin1()); Password = base64.encode(Password.toLatin1());
Server端
RealUsernameBase64,err := base64.StdEncoding.DecodeString(Username[0]) if err != nil{ w.Write([]byte("error")) return } RealPasswordBase64,err := base64.StdEncoding.DecodeString(Password[0]) if err != nil{ w.Write([]byte("error")) return } RealUsername := string(RealUsernameBase64) RealPassword := string(RealPasswordBase64)
7.3 读取本地Asset目录下的配置文件
李怡龙 1600802046
由于咱们存储的数据相对较少,并且不敏感,因此采用文本的形式存储
起初咱们准备使用JSON的形式存储,可是在解析的JSON的过程当中有部分问题,最后换用自定义格式的文本
InputStreamReader inputStreamReader = new InputStreamReader(getResources().getAssets().open("UserInfo.data")); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String line=""; while((line=bufferedReader.readLine())!=null) {}
7.4 Server中Redis的使用
李怡龙 1600802046
由于咱们的数据存在必定的时效性,并且要求访问必须作到低延时,因此咱们决定使用Redis 内存K-V型数据库
在查阅文档后,咱们采用了"github.com/garyburd/redigo/redis"包进行redis的各种操做
并创建两张哈希表
第一张为用户信息表
_, err := RedisClient.Do("HMSET",Username,"UserID",UserID,"UserPassword",Password,"Email",Email,"PhoneNumber",PhoneNumber,"SaveTime",SaveTime) if err != nil { fmt.Println("redis hset error:", err) return false } else { //_,err := RedisClient.Do("expire","myKey","10") return true }
第二张为数据表
key :UserID value:Data
_,err1 := RedisClient.Do("HMSET",RealUserID,"Data",RealData) if err1 != nil { fmt.Println("redis hset error:", err) w.Write([]byte("SetError")) } else { //_,err := RedisClient.Do("expire","myKey","10") w.Write([]byte("SetSuccess")) }
7.5 由于技术上的问题,咱们最先想要实现的局域网自动扫描没有编写出来,因此如今全部的功能必须经由服务器
8、分工
姓名 分工 工做比例 分数
李怡龙 服务器、PC端、安卓端POST、剪切板操做 50% 5
刘显云 安卓端登陆UI设计、数据读取、存储 25% 2.5
刘志祥 安卓端listview设计及响应 25% 2.5
9、运行演示
启动Server及PC(Server实际运行于服务器,这里只是用于演示)
Server工做中交换数据的输出(实际工做中不输出,这里只用于演示效果)
Windroid安卓端
不知什么缘由,登陆界面的动画在模拟器中没法显示,因此这里在小米MIX(Android8.0)环境下演示
演示途中的黑屏,是应为调起小米安全键盘时,MIUI系统不容许录制该部分,因此自动进行了遮挡
博客园限制没法上传20M以上的图片,因此这里图片存于公共图床,可能加载相对较慢时点击这个连接前往腾讯云对象存储下载(流量贼啦贵,不到万不得已不要下)