• [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以上的图片,所以这里图片存于公共图床,可能加载相对较慢时点击这个链接前往腾讯云对象存储下载(流量贼啦贵,不到万不得已不要下)

  • 相关阅读:
    使用Haskell写web
    src/lxml/etree.so: undefined symbol: xmlSchematronSetValidStructuredErrors 解决方案
    CentOS允许某一端口接受外部链接
    windows下的NTP服务
    Huffman树,Huffman编码的实现(C#)
    OpenGL的函数(GLU, GLUT)
    OpenGL的函数(GL)
    GLUT函数说明
    FreeImage使用基础,图像旋转,图像滤波
    Hello PureMVC!!!
  • 原文地址:https://www.cnblogs.com/lee-li/p/10205512.html
Copyright © 2020-2023  润新知