树莓派4B与Android之缘——Android应用MiniChat聊天软件

2023-10-26

基本介绍和总体架构

一、 应用介绍

(一)基本介绍

MiniChat是一款聊天软件,你可以通过此软件进行聊天, 本应用集成了融云模块,从而实现集成通讯。主要功能是账号注册,登录,好友添加,好友间发送文字、文件信息。

(二)应用展示

1.登录界面

未输入状态:
在这里插入图片描述
输入状态:
在这里插入图片描述

2.主界面

朋友栏:
在这里插入图片描述
个人栏:
在这里插入图片描述
会话列表:
在这里插入图片描述

好友添加:
在这里插入图片描述
聊天界面:
在这里插入图片描述
文件界面:
在这里插入图片描述

二、总体架构

1.组成及功能

组成:应用分为三个主要部分,客户端,即时通讯服务端,app 服务器端。
功能:客户端负责前端主要提供用户的功能使用。即时通讯服务端负责应用的通讯服务,负责消息处理,消息的转发。app服务器端负责个人信息维护,好友关系的维护。

2.流程:

在这里插入图片描述

三、主要任务

我们需要完成四个部分的工作。
第一,我们需要设计app,app能够通过网络访问app server,从而获取个人信息,好友关系,并且将信息存储到本地;第二,我们需要设计一个服务器,能够接受app的访问,连接数据库,返回用户所需信息;第三,我们需要集成融云客户端,从而实现通信;第四,设计数据库供app服务器进行访问。

四、各结构模块组成

app端:网络信息处理+本地数据库存储+UI+前端时间处理
app server端:servlet设计+数据库访问设计+数据过滤、整理
IM 端:app端集成+聊天信息处理
数据库端:设计关系模式+确定逻辑与物理结构+账户设置

五、开发IDE与相关库

1.IDE

app: Android Studio
app server: Intellij IDEA
融云:Android Studio
数据库:mysql workbench

2.相关库

app:room(本地数据库sqlite工具库)、jetback(Android 官方库)、okhttp(http协议网络连接工具)、glide(图片加载、缓冲库)、GSON(json处理工具)
app server:tomcat(服务器)、GSON、mysql-connector-J
数据库:mysql
IM:MKit、IMLib

需求分析与数据库设计

六、需求分析

此处只做功能需求方面简要概括,实际上真正的需求分析考虑的很多,比如还需要考虑性能需求、安全需求、
其他需求、等方面。

功能需求:
1.用户可以注册账号
2.用户可以添加好友
3.用户可以进行聊天
4.聊天信息可以发送文字、文件。

七、数据库设计

1.关系模型

由于app server只负责用户信息与好友关系维护,因此用于appserver访问的数据库关系模型即user与user组成friend关系,并不复杂。我们只需要设计用户user表、好友关系friend表就好。

2.数据表

friend表:
表结构:
主键:朋友id,用户id
在这里插入图片描述
样例数据:
在这里插入图片描述
第一行数据表示123456是1234567的朋友,123456的个人名字是123456,nick_name表示1234567为123456起的备注名字
user表:
表结构:
在这里插入图片描述
样例数据:
在这里插入图片描述

APP界面设计

八、界面结构

采用tab结构,activity与fragment进行结合。
在这里插入图片描述

九、主要界面设计

1. 主界面

博主使用了android jetback的导航组件,我们利用导航组件可以轻松的完成tab结构中的fragment跳转。
关于导航组件的介绍,请见下面链接
https://developer.android.google.cn/guide/navigation
(官方指南,yyds)
在这里插入图片描述

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="?attr/actionBarTheme"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@+id/nav_view"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/toolbar"
        app:navGraph="@navigation/mobile_navigation" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/bottom_nav_menu" />

</androidx.constraintlayout.widget.ConstraintLayout>

2.朋友fragment

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/friendsLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.Friends.FriendsFragment">

    <SearchView
        android:id="@+id/searchView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </SearchView>

    <ScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:fillViewport="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/searchView"
        app:layout_constraintVertical_bias="0.0">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/friend_list"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </ScrollView>

</androidx.constraintlayout.widget.ConstraintLayout>

3.个人界面

在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.home.HomeFragment">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/home_head"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        app:layout_constraintBottom_toTopOf="@id/home_box"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:id="@+id/account"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="15dp"
            android:text="113445368"
            app:layout_constraintBottom_toTopOf="@+id/name"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />

        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="15dp"
            android:text="张三"
            app:layout_constraintStart_toStartOf="@id/account"
            app:layout_constraintTop_toBottomOf="@id/account" />

    </androidx.constraintlayout.widget.ConstraintLayout>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/home_box"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/home_head">


        <LinearLayout
            android:id="@+id/accountView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <ImageView
                android:id="@+id/account_setting_icon"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="8dp"
                android:layout_marginTop="10dp"
                android:src="@drawable/rc_cs_default_portrait" />

            <TextView
                android:id="@+id/account_setting"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:text="账号管理"
                android:textSize="18sp" />
        </LinearLayout>


        <LinearLayout
            android:id="@+id/messageView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/accountView">

            <ImageView
                android:id="@+id/message_setting_icon"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="8dp"
                android:layout_marginTop="10dp"
                android:src="@drawable/rc_cs_default_portrait" />

            <TextView
                android:id="@+id/message_setting"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:text="消息管理"
                android:textSize="18sp" />
        </LinearLayout>
        <LinearLayout
            android:id="@+id/addFriendView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/messageView">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="8dp"
                android:layout_marginTop="10dp"
                android:src="@drawable/rc_cs_default_portrait" />

            <TextView
                android:id="@+id/add_friend_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:text="添加朋友"
                android:textSize="18sp" />
        </LinearLayout>


    </androidx.constraintlayout.widget.ConstraintLayout>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent">

        <Button
            android:id="@+id/exit"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:text="退出当前账号"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

3.登录界面

在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".ui.login.LoginActivity">

    <EditText
        android:id="@+id/username"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:layout_marginTop="96dp"
        android:layout_marginEnd="24dp"
        android:hint="电话"
        android:inputType="textPhonetic"
        android:selectAllOnFocus="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/password"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="24dp"
        android:hint="密码"
        android:imeActionLabel="@string/action_sign_in_short"
        android:imeOptions="actionDone"
        android:inputType="textPassword"
        android:selectAllOnFocus="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/username" />

    <Button
        android:id="@+id/rigster"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="start"
        android:layout_marginStart="48dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="48dp"
        android:layout_marginBottom="64dp"
        android:enabled="false"
        android:text="登录"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/password"
        app:layout_constraintVertical_bias="0.2" />

    <Button
        android:id="@+id/rigist_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="注册账号"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <ProgressBar
        android:id="@+id/loading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginStart="32dp"
        android:layout_marginTop="64dp"
        android:layout_marginEnd="32dp"
        android:layout_marginBottom="64dp"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="@+id/password"
        app:layout_constraintStart_toStartOf="@+id/password"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.3" />

</androidx.constraintlayout.widget.ConstraintLayout>

4.注册界面

在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SignUpActivity">

    <EditText
        android:id="@+id/phone_number"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:layout_marginTop="96dp"
        android:layout_marginEnd="24dp"
        android:hint="电话"
        android:inputType="textPhonetic"
        android:selectAllOnFocus="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/person_name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:layout_marginEnd="24dp"
        android:hint="名称"
        android:inputType="textPhonetic"
        android:selectAllOnFocus="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/phone_number" />

    <EditText
        android:id="@+id/password"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="24dp"
        android:hint="密码"
        android:imeActionLabel="@string/action_sign_in_short"
        android:imeOptions="actionDone"
        android:inputType="textPassword"
        android:selectAllOnFocus="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/person_name" />

    <EditText
        android:id="@+id/password2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:layout_marginEnd="24dp"
        android:hint="确认密码"
        android:imeActionLabel="@string/action_sign_in_short"
        android:imeOptions="actionDone"
        android:inputType="textPassword"
        android:selectAllOnFocus="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/password" />

    <Button
        android:id="@+id/rigster"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="start"
        android:layout_marginStart="48dp"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="48dp"
        android:text="注册"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/password2" />

</androidx.constraintlayout.widget.ConstraintLayout>

5.好友添加

在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FriendAddActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="请输入账号ID"
        android:textColor="@color/black"
        android:textSize="18sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textInputLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/friend_add_input"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="账号ID" />

    </com.google.android.material.textfield.TextInputLayout>

    <Button
        android:id="@+id/friend_add_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="19dp"
        android:text="添加"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textInputLayout" />

</androidx.constraintlayout.widget.ConstraintLayout>

6.文件展示

在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FileActivity">

    <ImageView
        android:id="@+id/imageView2"
        android:layout_width="91dp"
        android:layout_height="83dp"
        android:layout_marginTop="100dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/rc_file_icon_else" />

    <TextView
        android:id="@+id/fileName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="文件名"
        android:textSize="18sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageView2" />

    <Button
        android:id="@+id/other_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:text="用其他应用打开"
        android:visibility="gone"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/fileName" />

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="50dp"
        android:layout_marginTop="13dp"
        android:layout_marginEnd="50dp"
        android:layout_marginBottom="2dp"
        app:layout_constraintBottom_toTopOf="@+id/other_button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/fileName" />

    <Button
        android:id="@+id/download"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:text="开始下载"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/fileName" />
</androidx.constraintlayout.widget.ConstraintLayout>

App架构组成与项目结构组成

十、架构组成

1.ui架构

对于每一个主题,采用了activity+viewmodel+livedata+repository+datasource模式。我们设计应用时您不应在应用组件中存储任何应用数据或状态,并且应用组件不应相互依赖,而是应该注意分离关注点模型驱动界面
分离关注点是一个重要原则。一种常见的错误是在一个 Activity 或 Fragment 中编写所有代码。这些基于界面的类应仅包含处理界面和操作系统交互的逻辑。您应使这些类尽可能保持精简,这样可以避免许多与生命周期相关的问题。
另一个重要原则是您应该通过模型驱动界面(最好是持久性模型)。模型是负责处理应用数据的组件。它们独立于应用中的 View 对象和应用组件,因此不受应用的生命周期以及相关的关注点的影响。
持久性是理想之选,原因如下:
①如果 Android 操作系统销毁应用以释放资源,用户不会丢失数据。
②当网络连接不稳定或不可用时,应用会继续工作。
应用所基于的模型类应明确定义数据管理职责,这样将使应用更可测试且更一致。
博主个人认为在这样的架构下有三个优点,第一,结构精简且执行有效;第二点,便于测试,结构进行分离,使得测试更加简单;第三点,代码具有更强的健壮性,在极端环境下,保证应用可使用。

在这里插入图片描述

2.其他架构

(1)Room部分

此处不多赘述,主要是数据库的创建,这些均属于基础知识,建议去官方指南学习或者其他学习资源。

(2)网络部分

网络部分基于okhttp3库,Hp内封装了get方法,和post方法,实现http get和post方式。
hp接口,包装各个主题需要使用的网络请求,hpImpl中的类去实现接口。上图中的retrofit也是基于okhttp的,不过博主并未使用。retrofit是一个非常好的库,不过需要注意一些坑。

十一、项目结构

此处只介绍java部分,不再介绍res部分

1.java部分

在这里插入图片描述
java部分共为六个模块,data部分是数据的获取模块,db是本地数据库sqlite设计,netservice是网络模块,处理app与服务器的网络访问,ui是前端界面与事件响应设计模块,其余是主体activity。

(1)data模块详解

在这里插入图片描述
friends好友关系的数据源处理,home个人信息的数据源处理,login登录的数据源处理,model是使用到的bean模型,Result是一个数据结果类,用来表示数据获取的成功与否。

(2)db

在这里插入图片描述
dao表示数据库信息获取的sql处理语句类,model是本地数据库表的构建,appdatabase初始化本地数据库。

(3)nerservice

在这里插入图片描述在这里插入图片描述
①Base网络访问的接口
②HpInterface 网络特定访问接口继承于Hp
③HpImpl 是网络接口的实现
④HpModel message是基本信息体,response中所有类均继承于message,request是请求结构的适配体

(4)ui

在这里插入图片描述
表示各个主题的viewmodel与fragment设计

(5)util

在这里插入图片描述
这个包内包含了一个SHA1的加密算法,可以将一个字符串进行sha1运算。

App的DB设计与网络模块设计

十二、Room本地数据库

(一)引入

关于room库的最新版本请到room的github项目地址查看,关于room库的使用请查看官方指南

def room_version = "2.3.0"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"

(二)设计

1.appdatebase初始化

AppDataBase.java

@Database(entities = {UserRoom.class, FriendRoom.class},version = 1,exportSchema = false)
public abstract class APPDataBase extends RoomDatabase {
    public abstract UserDao userDao();
    public abstract FriendDao friendDao();
}

2.dao设计

UserDao.java

@Dao
public interface UserDao {
    @Query("select * from user")
    List<UserRoom> getAll();
    @Insert
    Completable insertOne(UserRoom userRoom);
    @Update
    Completable updateOne(UserRoom userRoom);
    @Query("delete from user where uid = :uid")
    Completable delete(String uid);
    @Query("select * from user where uid = :uid")
    Single<UserRoom> getById(String uid);
    @Query("update user set is_log=0 where is_log=1")
    Completable updateLog();
    @Query("select * from user where is_log=1")
    Single<UserRoom> getLogUser();

}

其余Dao将不介绍,请查看本人github项目。

3.表设计

UserRoom.java

@Entity(tableName = "user")
public class UserRoom {
    @PrimaryKey
    @ColumnInfo(name = "uid")
    @NonNull
    public String  uid = null;
    @ColumnInfo(name = "user_name")
    public String userName;
    @ColumnInfo(name = "is_log")
    public int isLog;

    @Override
    public String toString() {
        return "UserRoom{" +
                "uid='" + uid + '\'' +
                ", userName='" + userName + '\'' +
                ", isLog=" + isLog +
                '}';
    }
}

其余不做介绍,请查看本人github项目。

十三、网络模块设计

(一)post数据流

       json数据                解析json数据

app    ----------->servlert--------------->执行数据访问
app    <-----------servlert<---------------数据库
       接受json数据并解析               数据集转json
对象流:Request—>json—>requestModel–>database—>dbmodel–>responsemodel—>json---->response

(二)okhttp引入

implementation 'com.squareup.okhttp3:okhttp:4.9.0'

(三)Base设计

1.HttpGet.java

public class HttpGet implements Callable<String> {
    //要get的url
    private String url;

    public HttpGet(String url) {
        //初始化
        this.url = url;
    }


    @Override
    public String call() throws Exception {
        //获得okHttp客户端
        OkHttpClient client=new OkHttpClient();
        Log.d("httpGet",url+" start");
        //构建request
        Request request = new Request.Builder()
                .url(url)
                .build();
        Log.d("httpGet",url+" build");
        //执行访问
        Response response=client.newCall(request).execute();
        Log.d("httpGet",url+" execute");
        return Objects.requireNonNull(response.body()).string();
    }
}

2.HttpPost.java

public class HttpPost implements Callable<String> {
    private String url;
    private String json;
    private static final MediaType JSON
            = MediaType.get("application/json; charset=utf-8");

    public HttpPost(String url, String json) {
        this.url = url;
        this.json = json;
    }

    @Override
    public String call() throws Exception {
        //获得okHttp客户端
        OkHttpClient client=new OkHttpClient();
        Log.d("httpPost",url+" start for json:"+json);
        RequestBody body = RequestBody.create(JSON, json);
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();
        Log.d("httpPost",url+" build:"+body.contentType());
        Response response= client.newCall(request).execute();
        Log.d("httpPost",url+" receive");
        return Objects.requireNonNull(response.body()).string();
    }
}

3.HttpRongPost.java

public class HttpPostRong implements Callable<String> {
    private String url;
    private String json;
    private RequestBody body;

    public HttpPostRong(String url, RequestBody requestBody) {
        this.url = url;
        this.body=requestBody;
    }

    @Override
    public String call() throws Exception {

        String app_secret="xxxxxx";
        String nonce="131415";
        String timestamp=String.valueOf(System.currentTimeMillis());
        //进行sha1计算
        String signature= SHA1.sha1(app_secret+nonce+timestamp);
        //获得okHttp客户端
        OkHttpClient client=new OkHttpClient();
        Log.d("httpPost",url+" start for body:"+body.contentType());
        Request request = new Request.Builder()
                .addHeader("App-Key","8luwapkv86p3l")
                .addHeader("Nonce",nonce)
                .addHeader("Timestamp",timestamp)
                .addHeader("Signature",signature)
                .url(url)
                .post(body)
                .build();
        Log.d("httpPost",url+" build:"+request.toString());
        Response response= client.newCall(request).execute();
        Log.d("httpPost",url+" receive");
        return Objects.requireNonNull(response.body()).string();
    }
}

4.Hp.java

public interface Hp {
    Gson gson=new Gson();
    default String get(final String url) throws IOException, ExecutionException, InterruptedException {
        //execute get 执行get
        HttpGet get=new HttpGet(url);
        //thread schedule 线程调用
        ExecutorService service= Executors.newFixedThreadPool(1);
        Future<String> future_get=service.submit(get);
        return future_get.get();

    }
    default String post(final String url,final String json) throws IOException, ExecutionException, InterruptedException {
        //execute post 执行post
        HttpPost post=new HttpPost(url,json);
        //thread schedule 线程调用
        ExecutorService service= Executors.newFixedThreadPool(1);
        Future<String> future_post=service.submit(post);
        return future_post.get();
    }

    default String postRong(final String url, final RequestBody body) throws IOException, ExecutionException, InterruptedException {
        //excute RongCloud post 执行融云的post
        HttpPostRong post=new HttpPostRong(url,body);
        //thread schedule 线程调用
        ExecutorService service= Executors.newFixedThreadPool(1);
        Future<String> future_post=service.submit(post);
        return future_post.get();
    }

}

AppUi设计

十四、主界面设计

(一)BottomActivity主界面类

此类集成了融云Im服务,对融云服务器进行连接,另外初始化viewmodel,设置导航,便于切换。

public class BottomActivity extends AppCompatActivity {

    //最主要的activity
    private BottomViewModel bottomViewModel;
    public static void actionStart(Context context,String uid,String token){
        Intent intent=new Intent(context,BottomActivity.class);
        intent.putExtra("uid",uid);
        intent.putExtra("token",token);
        context.startActivity(intent);
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bottom);

        bottomViewModel=new ViewModelProvider(this,new ViewModelProvider.NewInstanceFactory()).get(BottomViewModel.class);
        Intent intent=getIntent();
        String uid=intent.getStringExtra("uid");
        String token=intent.getStringExtra("token");
        bottomViewModel.getUid().setValue(uid);
        bottomViewModel.setToken(token);

        //Rong
        RongIM.connect(token, new RongIMClient.ConnectCallback() {
            @Override
            public void onSuccess(String s) {

            }

            @Override
            public void onError(RongIMClient.ConnectionErrorCode connectionErrorCode) {
                if(connectionErrorCode.equals(RongIMClient.ConnectionErrorCode.RC_CONN_TOKEN_INCORRECT)) {
                    //从 APP 服务获取新 token,并重连
                } else {
                    //无法连接 IM 服务器,请根据相应的错误码作出对应处理
                }
            }

            @Override
            public void onDatabaseOpened(RongIMClient.DatabaseOpenStatus databaseOpenStatus) {
                //消息数据库打开,可以进入到主页面
            }
        });

        RongConfigCenter.conversationConfig().setConversationClickListener(new ConversationClickListener() {
            @Override
            public boolean onUserPortraitClick(Context context, Conversation.ConversationType conversationType, UserInfo userInfo, String s) {
                return false;
            }

            @Override
            public boolean onUserPortraitLongClick(Context context, Conversation.ConversationType conversationType, UserInfo userInfo, String s) {
                return false;
            }

            @Override
            public boolean onMessageClick(Context context, View view, Message message) {
                //文件消息处理
                /*if(message.getObjectName().equals("RC:FileMsg")){
                    FileMessage fileMessage=(FileMessage)message.getContent();
                    //FileActivity.ActionStart(context,fileMessage);
                    return false;
                }*/
                return false;
            }

            @Override
            public boolean onMessageLongClick(Context context, View view, Message message) {
                return false;
            }

            @Override
            public boolean onMessageLinkClick(Context context, String s, Message message) {
                return false;
            }

            @Override
            public boolean onReadReceiptStateClick(Context context, Message message) {
                return false;
            }
        });


        bottomViewModel.getIs_logout().observe(this, new Observer<Boolean>() {
            @Override
            public void onChanged(Boolean aBoolean) {
                if(aBoolean){
                    LoginActivity.ActionStart(BottomActivity.this);
                    finish();
                }
            }
        });
        Log.d("bottomViewModel",bottomViewModel.toString());


        //导航组件的处理
        BottomNavigationView navView = findViewById(R.id.nav_view);
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations. id要一一对应
        //标题
        AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
                R.id.navigation_conversation,R.id.navigation_friends,R.id.navigation_home )
                .build();
        NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
        NavController navController = navHostFragment.getNavController();
        //NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
        //设置toolbar
        NavigationUI.setupWithNavController(findViewById(R.id.toolbar),navController,appBarConfiguration);
        //设置导航图
        NavigationUI.setupWithNavController(navView, navController);
    }
}

(二)BottomViewModel类

public class BottomViewModel extends ViewModel {
    private MutableLiveData<String> uid;
    private MutableLiveData<String> u_name;
    private MutableLiveData<Boolean> is_logout;
    private String token;


    public BottomViewModel(){
        uid=new MutableLiveData<>();
        u_name=new MutableLiveData<>();
        is_logout=new MutableLiveData<>();
        is_logout.setValue(false);
    }

    public MutableLiveData<String> getUid() {
        return uid;
    }

    public MutableLiveData<String> getU_name() {
        return u_name;
    }

    public MutableLiveData<Boolean> getIs_logout() {
        return is_logout;
    }

    public void logout(){
        is_logout.setValue(true);
    }

    public void setToken(String token) {
        this.token=token;
    }

    public String getToken() {
        return token;
    }

    @Override
    public String toString() {
        return "BottomViewModel{" +
                "uid=" + uid.getValue() +
                ", u_name=" + u_name.getValue() +
                ", is_logout=" + is_logout.getValue() +
                ", token='" + token + '\'' +
                '}';
    }
}

十五、个人主页

(一)HomeFragment类

视图

public class HomeFragment extends Fragment implements LifecycleOwner {

    private HomeViewModel homeViewModel;

    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_home, container, false);
        homeViewModel =
                new ViewModelProvider(this,new HomeViewModelFactory()).get(HomeViewModel.class);
        BottomViewModel bottomViewModel=new ViewModelProvider(requireActivity()).get(BottomViewModel.class);
        final TextView accountView = root.findViewById(R.id.account);
        final TextView nameView=root.findViewById(R.id.name);
        final View a_s_view=root.findViewById(R.id.accountView);
        final View m_s_view=root.findViewById(R.id.messageView);
        final View addFriendView=root.findViewById(R.id.addFriendView);
        final Button exitButton=root.findViewById(R.id.exit);
		//视图根据viewModel中的liveData监测
        homeViewModel.getUid().observe(getViewLifecycleOwner(), new Observer<String>() {
            @Override
            public void onChanged(String s) {
                accountView.setText(s);
            }
        });
        homeViewModel.getU_name().observe(getViewLifecycleOwner(), new Observer<String>() {
            @Override
            public void onChanged(String s) {
                nameView.setText(s);
            }
        });

        //activity的启动
        a_s_view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                AccountSettingActivity.ActionStart(getContext());
            }
        });
        m_s_view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                MessageSettingsActivity.ActionStart(getContext());
            }
        });
        addFriendView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                FriendAddActivity.ActionStart(getContext(),bottomViewModel.getUid().getValue());
            }
        });
        //退出
        exitButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                bottomViewModel.logout();
                RongIM.getInstance().logout();
            }
        });


        return root;
    }

}

(二)HomeViewModel类

viewmodel 利用repository类进行构建,连接viewmodel数据源。

public class HomeViewModel extends ViewModel {

    private MutableLiveData<String> uid;
    private MutableLiveData<String> u_name;
    private HomeRepository homeRepository;

    public HomeViewModel(HomeRepository homeRepository) {
        uid=new MutableLiveData<>();
        u_name=new MutableLiveData<>();
        this.homeRepository=homeRepository;
        init();
    }

    public MutableLiveData<String> getUid() {
        return uid;
    }

    public MutableLiveData<String> getU_name() {
        return u_name;
    }


    private void init(){
        //初始化
        Result<User> userResult=homeRepository.getUser();
        if(userResult instanceof Result.Success){
            User user=((Result.Success<User>) userResult).getData();
            uid.setValue(user.getUid());
            u_name.setValue(user.getName());
        }else {
            uid.setValue("unknown error");
            u_name.setValue("unknown error");
        }
    }
}

(三)ViewFactory

public class HomeViewModelFactory implements ViewModelProvider.Factory {
    @NonNull
    @Override
    @SuppressWarnings("unchecked")
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        if (modelClass.isAssignableFrom(HomeViewModel.class)) {
            return (T) new HomeViewModel(HomeRepository.getInstance(new HomeDataSource()));
        } else {
            throw new IllegalArgumentException("Unknown ViewModel class");
        }
    }
}

十六、好友

(一)FriendFragment

public class FriendsFragment extends Fragment {

    private FriendsViewModel mViewModel;

    public static FriendsFragment newInstance() {
        return new FriendsFragment();
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        View root=inflater.inflate(R.layout.fragment_friends, container, false);

        //获取activity的viewModel
        BottomViewModel bottomViewModel=new ViewModelProvider(requireActivity()).get(BottomViewModel.class);
        //初始化viewmodel,获取朋友
        mViewModel=new ViewModelProvider(this,new FriendsViewModelFactory(bottomViewModel.getUid().getValue())).get(FriendsViewModel.class);
        mViewModel.init(bottomViewModel.getUid().getValue());

        //recyclerView构图
        RecyclerView recyclerView=root.findViewById(R.id.friend_list);
        LinearLayoutManager manager=new LinearLayoutManager(this.getContext());
        List<Friend> friendList=mViewModel.getFriendListData().getValue();
        Log.d("friendList",String.valueOf(friendList.size()));
        if(friendList==null){
            friendList=new ArrayList<>();
        }
        FriendAdapter adapter=new FriendAdapter(friendList);
        recyclerView.setLayoutManager(manager);
        recyclerView.setAdapter(adapter);

        mViewModel.getFriendListData().observe(getViewLifecycleOwner(), new Observer<List<Friend>>() {
            @Override
            public void onChanged(List<Friend> friendList) {
                adapter.notifyAfterAdd(friendList);
            }
        });
        return root;
    }
}

(二)ViewModel

public class FriendsViewModel extends ViewModel {
public MutableLiveData<List> getFriendListData() {
return friendListData;
}

public FriendRepository getRepository() {
    return repository;
}

private final MutableLiveData<List<Friend>> friendListData;
private final FriendRepository repository;

public FriendsViewModel(FriendRepository repository) {
    friendListData=new MutableLiveData<>();
    this.repository=repository;
}
public void init(String uid){
    //初始化
    Result<List<Friend>> result=repository.getFriends(uid);
    if(result instanceof Result.Success){
        List<Friend> friendList= ((Result.Success<List<Friend>>) result).getData();
        friendListData.setValue(friendList);
    }
    //test
    /*List<Friend> friendList=new ArrayList<>();
    friendList.add(new Friend("1234","1234567","xiaoming","nick1"));
    friendList.add(new Friend("12345","1234567","xiaming",null));
    friendListData.setValue(friendList);*/
}

}

(三)ViewmodelFactory

public class FriendsViewModelFactory implements ViewModelProvider.Factory{
    private String uid;

    public FriendsViewModelFactory(String uid) {
        this.uid = uid;
    }

    @NonNull
    @Override
    @SuppressWarnings("unchecked")
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        if (modelClass.isAssignableFrom(FriendsViewModel.class)) {
            return (T) new FriendsViewModel(FriendRepository.getInstance(new FriendDataSource()));
        } else {
            throw new IllegalArgumentException("Unknown ViewModel class");
        }
    }
}

(四)FriendAdapter

public class FriendAdapter extends RecyclerView.Adapter<FriendAdapter.ViewHolder> {
    private  List<Friend> friendList;
    public static class ViewHolder extends RecyclerView.ViewHolder{
        private final TextView nameView;
        private final View itemView;
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            this.itemView=itemView;
            nameView=(TextView)itemView.findViewById(R.id.friend_name);
        }

        public TextView getNameView() {
            return nameView;
        }

        public View getItemView() {
            return itemView;
        }
    }
    public void notifyAfterAdd(List<Friend> friends){
        friendList=friends;
        notifyDataSetChanged();
    }

    public FriendAdapter(List<Friend> friendList) {
        this.friendList=friendList;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_friend, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        //名称的设置
        if(friendList.get(position).getNick_name()==null){
            holder.getNameView().setText(friendList.get(position).getF_name());
        }else {
            holder.getNameView().setText(friendList.get(position).getNick_name());
        }

        holder.getItemView().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Conversation.ConversationType conversationType  = Conversation.ConversationType.PRIVATE;
                String targetId = friendList.get(position).getFid();
                String title =friendList.get(position).getF_name();
                Bundle bundle = new Bundle();
                if (!TextUtils.isEmpty(title)) {
                    bundle.putString(RouteUtils.TITLE, title); //会话页面标题
                    //bundle.putLong(RouteUtils.INDEX_MESSAGE_TIME, fixedMsgSentTime); //打开会话页面时的默认跳转位置,如果不配置将跳转到消息列表底部
                }
                RouteUtils.routeToConversationActivity(view.getContext(), conversationType, targetId, bundle);//跳转会话页面

            }
        });

    }

    @Override
    public int getItemCount() {
        return friendList.size();
    }
}

十七、注册

Activity

public class SignUpActivity extends AppCompatActivity {

    //注册活动
    public static void ActionStart(Context context){
        Intent intent=new Intent(context,SignUpActivity.class);
        context.startActivity(intent);
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sign_up);
        final EditText phone=findViewById(R.id.phone_number);
        final EditText name=findViewById(R.id.person_name);
        final EditText password=findViewById(R.id.password);
        final EditText password2=findViewById(R.id.password2);
        final Button button=findViewById(R.id.rigster);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String phone_v=phone.getText().toString();
                String name_v=name.getText().toString();
                String password_v=password.getText().toString();
                String password_v2=password2.getText().toString();

                if(phone_v.equals("")||name_v.equals("")||password_v.equals("")||password_v2.equals("")){
                    Toast.makeText(SignUpActivity.this,"不能为空",Toast.LENGTH_SHORT).show();
                }else {
                    if(!password_v.equals(password_v2)){
                        Toast.makeText(SignUpActivity.this,"密码不同",Toast.LENGTH_SHORT).show();
                    }else {
                        try {
                            boolean isOk=new SignUpImpl().signUp(phone_v,password_v2,name_v);
                            if(isOk){
                                Toast.makeText(getApplicationContext(),"注册成功",Toast.LENGTH_SHORT).show();
                                SignUpActivity.this.finish();
                            }else {
                                Toast.makeText(getApplicationContext(),"该账号已注册",Toast.LENGTH_SHORT).show();
                            }
                        } catch (HpException hpException) {
                            Toast.makeText(getApplicationContext(),"服务器错误hp",Toast.LENGTH_SHORT).show();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            Toast.makeText(getApplicationContext(),"服务器错误interrupt",Toast.LENGTH_SHORT).show();
                        } catch (ExecutionException e) {
                            e.printStackTrace();
                            Toast.makeText(getApplicationContext(),"服务器错误execu",Toast.LENGTH_SHORT).show();
                        } catch (IOException e) {
                            e.printStackTrace();
                            Toast.makeText(getApplicationContext(),"服务器错误io",Toast.LENGTH_SHORT).show();
                        }
                    }
                }
            }
        });


    }
}

十八、登录

(一)LoginActivity

public class LoginActivity extends AppCompatActivity {

    public static void ActionStart(Context context){
        Intent intent=new Intent(context,LoginActivity.class);
        context.startActivity(intent);
    }

    private LoginViewModel loginViewModel;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        loginViewModel = new ViewModelProvider(this, new LoginViewModelFactory())
                .get(LoginViewModel.class);

        final EditText usernameEditText = findViewById(R.id.username);
        final EditText passwordEditText = findViewById(R.id.password);
        final Button loginButton = findViewById(R.id.rigster);
        final ProgressBar loadingProgressBar = findViewById(R.id.loading);
        final Button signup_btn=findViewById(R.id.rigist_btn);

        loginViewModel.getLoginFormState().observe(this, new Observer<LoginFormState>() {
            @Override
            public void onChanged(@Nullable LoginFormState loginFormState) {
                if (loginFormState == null) {
                    return;
                }
                loginButton.setEnabled(loginFormState.isDataValid());
                if (loginFormState.getUsernameError() != null) {
                    usernameEditText.setError(getString(loginFormState.getUsernameError()));
                }
                if (loginFormState.getPasswordError() != null) {
                    passwordEditText.setError(getString(loginFormState.getPasswordError()));
                }
            }
        });

        loginViewModel.getLoginResult().observe(this, new Observer<LoginResult>() {
            @Override
            public void onChanged(@Nullable LoginResult loginResult) {
                if (loginResult == null) {
                    return;
                }
                loadingProgressBar.setVisibility(View.GONE);
                if (loginResult.getError() != null) {
                    //出现网络异常
                    showLoginFailed(loginResult.getError());
                    setResult(Activity.RESULT_CANCELED);
                }
                if (loginResult.getSuccess() != null) {
                    LoggedInUserView data=loginResult.getSuccess();
                    if(data.isOk()){
                        //密码正确
                        updateUiWithUser(data.getUid(),data.getToken());
                        setResult(Activity.RESULT_OK);
                        //Complete and destroy login activity once successful
                        finish();
                    }else {
                        //密码错误
                        Toast.makeText(getApplicationContext(), "password wrong", Toast.LENGTH_SHORT).show();
                    }
                }

            }
        });

        TextWatcher afterTextChangedListener = new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                // ignore
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                // ignore
            }

            @Override
            public void afterTextChanged(Editable s) {
                loginViewModel.loginDataChanged(usernameEditText.getText().toString(),
                        passwordEditText.getText().toString());
            }
        };
        usernameEditText.addTextChangedListener(afterTextChangedListener);
        passwordEditText.addTextChangedListener(afterTextChangedListener);
        passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {

            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if (actionId == EditorInfo.IME_ACTION_DONE) {
                    loginViewModel.login(usernameEditText.getText().toString(),
                            passwordEditText.getText().toString());
                }
                return false;
            }
        });

        loginButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                loadingProgressBar.setVisibility(View.VISIBLE);
                loginViewModel.login(usernameEditText.getText().toString(),
                        passwordEditText.getText().toString());
            }
        });
        signup_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                SignUpActivity.ActionStart(LoginActivity.this);
            }
        });
    }

    private void updateUiWithUser(String uid,String token) {
        //登录成功应该转至主界面,并传递登录的id
        BottomActivity.actionStart(LoginActivity.this,uid,token);
    }

    private void showLoginFailed(@StringRes Integer errorString) {
        Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_SHORT).show();
    }
}

(二)ViewModel类

public class LoginViewModel extends ViewModel {

    private MutableLiveData<LoginFormState> loginFormState = new MutableLiveData<>();
    private MutableLiveData<LoginResult> loginResult = new MutableLiveData<>();
    private LoginRepository loginRepository;

    LoginViewModel(LoginRepository loginRepository) {
        this.loginRepository = loginRepository;
    }

    LiveData<LoginFormState> getLoginFormState() {
        return loginFormState;
    }

    LiveData<LoginResult> getLoginResult() {
        return loginResult;
    }

    public void login(String username, String password) {
        // can be launched in a separate asynchronous job
        Result<LoggedInUser> result = loginRepository.login(username, password);

        if (result instanceof Result.Success) {
            LoggedInUser data = ((Result.Success<LoggedInUser>) result).getData();
            loginResult.setValue(new LoginResult(new LoggedInUserView(data.isOk(),data.getUserId(),data.getDisplayName(),data.getToken())));
        } else {
            loginResult.setValue(new LoginResult(R.string.login_failed));
        }
    }

    public void loginDataChanged(String username, String password) {
        if (!isUserNameValid(username)) {
            loginFormState.setValue(new LoginFormState(R.string.invalid_username, null));
        } else if (!isPasswordValid(password)) {
            loginFormState.setValue(new LoginFormState(null, R.string.invalid_password));
        } else {
            loginFormState.setValue(new LoginFormState(true));
        }
    }

    // A placeholder username validation check
    private boolean isUserNameValid(String username) {
        if (username == null) {
            return false;
        }
        return Patterns.PHONE.matcher(username).matches();
    }

    // A placeholder password validation check
    private boolean isPasswordValid(String password) {
        return password != null && password.trim().length() > 5;
    }
}

(三)其他类

public class LoginViewModelFactory implements ViewModelProvider.Factory {

    @NonNull
    @Override
    @SuppressWarnings("unchecked")
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        if (modelClass.isAssignableFrom(LoginViewModel.class)) {
            return (T) new LoginViewModel(LoginRepository.getInstance(new LoginDataSource()));
        } else {
            throw new IllegalArgumentException("Unknown ViewModel class");
        }
    }
}
class LoginResult {
    @Nullable
    private LoggedInUserView success;
    @Nullable
    private Integer error;

    LoginResult(@Nullable Integer error) {
        this.error = error;
    }

    LoginResult(@Nullable LoggedInUserView success) {
        this.success = success;
    }

    @Nullable
    LoggedInUserView getSuccess() {
        return success;
    }

    @Nullable
    Integer getError() {
        return error;
    }
}
class LoginFormState {
    @Nullable
    private Integer usernameError;
    @Nullable
    private Integer passwordError;
    private boolean isDataValid;

    LoginFormState(@Nullable Integer usernameError, @Nullable Integer passwordError) {
        this.usernameError = usernameError;
        this.passwordError = passwordError;
        this.isDataValid = false;
    }

    LoginFormState(boolean isDataValid) {
        this.usernameError = null;
        this.passwordError = null;
        this.isDataValid = isDataValid;
    }

    @Nullable
    Integer getUsernameError() {
        return usernameError;
    }

    @Nullable
    Integer getPasswordError() {
        return passwordError;
    }

    boolean isDataValid() {
        return isDataValid;
    }
}
class LoggedInUserView {
    private boolean isOk;
    private String uid;
    private String displayName;
    private String token;
    //... other data fields that may be accessible to the UI


    public LoggedInUserView(boolean isOk, String uid, String displayName, String token) {
        this.isOk = isOk;
        this.uid = uid;
        this.displayName = displayName;
        this.token = token;
    }

    public void setOk(boolean ok) {
        isOk = ok;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public void setDisplayName(String displayName) {
        this.displayName = displayName;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public LoggedInUserView(boolean isOk) {
        this.isOk = isOk;
    }

    public boolean isOk() {
        return isOk;
    }

    public String getDisplayName() {
        return displayName;
    }

    public String getUid() {
        return uid;
    }
}

十九、文件预览

1.Activity

public class FileActivity extends AppCompatActivity {
    //文件activity
    public static void ActionStart(Context context, FileMessage fileMessage){
        Intent intent=new Intent(context,FileActivity.class);
        Log.d("messageObject",fileMessage.getFileUrl().toString());
        intent.putExtra("fileMessage",fileMessage);
        context.startActivity(intent);
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_file);

        final TextView fileNameView=findViewById(R.id.fileName);
        final Button button=findViewById(R.id.other_button);
        final Button down_button=findViewById(R.id.download);
        final ProgressBar progressBar=findViewById(R.id.progressBar);
        Intent intent=getIntent();
        FileMessage fileMessage=intent.getParcelableExtra("fileMessage");
        String fileMessageName= fileMessage.getName();
        String saveDir="com.example.minichat/files";
        if(fileMessage.getLocalPath()==null){
            //表示不是本地文件
            File file=new File(Environment.getExternalStorageDirectory().getAbsolutePath(),saveDir+"/"+fileMessageName);
            Log.d("fileAbsolute",file.getAbsolutePath());
            if(!file.exists()){
                //未下载过
                down_button.setVisibility(View.VISIBLE);
                progressBar.setVisibility(View.VISIBLE);
                button.setVisibility(View.GONE);
            }else{
                down_button.setVisibility(View.GONE);
                progressBar.setVisibility(View.GONE);
                button.setVisibility(View.VISIBLE);
            }
        }else{
            Log.d("fileLocalPath",fileMessage.getLocalPath().toString());
            down_button.setVisibility(View.GONE);
            progressBar.setVisibility(View.GONE);
            button.setVisibility(View.VISIBLE);
        }


        down_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                        DownloadUtil.get().download(fileMessage.getFileUrl().toString(), saveDir, fileMessageName,new DownloadUtil.OnDownloadListener() {
                            @Override
                            public void onDownloadSuccess() {
                                FileActivity.this.runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        down_button.setVisibility(View.GONE);
                                        button.setVisibility(View.VISIBLE);
                                        progressBar.setVisibility(View.GONE);
                                    }
                                });

                            }
                            @Override
                            public void onDownloading(int progress) {
                                FileActivity.this.runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        progressBar.setProgress(progress);
                                        down_button.setText("下载中");
                                        down_button.setEnabled(false);
                                    }
                                });

                            }

                            @Override
                            public void onDownloadFailed() {
                                FileActivity.this.runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        Toast.makeText(FileActivity.this,"下载失败",Toast.LENGTH_LONG).show();
                                    }
                                });

                            }
                        });

            }
        });


        fileNameView.setText(fileMessage.getName());
        Log.d("fileMessageRemoteUrl",fileMessage.getFileUrl().toString());
        Log.d("fileType",fileMessage.getType());
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //用其他应用打开文件
                if(fileMessage.getLocalPath()!=null){
                    openFile(new File(fileMessage.getLocalPath().getPath()),FileActivity.this);
                }else {
                    openFile(new File(Environment.getExternalStorageDirectory().getAbsolutePath(),saveDir+"/"+fileMessageName),FileActivity.this);
                }
            }
        });
    }


    private void openFile(File file, Context mContext) {
        try {
            Intent intent = new Intent();
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            //设置intent的Action属性
            intent.setAction(Intent.ACTION_VIEW);
            //获取文件file的MIME类型
            String type = getMIMEType(file);
            Log.d("fielType",type);
            //设置intent的data和Type属性。android 7.0以上crash,改用provider
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                Uri fileUri = FileProvider.getUriForFile(mContext, mContext.getPackageName() + ".provider", file);//android 7.0以上
                intent.setDataAndType(fileUri, type);
                grantUriPermission(mContext, fileUri, intent);
            } else {
                intent.setDataAndType(/*uri*/Uri.fromFile(file), type);
            }
            //跳转
            mContext.startActivity(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }



    private static void grantUriPermission(Context context, Uri fileUri, Intent intent) {
        List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            String packageName = resolveInfo.activityInfo.packageName;
            context.grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
    }

    /**
     * 根据文件后缀名获得对应的MIME类型。
     *
     * @param file
     */
    private static String getMIMEType(File file) {

        String type = "*/*";
        String fName = file.getName();
        //获取后缀名前的分隔符"."在fName中的位置。
        int dotIndex = fName.lastIndexOf(".");
        if (dotIndex < 0) {
            return type;
        }
        /* 获取文件的后缀名 */
        String end = fName.substring(dotIndex, fName.length()).toLowerCase();
        if (end == "") return type;
        //在MIME和文件类型的匹配表中找到对应的MIME类型。
        for (int i = 0; i < MIME_MapTable.length; i++) { //MIME_MapTable??在这里你一定有疑问,这个MIME_MapTable是什么?
            if (end.equals(MIME_MapTable[i][0]))
                type = MIME_MapTable[i][1];
        }
        return type;
    }


    private static final String[][] MIME_MapTable = {
            //{后缀名, MIME类型}
            {".3gp", "video/3gpp"},
            {".apk", "application/vnd.android.package-archive"},
            {".asf", "video/x-ms-asf"},
            {".avi", "video/x-msvideo"},
            {".bin", "application/octet-stream"},
            {".bmp", "image/bmp"},
            {".c", "text/plain"},
            {".class", "application/octet-stream"},
            {".conf", "text/plain"},
            {".cpp", "text/plain"},
            {".doc", "application/msword"},
            {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
            {".xls", "application/vnd.ms-excel"},
            {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
            {".exe", "application/octet-stream"},
            {".gif", "image/gif"},
            {".gtar", "application/x-gtar"},
            {".gz", "application/x-gzip"},
            {".h", "text/plain"},
            {".htm", "text/html"},
            {".html", "text/html"},
            {".jar", "application/java-archive"},
            {".java", "text/plain"},
            {".jpeg", "image/jpeg"},
            {".jpg", "image/jpeg"},
            {".js", "application/x-javascript"},
            {".log", "text/plain"},
            {".m3u", "audio/x-mpegurl"},
            {".m4a", "audio/mp4a-latm"},
            {".m4b", "audio/mp4a-latm"},
            {".m4p", "audio/mp4a-latm"},
            {".m4u", "video/vnd.mpegurl"},
            {".m4v", "video/x-m4v"},
            {".mov", "video/quicktime"},
            {".mp2", "audio/x-mpeg"},
            {".mp3", "audio/x-mpeg"},
            {".mp4", "video/mp4"},
            {".mpc", "application/vnd.mpohun.certificate"},
            {".mpe", "video/mpeg"},
            {".mpeg", "video/mpeg"},
            {".mpg", "video/mpeg"},
            {".mpg4", "video/mp4"},
            {".mpga", "audio/mpeg"},
            {".msg", "application/vnd.ms-outlook"},
            {".ogg", "audio/ogg"},
            {".pdf", "application/pdf"},
            {".png", "image/png"},
            {".pps", "application/vnd.ms-powerpoint"},
            {".ppt", "application/vnd.ms-powerpoint"},
            {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
            {".prop", "text/plain"},
            {".rc", "text/plain"},
            {".rmvb", "audio/x-pn-realaudio"},
            {".rtf", "application/rtf"},
            {".sh", "text/plain"},
            {".tar", "application/x-tar"},
            {".tgz", "application/x-compressed"},
            {".txt", "text/plain"},
            {".wav", "audio/x-wav"},
            {".wma", "audio/x-ms-wma"},
            {".wmv", "audio/x-ms-wmv"},
            {".wps", "application/vnd.ms-works"},
            {".xml", "text/plain"},
            {".z", "application/x-compress"},
            {".zip", "application/x-zip-compressed"},
            {"", "*/*"}
    };

}

App数据源处理

二十、基本构成介绍

当前端需要调动后端执行一个动作时,前端ui将动作执行委托给viewmodel,viewmodel调用respository中执行,在repository中根据情况选择从本地存储读取还是从网络读取,一般情况下优先选择存储读取。repository应该是单例的,以免造成存在多个repository存在,造成本地数据库与网络数据混淆,数据库数据不一致。
在repository对于本地数据和网络数据的处理最好的做法是在repository设置读取本地数据库方法或者委托给其他类执行数据库读取如Dao,然后在repository设置一个方法,由该方法决定从datasource(remote数据源即网络数据源)还是从本地数据库读取,我们最好不要在datasource类中设置数据的方法。
如:一个聊天应用中,界面显示个人信息,我们需要的个人信息相关数据,一般情况下,不会更新,因此选择优先选择从数据库读取是个更好的选择。当ui需要数据时,viewmodel可调用getfriends方法,viewmodel委托给repository执行,repository选择本地数据库还调用datasource类执行。
repository如何判断是最新消息呢?我们可以在本地保留一个数据版本号,当读取数据时应向服务器发送最新版本号,当服务器发现版本号不同时,服务器返回最新数据和最新的版本号。根据返回情况,我们选择更新本地数据库,还是直接读取本地数据库。我们要使用一个较短时间能完成的响应尽可能的替代较长时间完成的响应。通过网络获取大量数据是极为耗时的,而且还可能在网络不好的情况下造成数据丢失,造成严重后果,因此我们应尽可能地减少这种操作。我们这样即可以减少服务器的负担,也能给用户更好的体验。
至于上面的代码实现,还请小伙伴们自己思考。
下面是我的mini聊天app中部分datasource与datarepository的实现。

二十一、datasource与repository

(一)朋友

1.FriendAddDataSource

public class FriendAddDataSource {
    Result<Friend> addFriend(String uid,String f_id){
        //net
        try {
            FriendAddResponse response = new FriendHpImpl().addFriend(uid, f_id);
            if(response.getCode()>0){
                Friend friend=new Friend(response.getId(),uid,response.getName(),null);
                return new Result.Success<>(friend);
            }else {
                return new Result.Error(new Exception("had added"));
            }
        }catch (HpException hpException){
            return new Result.Error(new Exception("NetService wsa shut down"));
        }

    }
}

2. FriendAddRepository

public class FriendAddRepository {
    private static volatile FriendAddRepository instance;
    private FriendAddDataSource dataSource;

    public static FriendAddRepository getInstance(FriendAddDataSource source) {
        if(instance==null){
            instance=new FriendAddRepository(source);
        }
        return instance;
    }

    public FriendAddRepository(FriendAddDataSource dataSource) {
        this.dataSource = dataSource;
    }

    public void setFriend(Friend friend){
        //load into local storage
        FriendRoom friendRoom=new FriendRoom(friend.getFid(),friend.getUid(),friend.getF_name(),friend.getNick_name());
        AsyncTask.execute(new Runnable() {
            @Override
            public void run() {
                APPDataBase db= App.getInstance().getDataBase();
                db.friendDao().insertFriendOne(friendRoom).blockingSubscribe();
            }
        });

    }
    public Result<Friend> addFriend(String uid,String f_id){
        Result<Friend> friendResult=dataSource.addFriend(uid,f_id);
        if(friendResult instanceof Result.Success){
            setFriend(((Result.Success<Friend>) friendResult).getData());
        }
        return friendResult;
    }
}

3.FriendDataSource

public class FriendDataSource {
    private LiveData<List<FriendRoom>> friendsLiveData;
    private final FriendDao dao;
    public FriendDataSource() {
        dao= App.getInstance().getDataBase().friendDao();
        //friendsLiveData=dao.getFriendsByUId(uid);
    }
    public Result<List<Friend>> getFriends(String uid){
        List<Friend> friendList=new ArrayList<>();
        //if (friendsLiveData==null||friendsLiveData.getValue()==null){
            List<FriendRoom> friendRoomList=new ArrayList<>();
            //remote
            try{
                List<FriendResponse> responseList=new FriendHpImpl().getFriendsById(uid);
                for(FriendResponse response:responseList){
                    if (response.getCode()>0){
                        friendList.add(new Friend(response.getFid(),response.getUid(),response.getF_name(),response.getNick_name()));
                        friendRoomList.add(new FriendRoom(response.getFid(),response.getUid(),response.getF_name(),response.getNick_name()));
                    }else {
                        return new Result.Error(new IOException("Error fetching friends"));
                    }
                }
                //load into room
                AsyncTask.execute(new Runnable() {
                    @Override
                    public void run() {
                        if(friendRoomList.size()>0){
                            dao.insertFriends(friendRoomList).blockingSubscribe();
                            friendsLiveData=dao.getFriendsByUId(uid);
                        }
                    }
                });
            }catch (HpException hp){
                return new Result.Error(new Exception("NetService was shut down."));
            }
        for (Friend f:friendList
             ) {
            Log.d("friendListItme",f.toString());
        }
//        }else {
//            //room
//            List<FriendRoom> friendRoomList=friendsLiveData.getValue();
//            for (FriendRoom friendRoom:friendRoomList){
//                friendList.add(new Friend(friendRoom.fid,friendRoom.uid,friendRoom.f_name,friendRoom.nick_name));
//            }
//        }
        return new Result.Success<>(friendList);
    }

    public LiveData<List<FriendRoom>> getFriendsLiveData() {
        return friendsLiveData;
    }
}

4.FriendRepository

public class FriendRepository {
    private static volatile FriendRepository instance;
    private FriendDataSource dataSource;

    public static FriendRepository getInstance(FriendDataSource dataSource) {
        if(instance==null){
            instance=new FriendRepository(dataSource);
        }
        return instance;
    }
    private FriendRepository(FriendDataSource dataSource){
        this.dataSource=dataSource;
    }

    public Result<List<Friend>> getFriends(String uid){
        return dataSource.getFriends(uid);
    }
    public LiveData<List<FriendRoom>> getFriendLiveData(){
        return dataSource.getFriendsLiveData();
    }
}

(二)个人主页

1.datasource

public class HomeDataSource {
    public Result<User> getUser(){
        //访问数据库,并返回信息
        APPDataBase appDataBase= App.getInstance().getDataBase();
        Single<UserRoom> userSingle=appDataBase.userDao().getLogUser();
        try{
            UserRoom u=userSingle.blockingGet();
            User user=new User(u.uid,u.userName);
            return new Result.Success<>(user);
        }catch (NullPointerException e){
            return new Result.Error(new IOException("Error getUser in",e));
        }
    }
}

2.repository

public class HomeDataSource {
    public Result<User> getUser(){
        //访问数据库,并返回信息
        APPDataBase appDataBase= App.getInstance().getDataBase();
        Single<UserRoom> userSingle=appDataBase.userDao().getLogUser();
        try{
            UserRoom u=userSingle.blockingGet();
            User user=new User(u.uid,u.userName);
            return new Result.Success<>(user);
        }catch (NullPointerException e){
            return new Result.Error(new IOException("Error getUser in",e));
        }
    }
}

(三)登录

1. datasource

public class LoginDataSource {

    public Result<LoggedInUser> login(String uid, String password) {

        try {
            // 网络登录操作
            LoginResponse response=new LoginHpImpl().login(uid,password);
            LoggedInUser user=null;
            if(response.getCode()>0){
                user =
                        new LoggedInUser(true,
                                uid,response.getName(),response.getToken()
                        );
            }else {
                user =
                        new LoggedInUser(false,
                                null,null,null
                        );
            }
            return new Result.Success<>(user);
        } catch (Exception e) {
            return new Result.Error(new IOException("Error logging in", e));
        }
    }

}

2.respository

public class LoginDataSource {

    public Result<LoggedInUser> login(String uid, String password) {

        try {
            // 网络登录操作
            LoginResponse response=new LoginHpImpl().login(uid,password);
            LoggedInUser user=null;
            if(response.getCode()>0){
                user =
                        new LoggedInUser(true,
                                uid,response.getName(),response.getToken()
                        );
            }else {
                user =
                        new LoggedInUser(false,
                                null,null,null
                        );
            }
            return new Result.Success<>(user);
        } catch (Exception e) {
            return new Result.Error(new IOException("Error logging in", e));
        }
    }

}

二十二、model

public class Friend {
    String fid;
    String uid;
    String f_name;
    String nick_name;

    public Friend(String fid, String uid, String f_name, String nick_name) {
        this.fid = fid;
        this.uid = uid;
        this.f_name = f_name;
        this.nick_name = nick_name;
    }

    public String getFid() {
        return fid;
    }

    public void setFid(String fid) {
        this.fid = fid;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getF_name() {
        return f_name;
    }

    public void setF_name(String f_name) {
        this.f_name = f_name;
    }

    public String getNick_name() {
        return nick_name;
    }

    public void setNick_name(String nick_name) {
        this.nick_name = nick_name;
    }

    @Override
    public String toString() {
        return "Friend{" +
                "fid='" + fid + '\'' +
                ", uid='" + uid + '\'' +
                ", f_name='" + f_name + '\'' +
                ", nick_name='" + nick_name + '\'' +
                '}';
    }
}
public class LoggedInUser {

    private boolean isOk;
    private String userId;
    private String displayName;
    private String token;

    public LoggedInUser(boolean isOk, String userId, String displayName, String token) {
        this.isOk = isOk;
        this.userId = userId;
        this.displayName = displayName;
        this.token = token;
    }

    public void setOk(boolean ok) {
        isOk = ok;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public void setDisplayName(String displayName) {
        this.displayName = displayName;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public LoggedInUser(boolean isOk) {
        this.isOk = isOk;
    }

    public String getUserId() {
        return userId;
    }

    public String getDisplayName() {
        return displayName;
    }

    public boolean isOk() {
        return isOk;
    }
}
public class Message {
    String m_id;
    String r_id;
    String s_id;
    String name;
    String time;
    String content;


    public String getR_id() {
        return r_id;
    }

    public void setR_id(String r_id) {
        this.r_id = r_id;
    }

    public String getS_id() {
        return s_id;
    }

    public void setS_id(String s_id) {
        this.s_id = s_id;
    }

    public String getM_id() {
        return m_id;
    }

    public void setM_id(String m_id) {
        this.m_id = m_id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

    public String getContent() {
        return content;
    }

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

public class User {
    String uid;
    String name;

    public User(String uid, String name) {
        this.uid = uid;
        this.name = name;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class Result<T> {
    // hide the private constructor to limit subclass types (Success, Error)
    private Result() {
    }

    @Override
    public String toString() {
        if (this instanceof Result.Success) {
            Result.Success success = (Result.Success) this;
            return "Success[data=" + success.getData().toString() + "]";
        } else if (this instanceof Result.Error) {
            Result.Error error = (Result.Error) this;
            return "Error[exception=" + error.getError().toString() + "]";
        }
        return "";
    }

    // Success sub-class
    public final static class Success<T> extends Result {
        private T data;

        public Success(T data) {
            this.data = data;
        }

        public T getData() {
            return this.data;
        }
    }

    // Error sub-class
    public final static class Error extends Result {
        private Exception error;

        public Error(Exception error) {
            this.error = error;
        }

        public Exception getError() {
            return this.error;
        }
    }
}

AppServer构建

二十三、项目结构

在这里插入图片描述

二十四、数据库连接与dao的实现

1.context.xml

第一,在web 目录下创建META-INF目录,在目录下创建文件context.xml
地址构成
jdbc:mysql://用户名@ip:端口/模式名?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
第二,WEB-INF目录下创建目录lib
加入gson与mysqljava包

<?xml version="1.0" encoding="utf-8"?>
<Context reloadable="true">
    <Resource
        name="jdbc/minichat"
        type="javax.sql.DataSource"
        maxTotal="100"
        maxIdle="50"
        driverClassName="com.mysql.cj.jdbc.Driver"
        url="jdbc:mysql://minichat@localhost:3306/mini_chat?serverTimezone=GMT%2B8&amp;useUnicode=true&amp;characterEncoding=utf-8"
        userName="minichat"
        password="minichat"
        maxWaitMillis="5000"/>
</Context>

2.数据库 Dao

所有的实体dao都应该继承于Dao接口

public interface Dao {
    static DataSource getDataSource(){
        DataSource dataSource=null;
        try{
            Context context=new InitialContext();
            dataSource=(DataSource)context.lookup("java:comp/env/jdbc/minichat");
        }catch (NamingException ne){
            ne.printStackTrace();
        }
        return dataSource;
    }
    default Connection getConnection() throws DaoException{
        DataSource dataSource=getDataSource();
        Connection connection=null;
        try{
            connection=dataSource.getConnection();

        }catch (SQLException se){
            se.printStackTrace();
        }
        return connection;
    }
}

DaoException

public class DaoException extends Exception implements Serializable {
    private static final long serialVersionUID=19192L;
    private String message;
    public DaoException(){}
    public DaoException(String message){
        this.message=message;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return "DaoException{" +
                "message='" + message + '\'' +
                '}';
    }
}

3.Dao

public interface FriendDao extends Dao {
    List<FriendResponse> getFriendsById(String uid);
    FriendAddResponse addFriend(String uid,String fid);
}
public interface UserDao extends Dao {
    boolean signup(String uid,String uname,String password) throws DaoException;
    LoginResponse login(String uid,String password);
}

4.DaoImpl

public class FriendDaoImpl implements FriendDao {
    @Override
    public List<FriendResponse> getFriendsById(String uid) {
        List<FriendResponse> friendResponses=new ArrayList<>();
        System.out.println(uid);
        try(Connection connection=getConnection()){
            PreparedStatement pst=connection.prepareStatement("select * from friend where uid=?");
            pst.setString(1,uid);
            ResultSet rst=pst.executeQuery();
            while (rst.next()){
                friendResponses.add(new FriendResponse(1,null,rst.getString("fid"),
                        uid,rst.getString("f_name"),rst.getString("nick_name")));
            }
        } catch (SQLException sqlException) {
            sqlException.printStackTrace();
        } catch (DaoException e) {
            e.printStackTrace();
        }
        return friendResponses;
    }

    @Override
    public FriendAddResponse addFriend(String uid, String fid) {
        FriendAddResponse response=new FriendAddResponse(-1,null);
        try(Connection connection=getConnection()){
            String f_name=null;
            String uname=null;
            PreparedStatement pst=connection.prepareStatement("select uname from user where uid=?");
            pst.setString(1,fid);
            ResultSet rst=pst.executeQuery();
            if(rst.next()){
                f_name=rst.getString("uname");
            }else {
                response.setMessage("id 不存在");
            }
            pst.setString(1,uid);
            rst=pst.executeQuery();
            if(rst.next()){
                uname=rst.getString("uname");
            }else {
                response.setMessage("id 不存在");
            }
            if(f_name!=null&&uname!=null){
                PreparedStatement pst2=connection.prepareStatement("insert into friend(fid,uid,f_name) VALUES (?,?,?)");
                pst2.setString(1,fid);
                pst2.setString(2,uid);
                pst2.setString(3,f_name);
                int result0=pst2.executeUpdate();
                if(result0>0){
                    pst2.setString(1,uid);
                    pst2.setString(2,fid);
                    pst2.setString(3,uname);
                    int result1=pst2.executeUpdate();
                    if(result1>0){
                        response.setCode(1);
                        response.setMessage("add friend success");
                        response.setId(fid);
                        response.setName(f_name);
                    }else {
                        response.setMessage("已添加");
                    }
                }else {
                    response.setMessage("已添加");
                }
            }

        } catch (SQLException | DaoException sqlException) {
            sqlException.printStackTrace();
        }
        return response;
    }
}
public class UserDaoImpl implements UserDao {
    @Override
    public boolean signup(String uid, String uname, String password) throws DaoException{
        try(Connection connection=getConnection()){
            PreparedStatement pst=connection.prepareStatement("insert into user(uid,uname,password) values (?,?,?)");
            pst.setString(1,uid);
            pst.setString(2,uname);
            pst.setString(3,password);
            int result=pst.executeUpdate();
            return result>0;
        } catch (SQLException sqlException) {
            sqlException.printStackTrace();
        } catch (DaoException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public LoginResponse login(String uid, String password)  {
        System.out.println(uid+password);
        LoginResponse loginResponse=new LoginResponse();
        loginResponse.setCode(-1);
        try(Connection connection=getConnection()){
            PreparedStatement pst=connection.prepareStatement("select * from user where uid=?");
            pst.setString(1,uid);
            ResultSet rst=pst.executeQuery();
            while (rst.next()){
                if(rst.getString("password").equals(password)){
                    loginResponse.setCode(1);
                    loginResponse.setMessage("login ok");
                    loginResponse.setUid(uid);
                    loginResponse.setName(rst.getString("uname"));
                }else {
                    loginResponse.setCode(-1);
                    loginResponse.setMessage("login in password failed");
                }
            }
        }catch (DaoException daoException){
            daoException.printStackTrace();
            loginResponse.setMessage("login in dao failed");
        } catch (SQLException sqlException) {
            sqlException.printStackTrace();
            loginResponse.setMessage("login in sql failed");
        }
        return loginResponse;
    }
}

5.model

(1)Message
public class Message implements Serializable {
    //@param: code 1成功 0未初始化 -1错误
    //@param: message 错误信息提示或其他信息
    private int code;
    private String message;

    public Message() {
        code=0;
        message=null;
    }

    public Message(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

(2)reuqest

public class FriendAddRequest {
    String uid;
    String f_id;

    public FriendAddRequest(String uid, String f_id) {
        this.uid = uid;
        this.f_id = f_id;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getF_id() {
        return f_id;
    }

    public void setF_id(String f_id) {
        this.f_id = f_id;
    }
}
public class FriendRequest {
    private String uid;


    public FriendRequest(String uid) {
        this.uid = uid;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }
}

public class LoginRequest {
    private String uid;
    private String password;
    public LoginRequest(String uid,String password) {
        this.uid=uid;
        this.password=password;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
public class SignUpRequest {
    String uid;
    String uname;
    String password;

    public SignUpRequest(String uid, String uname, String password) {
        this.uid = uid;
        this.uname = uname;
        this.password = password;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getUname() {
        return uname;
    }

    public void setUname(String uname) {
        this.uname = uname;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

(3)response

public class FriendAddResponse extends Message {
    String id;
    String name;
    public FriendAddResponse() {
    }

    public FriendAddResponse(int code, String message) {
        super(code, message);
    }

    public FriendAddResponse(int code, String message, String id, String name) {
        super(code, message);
        this.id = id;
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class FriendResponse extends Message {
    String fid;
    String uid;
    String f_name;
    String nick_name;

    public FriendResponse(int code, String message, String fid, String uid, String f_name, String nick_name) {
        super(code, message);
        this.fid = fid;
        this.uid = uid;
        this.f_name = f_name;
        this.nick_name = nick_name;
    }

    public FriendResponse(int code, String message) {
        super(code, message);
    }

    public String getFid() {
        return fid;
    }

    public void setFid(String fid) {
        this.fid = fid;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getF_name() {
        return f_name;
    }

    public void setF_name(String f_name) {
        this.f_name = f_name;
    }

    public String getNick_name() {
        return nick_name;
    }

    public void setNick_name(String nick_name) {
        this.nick_name = nick_name;
    }
}

public class LoginResponse extends Message {

    private String uid;
    private String name;
    public LoginResponse() {
    }

    public LoginResponse(int code, String message) {
        super(code, message);
    }

    public LoginResponse(int code, String message, String uid, String name) {
        super(code, message);
        this.uid = uid;
        this.name = name;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "LoginResponse{" +
                "uid='" + uid + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

二十五、servelet

@WebServlet(name = "FriendsGetServlet",urlPatterns = {"/getFriends"})
public class FriendsGetServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        FriendRequest friendRequest=new Gson().fromJson(request.getReader(),FriendRequest.class);
        List<FriendResponse> friendResponseList=new FriendDaoImpl().getFriendsById(friendRequest.getUid());
        Type listType=new TypeToken<List<FriendResponse>>(){}.getType();
        String rp=new Gson().toJson(friendResponseList,listType);
        response.setContentType("txt/html;charset=utf-8");
        PrintWriter writer=response.getWriter();
        writer.print(rp);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String uid=request.getParameter("uid");
        List<FriendResponse> friendResponseList=new FriendDaoImpl().getFriendsById(uid);
        Type listType=new TypeToken<List<FriendResponse>>(){}.getType();
        String rp=new Gson().toJson(friendResponseList,listType);
        response.setContentType("txt/html;charset=utf-8");
        PrintWriter writer=response.getWriter();
        writer.print(rp);
    }
}
@WebServlet(name = "FriendAddServlet",urlPatterns ={"/addFriend"})
public class FriendAddServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        FriendAddRequest friendAddRequest=new Gson().fromJson(request.getReader(),FriendAddRequest.class);
        FriendAddResponse friendAddResponse=new FriendDaoImpl().addFriend(friendAddRequest.getUid(),friendAddRequest.getF_id());
        String rp=new Gson().toJson(friendAddResponse);
        response.setContentType("txt/html;charset=utf-8");
        PrintWriter writer=response.getWriter();
        writer.print(rp);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}
	@WebServlet(name = "LoginServlet",urlPatterns = {"/login"})
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        LoginRequest loginRequest=new Gson().fromJson(request.getReader(),LoginRequest.class);
        LoginResponse loginResponse=new UserDaoImpl().login(loginRequest.getUid(),loginRequest.getPassword());
        System.out.println(loginRequest.getUid()+" ask for login {password:"+loginRequest.getPassword()+"}");
        System.out.println(loginResponse.getUid()+" code:"+loginResponse.getCode());
        String rp=new Gson().toJson(loginResponse);
        response.setContentType("txt/html;charset=utf-8");
        PrintWriter writer=response.getWriter();
        writer.print(rp);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}
@WebServlet(name = "SignUpServlet",urlPatterns = {"/signup"})
public class SignUpServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("signupservlet start");
        SignUpRequest signUpRequest=new Gson().fromJson(request.getReader(),SignUpRequest.class);

        Message message=new Message();
        try{
            boolean isOk=new UserDaoImpl().signup(signUpRequest.getUid(),signUpRequest.getUname(),signUpRequest.getPassword());
            if(isOk){
                message.setCode(1);
                message.setMessage("sign up success");
            }else {
                message.setCode(-1);
                message.setMessage("sign up failed");
            }
        }catch (DaoException e){
            message.setCode(-1);
            message.setMessage("sign up failed");
        }
        String rp=new Gson().toJson(message);
        System.out.println(rp);
        response.setContentType("txt/html;charset=utf-8");
        PrintWriter writer=response.getWriter();
        writer.print(rp);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

树莓派4B与Android之缘——Android应用MiniChat聊天软件 的相关文章

  • 应用程序未安装在 Android 模拟器上

    我正在 android Geocoder 中开发一个应用程序 当我运行该应用程序时 它会显示 2011 01 11 11 08 13 GeoTourProject 自动目标模式 使用现有模拟器 emulator 5554 运行兼容的 AVD
  • minHeight 有什么作用吗?

    在附图中 我希望按钮列与图像的高度相匹配 但我也希望按钮列有一个最小高度 它正确匹配图像的高度 但不遵守 minHeight 并且会使按钮向下滑动 我正在为按钮列设置这些属性
  • 迁移到 java 17 后有关“每个进程的内存映射”和 JVM 崩溃的 GC 警告

    我们正在将 java 8 应用程序迁移到 java 17 并将 GC 从G1GC to ZGC 我们的应用程序作为容器运行 这两个基础映像之间的唯一区别是 java 的版本 例如对于 java 17 版本 FROM ubuntu 20 04
  • Flutter 深度链接

    据Flutter官方介绍深层链接页面 https flutter dev docs development ui navigation deep linking 我们不需要任何插件或本机 Android iOS 代码来处理深层链接 但它并没
  • 序列化对象以进行单元测试

    假设在单元测试中我需要一个对象 其中所有 50 个字段都设置了一些值 我不想手动设置所有这些字段 因为这需要时间而且很烦人 不知何故 我需要获得一个实例 其中所有字段都由一些非空值初始化 我有一个想法 如果我要调试一些代码 在某个时候我会得
  • 在具有相同属性名称的不同数据类型上使用 ModelMapper

    我有两节课说Animal AnimalDto我想用ModelMapper将 Entity 转换为 DTO 反之亦然 但是对于具有相似名称的一些属性 这些类应该具有不同的数据类型 我该如何实现这一目标 动物 java public class
  • Android构建apk:控制MANIFEST.MF

    Android 构建 APK 假设一个 apk 包含一个库 jar 例如 foo jar 该库具有 META INF MANIFEST MF 这对于它的运行很重要 但在APK中有一个包含签名数据的MANIFEST MF 并且lib jar
  • 调节麦克风录音音量

    我们正在尝试调整录音时的音量级别 麦克风似乎非常敏感 会接收到很多静电 我们查看了 setVolumeControlStream 但找不到传入其中来控制麦克风的流 将您的音频源设置为 MIC using MediaRecorder Audi
  • 如何创建像谷歌位置历史记录一样的Android时间轴视图?

    我想设计像谷歌位置历史这样的用户界面 我必须为我正在使用的应用程序复制此 UIRecyclerView 每行都是水平的LinearLayout其中包含右侧的图标 线条和视图 该线是一个FrameLayout具有圆形背景和半透明圆圈Views
  • org.jdesktop.application 包不存在

    几天以来我一直在构建一个 Java 桌面应用程序 一切都很顺利 但是今天 当我打开Netbeans并编译文件时 出现以下编译错误 Compiling 9 source files to C Documents and Settings Ad
  • 将多模块 Maven 项目导入 Eclipse 时出现问题 (STS 2.5.2)

    我刚刚花了最后一个小时查看 Stackoverflow com 上的线程 尝试将 Maven 项目导入到 Spring ToolSuite 2 5 2 中 Maven 项目有多个模块 当我使用 STS 中的 Import 向导导入项目时 所
  • 当手机旋转(方向改变)时如何最好地重新创建标记/折线

    背景 开发一个使用 Android Google Map v2 的本机 Android 应用程序 使用android support v4 app FragmentActivity 在 Android v2 2 上运行 客观的 在更改手机方
  • Android 如何聚焦当前位置

    您好 我有一个 Android 应用程序 可以在谷歌地图上找到您的位置 但是当我启动该应用程序时 它从非洲开始 而不是在我当前的城市 国家 位置等 我已经在developer android com上检查了信息与位置问题有关 但问题仍然存在
  • 用于推送通知的设备令牌

    我正在实施推送通知服务 我需要创建一个数据库来存储 4 个移动平台的所有设备令牌 我想根据他们的平台 iOS Android BlackBerry WP7 来组织它们 但是有什么方法可以区分平台 这样如果我只想向 Android 用户发送消
  • 将 JTextArea 内容写入文件

    我在 Java Swing 中有一个 JTextArea 和一个 提交 按钮 需要将textarea的内容写入一个带有换行符的文件中 我得到的输出是这样的 它被写为文件中的一个字符串 try BufferedWriter fileOut n
  • 将2-3-4树转换为红黑树

    我正在尝试将 2 3 4 树转换为 java 中的红黑树 但我无法弄清楚它 我将这两个基本类编写如下 以使问题简单明了 但不知道从这里到哪里去 public class TwoThreeFour
  • 休眠以持久保存日期

    有没有办法告诉 Hibernate java util Date 应该持久保存 我需要这个来解决 MySQL 中缺少的毫秒分辨率问题 您能想到这种方法有什么缺点吗 您可以自己创建字段long 或者使用自定义的UserType 实施后User
  • java迭代器内部是如何工作的? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我有一个员工列表 List
  • 如何将图像从 Android 应用程序上传到网络服务器的特定文件夹中

    如何将图像从 android 移动到 Web 服务器上的指定文件夹 这是我的安卓代码 package com example bitmaptest import java io ByteArrayOutputStream import ja
  • 中断连接套接字

    我有一个 GUI 其中包含要连接的服务器列表 如果用户单击服务器 则会连接到该服务器 如果用户单击第二个服务器 它将断开第一个服务器的连接并连接到第二个服务器 每个新连接都在一个新线程中运行 以便程序可以执行其他任务 但是 如果用户在第一个

随机推荐

  • 后缀自动机(SAM)——黑盒使用方案

    首先讲下后缀自动机吧 会写一下部分必要的原理 其他的原理不做解释 代码未讲解的部分希望能当做黑盒来使用 既不了解具体原理但知道其性质以及如何使用 我实在是佩服发明出AC自动机 回文自动机 后缀自动机这人 前置知识 AC自动机中的Fail树
  • 如何使用Chrome浏览器模拟弱网情况

    点击谷歌浏览器图标 打开浏览器后 按下F12键 弹出开发者工具窗口 刷新网页 页面的加载速度为597ms 在开发者工具中 点击Online 在弹出的菜单中点击Slow 3G 慢速3G网络 重新加载网站 发现页面的加载速度变慢了 变成6 5s
  • openssl engine在tls中的应用

    openssl engine的实现和原理在上一篇文章 https blog csdn net liu942947766 article details 128837041 spm 1001 2014 3001 5502 openssl en
  • MATLAB随机生成m个三维坐标点,且各个坐标点之间的距离不小于n

    randi函数 randi max m n 生成均匀分布的随机整数 max生成的随机整数最大值 生成m行n列的矩阵 编写函数sampling function x y z sampling lowx upx lowy upy lowz up
  • 第五篇:进阶篇 发动机的噪声特性

    本专栏分享传统NVH知识点 从声学理论 材料声学 汽车噪声振动分析 车辆及其零部件甚至原材料的声学测试方法等多维度介绍汽车NVH 一些专用术语同时给出了中英文对照 欢迎新人 同行 爱好者一起交流 由于内容写的较为仓促 有误的地方欢迎大家批评
  • JS优化方法(使用最新的js方法)

    1 带有多个条件的if语句 将多个值放在一个数组中 然后调用数组的includes方法 longhand 直接的 if x abc x def x ghi x jkl logic 逻辑 shorthand 速记 if abc def ghi
  • 【FFmpeg】 音视频解码详细流程

    目录 一 视频解码流程 二 FFMPEG解码流程 三 FFmpeg解码函数 四 FFmpeg解码的数据结构 五 FFmpeg数据结构简介 六 FFmpeg数据结构分析 七 像素数据转换 八 FFMPEG解码 九 FFMPEG解码 视频播放
  • Donation-树形dp-建图

    题目网址 链接 int head maxn int n m cnt tot ll a maxn b maxn c maxn id maxn int fa maxn int lson maxn rson maxn struct node in
  • 用单片机C语言精确延时(定时)的方法

    用单片机C语言精确延时 定时 的方法 作者 51hei 来源 原创 点击数 更新时间 2009年09月29日 字体 大 中 小 最近在忙着单片机的项目 偶尔停下来小结了一下最近的收获 还是有不少可贵的收益的 本人在闲暇的时候对单片机C语言下
  • 显示本地openssl支持的加密算法

    在命令行中输入命令 openssl list ciper algorithms 运行后即刻显示支持的加密算法 END
  • [2022CISCN]初赛 复现

    ez usb 刚开始直接提取usb键盘流量 发现导出有问题 键盘流量 搜索8个字节长度的数据包 这里也能发现版本有2 8 1和2 10 1两种 因此猜测需要分别导出 tshark r ez usb pcapng Y usb data len
  • 16. GD32F103C8T6入门教程-adc 使用教程2-dma+连续扫描方式采集数据

    adc 使用教程2 dma 连续扫描方式采集数据 adc 的扫描模式就是把配置了规则或注入通道按照配置的顺序采集一轮 adc 的连续转换模式就是把配置了规则或注入通道按照配置的顺序采集N轮 注意 dma使用时存在一个外设映射到一个dam外设
  • css动画改变高度有过渡效果,css3-形变、过渡、动画

    一 2D形变 平移 transform translate x y 相对当前位置 向左移动x像素 像下移动y像素 transform translateX num 相对当前位置 向左移动num像素 transform translateY
  • OpenGLES从2.0到3.0的变化

    1 在着色器文件中添加 version 300 es 表明使用3 0版本 如果不添加则使用默认2 0版本 注意使用opengles3 0的API的时候必须添加 version 300 es 2 GLES 3 0中将GLES 2 0的 att
  • 两个栈来实现一个队列的C++代码

    利用两个栈来实现一个队列 这个问题很常见 最关键的是要有好的思路 至于实现 那是很简单的事情了 在本文中 也想说说自己的思路 但是 我觉得用代码来表述思路更符合我的习惯 也是我的菜 所以 仅仅给出代码 如有需要 大家可以根据代码来理解思路
  • Oracle 11g R2静默安装

    2015年1月6日 测试安装Oracle 11g R2静默安装安装 环境是vmware平台虚拟机 做个记录 CentOS 6 5 x64安装Oracle 11g R2 一 下载地址 http www oracle com technetwo
  • Java实现将数字转换成中文大写

    程序功能 支持将仟亿数字转换成中文数字表示 如 110 12 转换成壹佰壹拾元壹角贰分 算法思路 将数字 分成整数部分和小数部分 小数部分四舍五入到两位 分别进行转换 然后将转换后的结果合并后 生成最终结果 转换过程关键怎么处理中间的零 以
  • 索引2

    索引和全文索引 索引的目的是提高性能 索引提供了一种基于一列或多列的值对表的数据行进行快速访问的方法 索引提供的是表为数据 的逻辑顺序 规划合理的索引能够减少访问所需的时间 从而大大提高数据库的性能 索引 索引为性能带来的好处却是有代价的
  • R手册(Parallel Computing)--foreach

    R手册 Parallel Computing foreach foreach foreach 后端支持 library doParallel 为foreah包提供一个并行的后端 n cores lt detectCores logical
  • 树莓派4B与Android之缘——Android应用MiniChat聊天软件

    基本介绍和总体架构 一 应用介绍 一 基本介绍 MiniChat是一款聊天软件 你可以通过此软件进行聊天 本应用集成了融云模块 从而实现集成通讯 主要功能是账号注册 登录 好友添加 好友间发送文字 文件信息 二 应用展示 1 登录界面 未输