产品介绍

项目背景

  随着网络的出现,即时通讯软件便出现在了人们的生活中,即时通讯app可以帮助人们实现快速沟通,节省时间,提高效率。例如,团队成员可以在办公室或出差途中随时通过即时通讯app进行实时讨论和共享资源,避免沟通障碍,增进沟通的质量和效果。

技术框架

  本次的软件开发主要以Java编程环境为主,在手机端编写安卓原生APP,在服务器后端使用SpringBoot框架为主,实现用户消息的转发和暂存,用户头像的上传与保存。

upload successful

  在软件的后端实现上,我使用SpringBoot框架,目的是使得产品的后期功能开发更加轻松,同时使用SpringBoot可以将服务端一键打包成jar包,简化服务器部署的复杂度。用户的头像使用腾讯云的对象存储保存,免去了调试与实际运行时头像存储位置不一致的问题,同时文件保存的可靠性和安全性也得到加强。

功能介绍

登录功能介绍

  本次开发的APP与大部分软件不同,为保证软件的私密性,我们采用最小云端保存的方案,即将用户的数据尽量的放在本地,仅仅上传必须的内容。   用户初次使用本软件,软件会引导用户完成昵称和个性签名的配置,配置完成之后,系统会通过使用安卓的API生成一个几乎唯一的AndroidID作为用户的唯一ID。至此用户的ID基本信息就设置完成了。 upload successful

头像上传

  为保证更好的美观以及用户的体验,我们的软件可以自定义头像,用户可以从自己相册或者文件中选择一个自己喜欢的照片作为自己的头像。选择完成之后,软件便会上传头像,进入到欢迎界面。

upload successful

扫一扫添加好友

  使用扫一扫添加好友,在另一部手机的我的界面点击二维码的小图标,进入名片界面,使用另一部手机APP中的扫一扫功能扫描名片上的二维码,即可添加对方为好友。

upload successful

好友聊天

  选择一个好友,点击该好有,选择发消息,进入到聊天界面,在聊天框输入消息,点击发送,好友便会收到消息,并收到通知(需要打开通知权限)。

upload successful

更换聊天背景

  进入我的界面,点击关于,在关于界面中点击摇一摇更换头像开关,返回到聊天界面,摇一摇手机即可切换软件内部自带的聊天背景,此时去关于界面关闭摇一摇切换聊天背景,即可固定当前的聊天背景。

upload successful

清空聊天记录

  在消息界面,长按消息,软件会弹出一个界面您可以选择不显示该聊天,或者删除该聊天。

upload successful

删除好友

  在好友界面,点击任意好友,选择删除好友,再次确认,即可删除该好友,好友删除后,对方的好友列表中自己并不会消失,对方给自己发送的消息自己将无法收到。

upload successful

技术实现

  本次软件的核心功能便是用户消息的转发与暂存,网络的状况千变万化,如何保证消息能够被正确的接收和发送,避免消息被漏掉以及消息的实时性都是我们要关注的重点。

消息的封装

  为保证消息的完整性和后期修改的灵活行,我们将消息封装在一个实体类中,并将该类序列化以便在Socket上传输。

package cn.shilight.myapplication.message;

import java.io.Serializable;

public class MessagObtian implements Serializable {
   
   private static final long serialVersionUID = 7471391170055841173L;


   public String getForUid() {
   	return forUid;
   }

   public void setForUid(String forUid) {
   	this.forUid = forUid;
   }

   public String getFromUid() {
   	return fromUid;
   }

   public void setFromUid(String fromUid) {
   	this.fromUid = fromUid;
   }

   private String forUid;
   private String fromUid;
   private int messageType;


   public int getMessageType() {
   	return messageType;
   }

   public void setMessageType(int messageType) {
   	this.messageType = messageType;
   }

   private String content ;
   final static int Tap = 0;
   final static int Text = 1;
   
   
   public MessagObtian(String forUid, String fromUid, int messageType) {
   	// TODO Auto-generated method stub
   	this.forUid = forUid;
   	this.fromUid = fromUid;
   	this.messageType = messageType;
   	
   }
   
   
   public String getContent() {
   	return content;
   }


   public MessagObtian setContent(String content) {
   	this.content = content;
   	return this;
   }
   
   
   

}

图表 2消息类

消息的转发

  我们的服务器会为每一个用户的套接字连接请求开两个线程,一个用来接收客户发送来的消息,另一个用来向客户端发送属于该客户端的消息。同时使用一个线程管理类来维护线程的运行,确保在客户端断开时,线程能够及时关闭。减少系统负载。

    package cn.shilight.myapplication.messagehandle;

import cn.shilight.myapplication.message.MessagObtian;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

public class WorkThread {
    Logger logger = LoggerFactory.getLogger(WorkThread.class);


    private Socket server;
    private String CurrectUser;
    private boolean alive;
    ObjectInputStream io;
    ObjectOutputStream oo;
    int flag = 0;
    Thread in;
    Thread out;


    public WorkThread(Socket server,String clientId,ObjectInputStream io,ObjectOutputStream oo){
        super();
        this.server = server;
        CurrectUser = clientId;
        this.io = io;
        this.oo = oo;
        alive = true;
        logger.info("客户端"+clientId+"收发管理线程建立成功");
        flag = 1;

    }


    public void closeThread(){
        this.alive = false;
        in.interrupt();
        out.interrupt();
    }


    public void start(){

        /// 接收线程
       in =  new Thread(new Runnable() {
            @Override
            public void run() {

                //System.out.println("接收线程开始");
                while(server.isConnected()&&!server.isClosed()&&alive){
                    try {
                        MessagObtian ms = (MessagObtian) io.readObject();
                        CurrectUser = ms.getFromUid();

                       logger.info("保存从"+CurrectUser+"客户端发来的消息");
                        MessageManager.showMessageData();

                        MessageManager.addMessage(ms);
                    } catch (IOException e) {
                        logger.error("接收线程IO错误");
                        break;

                    } catch (ClassNotFoundException e) {
                        logger.error("对象未找到-反序列化失败");
                        break;
                    }
                }
                logger.error("接收线程与"+CurrectUser+"客户端断开");
                flag = 0;
            }
        });


        //发送线程
       out =  new Thread(new Runnable() {
            MessagObtian t = null;

            @Override
            public void run() {
                while(server.isConnected()&&!server.isClosed()&&flag!=0&&alive){
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        break;
                    }

                    try {
                        if(MessageManager.isHaveMessage(CurrectUser)&&server.isConnected()&&flag!=0){
                             t = MessageManager.getMessage(CurrectUser);
                            //System.out.println("拿到消息");

                            if(t!=null){
                               // System.out.println(t.getContent());
                                oo.writeObject(t);
                                oo.flush();
                                t = null;
                            }
                        }

                    } catch (IOException e) {
                        if (t!=null){
                            MessageManager.addMessage(t);
                        }
                        logger.error("发送失败 等待下次发送");
                        break;

                    }
                }

                logger.error("发送线程与客户端"+CurrectUser+"断开");
                if (t!=null){
                    MessageManager.addMessage(t);
                    t = null;
                }
            }
        });

       in.start();
       out.start();


        while(alive){
            ///什么也不做
            try {
                Thread.sleep(1000);
            }catch (InterruptedException E){


            }
        };

    }




}

消息的暂存

  天下没有不散的宴席,用户不可能一直在线,当用户离线时,用户要接收的消息就应该先保存在服务器,等用户在线时按照时间顺序将用户的消息发送给用户。(类似于信箱)。   以下为服务器消息管理类,该类使用 HashMa+ LinkedList来存储不同客户端的消息队列。

package cn.shilight.myapplication.messagehandle;

import cn.shilight.myapplication.message.MessagObtian;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

public class MessageManager {
   static private Map<String,LinkedList<MessagObtian>> MessageMap = new HashMap<>() ;
    static Logger logger = LoggerFactory.getLogger(MessageManager.class);


    // 向系统中添加 消息
   static public void addMessage(MessagObtian messagObtian){

      if(MessageMap.containsKey(messagObtian.getForUid())) {

           MessageMap.get(messagObtian.getForUid()).offer(messagObtian);

       }else {

          MessageMap.put(messagObtian.getForUid(), new LinkedList<MessagObtian>());
          MessageMap.get(messagObtian.getForUid()).offer(messagObtian);

      }

   }

    //  对应用户是否有消息
    static public boolean isHaveMessage(String getUser){

        if(MessageMap.containsKey(getUser)) {

            if(MessageMap.get(getUser).size()>0){
                return true;
            }else {
                return false;
            }

        }else {
            return false;

        }

    }

    //  取出用户的消息
    static public MessagObtian getMessage(String getUser){

        if(MessageMap.containsKey(getUser)) {

            return MessageMap.get(getUser).poll();

        }else {

            return null;

        }

    }

    static public void showMessageData(){

        logger.info("当前待转发消息用户消息队列个数(非消息总数): " +String.valueOf(MessageMap.size()));

    }

}

总结

  纸上得来终觉浅,觉知此事要躬行,起初,在进行功能得初步拟定以及思考产品的实现思路上,我们想的还远远不够,因此在最后的代码编写遇到了许多意想不到得问题,但是令人欣慰的是,我们都将这些突入其来的困难解决掉了,整个过程中,不能说顺风顺水,但是也没有过多的我们难以解决的问题,通过这一次安卓实习大作业,我们深刻的了解到仅仅思考所存在的局限性,经验是生活的肥料,有什么用的经验就有什么样的人生,沙漠里长不出牡丹。而积累经验的过程就是脚踏实地,干实事,抛弃空想。时间短暂,这一次的实习也在本文的结束而告一段落,我很享受这个过程,不仅仅是应为他带给了我更多的经验,更重要的是,他让我们有事可做、有梦可追、有友可伴、我们永远都在路上……

开源软件使用情况

1、UCrop GitHub - Yalantis/uCrop: Image Cropping Library for Android 安卓图像剪切

2、Okhttp3 网络访问

3、com.google.zxing:core 二维码相关

4、com.jinrishici:android-sdk:1.5 今日诗句 API 实现 我的 界面自定义诗句