zoukankan      html  css  js  c++  java
  • [Android]第四次作业

    一、团队成员

    李怡龙 学号:1600802046 博客地址:https://www.cnblogs.com/lee-li/

    刘显云 学号:1600802048 博客地址:https://www.cnblogs.com/lxy-y/

    刘志祥 学号:1600802049 博客地址:https://www.cnblogs.com/love-love/

    二、APK下载地址

    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

    三、项目地址

    Android APP:https://github.com/leeli73/Windroid.git

    Server PC:https://github.com/leeli73/Windroid_Server_PC.git

    四、项目介绍

    名称:Windroid

    功能:主要用于共享Windows系统和Android手机的剪切板,用户不用在通过QQ、微信发信息给PC端,手机复制的信息可以共享给PC,PC复制的信息亦可以共享给手机。

    主要构成:Windows端应用程序、Android端程序、Server端程序

    4.1 团队项目的总体效果截图

    Android登录界面

    Android设置界面

    PC登录界面

    PC工作界面

    PC端当登录成功后,便会自动隐藏窗口,在后台运行

    Server工作界面

    4.2 实现的功能及其效果的描述

    登录

    当用户输入用户名密码后,点击登录,验证通过后即可进入设置页面

    点击注册后,即可进行注册

    设置信息(目前测试有BUG、在某些情况下会闪退,比如快速上下滑动)

    在点击允许修改的表项后,就会弹出下面的输入框

    输入完成后,点击确定即可更新数据

    五、项目中的关键代码

    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();

    六、心目中的前五名

    1、季澈组 https://www.cnblogs.com/qingzhujushi/p/10200806.html

    类似网易云音乐界面美观优雅的音乐播放器

    项目优点:自动搜集本地音乐,有上一曲,下一曲,开始暂停,顺序播放,随机播放,单首播放,音量的控制,进度条,歌词,可以删除歌曲

    项目缺点:无法加载云端的音乐,不能说是一个完美的音乐播放器。没有用户机制,无法保存自己的歌曲列表。

    我的设想:支持播放云端的音乐,爬虫现有几个音乐播放器的资源。加入用户机制。

    2、贺鸿琨组 https://www.cnblogs.com/hehongkun/p/10202262.html

    类似QQ音乐界面,选择图片资源很优秀

    项目优点:完成了歌曲列表与播放界面之间的切换,完成了播放过程中图片旋转状态与歌曲播放状态的绑定,还完成了歌曲进度条与歌曲进度的绑定。

    项目缺点:无法加载云端的音乐,没有用户机制

    我的设想:支持播放云端的音乐,爬虫现有几个音乐播放器的资源

    3、李凌龙组 https://www.cnblogs.com/Trip1eL/p/10190488.html

    对于我这样爱忘事者,这是一个刚需,简单使用

    项目优点:功能齐全,界面简单

    项目缺点:没有批量删除功能

    我的设想:支持语音助手,一句话就能设定好

    4、田光欣组 https://www.cnblogs.com/tiangxin/p/10206469.html

    简单使用,没有花里胡哨功能的记事本

    项目优点:界面简单,功能齐全

    项目缺点:数据存储在本地,更换手机后无法使用

    我的设想:支持语音助手,一句话记下文本,支持图片、音频、视频的记录。将数据加密存储在服务器上。

    5、李钊组 https://www.cnblogs.com/18LZblog/p/10205321.html

    功能齐全的乒乓球社区,乒乓球爱好者的必备

    项目优点:功能齐全,无论是视频、排名、照片等都能一次性了解到

    项目缺点:无法联网实时获取最新的数据

    我的设想:APP自动去互联网上爬去最新的信息,向用户展示最新的数据

    七、遇到的问题及解决方案

    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的各类操作

    并建立两张哈希表

    第一张为用户信息表

    key:Username value:UserID Password Email PhoneNumber SaveTime
    _, 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 因为技术上的问题,我们最早想要实现的局域网自动扫描没有编写出来,所以现在所有的功能必须经由服务器

     

    八、分工

    姓名      分工                                                                 工作比例      分数

    李怡龙  服务器、PC端、安卓端POST、剪切板操作    50%             5

    刘显云  安卓端登录UI设计、数据读取、存储               25%             2.5

    刘志祥  安卓端listview设计及响应                                25%             2.5

    九、运行演示

    启动Server及PC(Server实际运行于服务器,这里只是用于演示)

    Server工作中交换数据的输出(实际工作中不输出,这里只用于演示效果)

    Windroid安卓端

    不知什么原因,登录界面的动画在模拟器中无法显示,所以这里在小米MIX(Android8.0)环境下演示

    演示途中的黑屏,是应为调起小米安全键盘时,MIUI系统不允许录制该部分,所以自动进行了遮挡

    博客园限制无法上传20M以上的图片,所以这里图片存于公共图床,可能加载相对较慢时点击这个链接前往腾讯云对象存储下载(流量贼啦贵,不到万不得已不要下)

  • 相关阅读:
    Windows server 2016 解决“无法完成域加入,原因是试图加入的域的SID与本计算机的SID相同。”
    Windows Server 2016 辅助域控制器搭建
    Windows Server 2016 主域控制器搭建
    Net Framework 4.7.2 覆盖 Net Framework 4.5 解决办法
    SQL SERVER 2012更改默认的端口号为1772
    Windows下彻底卸载删除SQL Serever2012
    在Windows Server2016中安装SQL Server2016
    SQL Server 创建索引
    C#控制台或应用程序中两个多个Main()方法的设置
    Icon cache rebuilding with Delphi(Delphi 清除Windows 图标缓存源代码)
  • 原文地址:https://www.cnblogs.com/lee-li/p/10205512.html
Copyright © 2011-2022 走看看