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以上的圖片,因此這裏圖片存於公共圖牀,可能加載相對較慢時點擊這個連接前往騰訊雲對象存儲下載(流量賊啦貴,不到萬不得已不要下)