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

原文地址:https://www.cnblogs.com/lee-li/p/10205512.html