sponsored links

编程新技术实验(三)---Android注册登录功能App实现

目录

完整代码地址

1.引言

2.项目结构

3.模块汇总

4.子模块详细设计

模块1:.xml文件(控件布局)

模块2:.java文件(控件功能实现)

模块3:R.java(这个写.xml时自动生成)

模块4:.jsp(后台处理)

5.运行流程图 & 效果图

6.扩展模块设计

7.Tips(踩坑)

①mysql大小写问题

②Eclipse无法连接到模拟器解决方案

③模拟器上能连接数据库但是真机上不行的解决方案


完整代码地址

https://github.com/ChenMingK/Android-lab

1.引言

实验目的

通过使用Android API进行系统注册模块的开发,包括前台的Android原生app以后后台服务模块的开发,要求后台使用JavaEE框架实现。进一步理解Java、SDK、ADT、Eclipse的彼此依赖关系,并且能熟练使用java语言来编写Android工程。掌握Android应用开发环境的搭建方法以及虚拟机的配制方法,掌握Android工程创建方法,掌握基于虚拟机与真机的Android工程运行方法,了解Activity生命周期,理解Activity事件回调,onRestoreInstanceState()和 onSaveInstanceState两个函数的使用。在此基础上,掌握Android客户端与服务器通信的原理并且运用到项目中,理解Android发送http网络请求,包括GET请求和POST请求,熟悉Android异步任务的处理方法,包括各种回调函数。能够运用Servlet和数据库来实现一个登录界面。最后用json实现android客户端与服务端的通讯。对于Android  app调用的后台服务而言,要求使用JavaEE中的JSP或者Servlet进行编写,响应信息可以使用XML或JSON等方式进行封装,封装的信息由Android app再进行解析处理。

实验要求

对于Android app,至少需要有如下界面:

一、Android App开发

  1. 登录界面:包含用户名、密码的文字标识以及相应的输入栏,登录以及注册的按钮。当输入用户名以及密码后,点击登录按钮,则交数据提交至后台进行验证,如通过验证则跳转至欢迎界面,否则跳转回登录界面,并提示用户的验证错误原因;当用户点击注册按钮则跳转至注册界面。
  2. 注册界面:包含用户名、密码以及确认密码的文字标识以及相应的输入栏,提交以及取消按钮。当输入相关信息后,首先验证输入的信息是否符合要求(用户名至少5位,最多10位,以英文字母开头,只允许包含英文字母、数字以及_,同时必须至少有一个大写英文字母;密码为6-12位,只允许包含英文字母、数字和_,同时要求确认密码必须与密码一致),如不符合要求则在界面内提示错误,只有符合要求才提交给后台进行注册操作。如注册成功则跳转至欢迎界面,否则跳转回注册界面并提示用户的注册错误原因;当用户点击取消按钮则返回登录界面。
  3. 欢迎界面:显示对用户的欢迎信息,其中必须包括用户的登录名。

当获取到后台响应的字符串后,再在app内进行处理并显示相关信息。

二、后台服务的开发

对于Android  app调用的后台服务而言,要求使用JavaEE中的JSP或者Servlet进行编写,响应信息可以使用XML或JSON等方式进行封装,封装的信息由Android app再进行解析处理。

对于注册信息的存储,要求使用Mysql数据库进行存放,其中用户名作为表的主键进行存储。

参考资料

  1. https://blog.csdn.net/lemonxq/article/details/79514577
  2. http://www.cnblogs.com/xdp-gacl/p/3760336.html
  3. https://www.cnblogs.com/jingmoxukong/p/8258837.html?utm_source=gold_browser_extension
  4. https://blog.csdn.net/qq_23035265/article/details/52625110
  5. http://www.runoob.com/w3cnote/android-tutorial-activity.html

术语与缩写解释


缩写、术语


解 释


Servlet


小服务程序或服务连接器


Tomcat


免费的开放源代码的Web 应用服务器


JDK


Java 语言的软件开发工具包,Java Development Kit


Eclipse


一个开放源代码的、基于Java的可扩展开发平台


SDK


软件开发工具包,Software Development Kit


ADT


安卓开发工具,Android Development Tools


HTTP


超文本传送协议 ,Hypertext transfer protocol


JSON


一种轻量级的数据交换格式, JavaScript Object Notation


SQL


结构化查询语言(Structured Query Language)


SPP


精简并行过程,Simplified Parallel Process


SD


系统设计,System Design


AsyncTask


Android提供的一个处理异步任务的类

2.项目结构

应用程序名:lab4

工程文件名:lab4

开发环境:

Eclipse

Eclipse With ADT

mysql

Tomcat

模拟器:夜神模拟器(用自带的模拟器卡爆你);也可以真机调试(需要与电脑连接同一WLAN);

项目结构:每个布局文件对应一个.java程序

编程新技术实验(三)---Android注册登录功能App实现
       编程新技术实验(三)---Android注册登录功能App实现

左图为Android工程,右图为Web工程(.jsp文件部署在Tomcat上)

3.模块汇总

PS:这个模块纯属个人瞎扯,没有详细研究过Android各组件关系,大概看一下就好...

模块汇总表


子系统A


模块名称


功能简述


UI模块


作为视图层展现给用户

xml布局文件


功能模块


作为后台实现功能的代码

.java文件,实现每个页面的Activity


模型模块


存储数据和属性(R.java)

存储各个控件的id


子系统B


模块名称


功能简述


控制模块


实现的servlet功能,用于访问与操作数据库

同时能反馈给用户信息

模块关系图

对于Android http通信,包含两个系统,分别是客户端系统和服务器系统,客户端系统包括视图模块和代码功能实现模块和模型模块,服务器系统包括控制模块。(可能名字不是那么科学)

编程新技术实验(三)---Android注册登录功能App实现

4.子模块详细设计

模块1:.xml文件(控件布局)

Tips:布局自己看着大概写吧,另外注意onClick属性会绑定一个监听事件,需要在.java中写对应的方法名及操作。


模块名称


UI模块


功能描述


直接显示在屏幕上的视图,可以在其中放置多个控件并且设置其布局方式使其呈现在用户面前。


接口与属性


所有用户界面元素都是由View和ViewGroup对象创建的。 View 是一种可以在屏幕上绘制某种画面并且可以与用户互动的对象。ViewGroup对象则是为了定义布局的接口而保存其他View(和ViewGroup)对象。Android提供一个View和ViewGroup子类的集合,这个集合能提供相同的输入控制(例如按钮和文本框)和各种各样的布局模式(例如一个线性或者相对布局)。


数据结构

与算法


XML文件可以为布局提供一个可读结构, 一个View的XML节点名称与它代表的Android类相对应。在布局中,使用了相对布局,线性布局,线性布局嵌套在相对布局中。相对布局是一种通过设置相对位置进行的布局,如android:layout_below 表示在目标组件之下。设置id的组件是为了后台寻找方便。

login.xml

相对布局RelativeLayout可以通过指定子对象之间的关系或者子对象与父对象之间的关系来对空间或者文本进行布局。这种布局方式比较灵活,自由度大。Width和Height均为fill_parent,设置background可以将自己的图片保存入drawable中进行使用。

设置两个输入框,一个为用户名,设置id为username, layout_width为fill_parent,layout_height为50dp,layout_paddingLeft为40dp,layout_marginTop为4dp,ems为10,hint为“请输入用户名”。对于密码输入框,设置基本相同但是id为password,类型也显示为password,这样输入密码时会显示为·而非密码,避免泄密。同时增加singleLine=true来保证输入的密码只能为一行,即不能使用回车键,hint为“请输入密码”。

密码框旁设置了一个CheckBox,可以通过代码勾选是否显示密码。

设置登录按钮,设置id为log_button_1,layout_width为match_parent,layout_paddingLeft为40dp,background为#000000(黑色),layout_gravity为center按钮上的文字为"注册 ",textColor为"#FFFFFF"。

设置注册按钮,设置id为log_button_2,layout_width为match_parent,layout_height为wrap_content,layout_maxHeight为50dp, layout_maxWidth为40dp,layout _marginTop为10dp,background为#000000(黑色),layout_gravity为center按钮上的文字为"登录 ",textColor为"#FFFFFF"。

对于button按键的样式,可以先在drawable文件夹中新建一个xml文件,在文件里设置出想要的按键样式,使用时就将按键的background属性链接到新建的xml文件即可。

设置提示信息,同textview文本框,将提示信息输入(比如密码不正确等),平时设置为不可见,只有点击登录键之后才会变成可见状态用于提示。

register.xml

总体为线性布局LinerLayout,大致的结构与login.xml相似,有三个输入框,分别为用户名和两个密码(一般注册会让用户再次输入密码以确认),相关设置与登录界面相似。

提示信息不符设置与登录界面相同

有两个按键,确认和取消,设置方式也同登录界面

welcome.xml

线性布局,设置一个文本框显示之后的动态内容,id为welcome_text,layout_width,layout_height都为fill_content,textColor为#FFFF00,textSize为30sp,gravity为center。

右上角设置了一个注销按钮,id为welcome_bt,layout_width,layout_height都为wrap_content,layout_marginTop为0,gravity为right,textSize为20sp,text为“注销”。

changepass.xml

修改密码的界面,同样是线性布局,其结构与register如出一辙。


补充说明


android:layout_below,意思是该组件位于引用组件的下方,而引用的组件就是这个属性值里面的内容“@id/要引用的id名”;

android:layout_alignParentTop 是否对齐父组件的顶部;

android:layout_weight属性限定在水平布局时,不同的控件占的宽度比率,具体规则为:如果水平布局有两个控件,其android:layout_weight属性值分别为n和m,则属性值为n的控件所占的长度比例为总长的n/(n+m),属性值为m的控件所占的长度比例为m/(n+m)。属性值越大,占的份额越多;

fill_parent、wrap_content和match_parent三个属性都用来适应视图的水平或垂直大小:fill_parent设置一个顶部布局或控件为fill_parent将强制性让它布满整个屏幕。wrap_content设置一个视图的尺寸为wrap_content将强制性地使视图扩展以显示全部内容。match_parent在Android2.2中match_parent和fill_parent是一个意思;

android:layout_alignParentLe    贴紧父元素的左边缘

android:layout_alignParentRight  贴紧父元素的右边缘

android:layout_alignTop   本元素的上边缘和某元素的的上边缘对齐

android:layout_alignLeft   本元素的左边缘和某元素的的左边缘对齐

layout_gravity是用来设置该view相对与起父view的位置

在Android上一般不使用绝对布局。

android:layout_marginleft  本元素与父元素的距离(left表示左距离)

android:layout_paddingleft  本元素有效文字显示部分与边框的距离(left表示左距离)

部分代码

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@drawable/background_img3"
    android:fitsSystemWindows="true" >

    <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_width="fill_parent"
        android:layout_height="300dp"
        android:layout_centerHorizontal="true"
        android:gravity="center_vertical"
        android:orientation="vertical" >

        <ImageView
            android:id="@+id/imageView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="30dp"
            android:src="@drawable/logo" />

        <FrameLayout
            android:id="@+id/frameLayout1"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:layout_marginRight="20dp"
            android:background="#FFFFFF"
            android:layout_marginTop="20dp" >

            <EditText
                android:id="@+id/username"
                android:layout_width="fill_parent"
                android:layout_height="50dp"
                android:hint="请输入账号"
                android:paddingLeft="40dp"
                android:singleLine="true"
                 />

            <ImageView
                android:id="@+id/login_img1"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_marginTop="10dp"
                android:src="@drawable/login_img1"
                android:visibility="visible" />

        </FrameLayout>

        <FrameLayout
            android:id="@+id/frameLayout2"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:layout_marginRight="20dp"
            android:layout_marginTop="5dp"
            android:background="#FFFFFF" >

            <ImageView
                android:id="@+id/login_img2"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_gravity="center|left"
                android:src="@drawable/login_img2" />

            <EditText
                android:id="@+id/password"
                android:layout_width="fill_parent"
                android:layout_height="50dp"
                android:hint="请输入密码"
                android:inputType="textPassword"
                android:paddingLeft="40dp"
                android:singleLine="true"
                 >

                <requestFocus />
            </EditText>

            <CheckBox
                android:id="@+id/checkBox1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center|right"
                android:text="显示密码" />

        </FrameLayout>
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_marginTop="260dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:orientation="vertical" >

        <RelativeLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" >

            <TextView
                android:id="@+id/err"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="20dp"
                android:text="TextView"
                android:textColor="#FFFFFF"
                android:textSize="20sp"

                android:visibility="invisible" />

        </RelativeLayout>

        <Button
            android:id="@+id/log_button_1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#000000"
            android:gravity="center"
            android:maxWidth="40dp"
            android:minHeight="50dp"
            android:onClick="login"
            android:layout_marginTop="10dp"
            android:text="登录"
            android:textColor="#FFFFFF" />

        <Button
            android:id="@+id/log_button_2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:background="#000000"
            android:onClick="sign"
            android:text="注册"
            android:textColor="#FFFFFF" />

        <Button
            android:id="@+id/changebutton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="change"
            android:text="修改密码"
            android:layout_gravity="right"
            android:textColor="#FFFFFF"
            android:textSize="15sp" />

    </LinearLayout>

</RelativeLayout>

效果图

编程新技术实验(三)---Android注册登录功能App实现

模块2:.java文件(控件功能实现)

Tips:

1.AsyncTask

构建AsyncTask子类的泛型参数

AsyncTask<Params,Progress,Result>是一个抽象类,通常用于被继承.继承AsyncTask需要指定如下三个泛型参数:

Params:启动任务时输入的参数类型.

Progress:后台任务执行中返回进度值的类型.

Result:后台任务执行完成后返回结果的类型.

构建AsyncTask子类的回调方法

AsyncTask主要有如下几个方法:

doInBackground:必须重写,异步执行后台线程要完成的任务,耗时操作将在此方法中完成.

onPreExecute:执行后台耗时操作前被调用,通常用于进行初始化操作.

onPostExecute:当doInBackground方法完成后,系统将自动调用此方法,并将doInBackground方法返回的值传入此方法.通过此方法进行UI的更新.

onProgressUpdate:当在doInBackground方法中调用publishProgress方法更新任务执行进度后,将调用此方法.通过此方法我们可以知晓任务的完成进度.

2.Intent

四大组件间的枢纽——Intent(意图),Android通信的桥梁;

本工程使用Intent作Activity之间简单的数据传输(如用户名)

startActivity(Intent)/startActivityForResult(Intent):来启动一个Activity

3.每一个Activity都要在AndroidManifest.xml中注册

4.AndroidManifest.xml文件中添加访问网络的权限:<uses-permission android:name="android.permission.INTERNET" />

5.Android HTTP请求方式:HttpURLConnection

步骤:

step1:创建一个URL对象: URL url = new URL(https://www.baidu.com);

step2:调用URL对象的openConnection( )来获取HttpURLConnection对象实例:

HttpURLConnection conn = (HttpURLConnection) url.openConnection();

step3:设置HTTP请求使用的方法:GET或者POST,或者其他请求方式比如:

PUT conn.setRequestMethod("GET");

step4:设置连接超时,读取超时的毫秒数,以及服务器希望得到的一些消息头

conn.setConnectTimeout(6*1000); conn.setReadTimeout(6 * 1000);

step5:调用getInputStream()方法获得服务器返回的输入流,然后输入流进行读取了

InputStream in = conn.getInputStream();

step6:最后调用disconnect()方法将HTTP连接关掉 conn.disconnect();

具体见下面的代码吧


模块名称


功能模块


功能描述


这里是实现Android的http通信中客户端的功能。包括异步线程处理,访问servlet,用JSON实现客户端和服务器端的通信以及与用户的对话提示等。


接口与属性


Activity的四种状态

活动状态(Active/Running):Activity处于界面最顶端,获取焦点

暂停状态(Paused):Activity失去焦点,但对用户可见

停止状态(Stopped):Activity被完全遮挡,但保留

非活动状态(Killed):Activity被停止

一个activity的用户接口被一个层次化的视图提供--继承于View类的对象。每个View控制activity窗口中的一个特定矩形区域并且能响应用户交互。例如,一个view可能是个button,初始化动作当用户触摸它的时候。

 

Android提供大量预定义的view来设计和组件你的布局。“Widgets”是一种给屏幕提供可视化(并且交互)元素的view,例如按钮、文件域、复选框或者仅仅是图像。“Layouts”是继承于ViewGroup的View,提供特殊的布局模型为它的子view,例如线程布局、格子布局或相关性布局。可以子类化View和ViewGroup类(或者存在的子类)来创建自己的widget和而已并且应用它们到activity布局中。

最普通的方法是定义一个布局使用view加上XML布局文件保存在程序资源里。这样可以单独维护用户接口设计,而与定义activity行为的代码无关。设置布局作为UI使用setContentView(),传递资源布局的资源ID。可是,你也可以创建新Views在你的activity代码,并且创建一个view层次通过插入新Views到ViewGroup,然后使用那个布局通过传递到ViewGroup给setContentView()。

HttpClient中,接口HttpParams定义了这些参数的通用接口。


数据结构

与算法


Activity是一个应用中的组件,它为用户提供一个可视的界面,方便用户操作。Activity生命周期如下:

onCreate() -> onStart() -> onResume() -> onPause() -> onStop() -> onDestroy

onCreate的方法是在Activity创建时被系统调用,是一个Activity生命周期的开始。在其中通过findViewById方法注册要用到的变量,同可以设置监听事件绑定控件/对象(或者在布局文件中设置onClick属性设置点击事件在对应的Activity实现设置的方法名)。

本工程有4个.java文件:

login.java  register,java  welcome.jave  changepassword.java

其命名与xml文件对应,分别实现的是:登录、注册、欢迎、修改密码界面的功能。下面分别对其进行介绍。

login.java实现登录界面功能

设置了CheckBox按钮对应的点击事件用于显示密码

设置了登录按钮和注册按钮对应的点击事件:

“登录”按钮对应login方法,该方法先检验用户名和密码是否为空,如果不满足要求,使用预留的TextView提示用户;满足要求则调用后台线程发送HTTP请求与服务器通讯,将服务器传回的字节输入流转换为字符串最后转换为JSONObject,判断其中是否有error键(错误信息存储在error键对应的值中),如果有错误信息,则通过预留的TextView提示用户并结束线程,否则将用户名信息以键值对的形式保存在Intent中传递给欢迎页面(welcome.java)并提示登录成功(Toast)。

register.java实现注册界面功能

注册按钮绑定check方法,将对用户输入的用户名及密码进行如下检查

* 1.用户名不能为空

* 2.用户名必须为5-10位

* 3.用户名必须以英文字母开头s,只允许包含英文字母、数字以及_

* 4.用户名必须至少有一个大写英文字母

* 5.密码&确认密码不能为空

* 6.密码&确认密码必须为6-12位

* 7.密码&确认密码只允许包含英文字母、数字和_

* 8.密码与确认密码必须与密码一致

不满足要求则以预留的TextView提示用户,否则调用showNormalDialog方法让用户进行确认,用户点击确认后与login.java一样调用后台线程与服务器通讯。如果有错误信息,则显示。否则进入欢迎界面并提示注册成功。

welcome.java实现欢迎界面功能

首先从登录和注册界面传过来的Intent里获取用户名信息,然后使用TextView控件显示欢迎用户的文字,如Welcome XXX。另外提供注销功能,点击“注销”按钮调用logout方法返回登录界面。

changepassword.java实现修改密码功能

此为扩展模块,与注册界面功能类似

首先检查用户名 原始密码 新密码是否符合规范,如果符合,弹出确认提示,最后调用后台线程与服务器通讯,修改数据库中该用户的密码。

后台验证:

error:用户名不存在

error:原始密码不正确


补充说明


strings.xml里定义了我们需要显示的字符串;

Android HTTP请求方式:HttpURLConnection

step1:创建一个URL对象

step2:调用URL对象的openConnection( )来获取HttpURLConnection对象

step3:设置HTTP请求使用的方法:GET或者POST,或者其他请求方式step4:设置连接超时,读取超时的毫秒数,以及服务器希望得到的一些消息头

step5:调用getInputStream()方法获得服务器返回的输入流,然后输入流进行读取

step6:最后调用disconnect()方法将HTTP连接关掉

AsyncTask

构建AsyncTask子类的泛型参数

AsyncTask<Params,Progress,Result>是一个抽象类,通常用于被继承.继承AsyncTask需要指定如下三个泛型参数:

Params:启动任务时输入的参数类型.

Progress:后台任务执行中返回进度值的类型.

Result:后台任务执行完成后返回结果的类型.

构建AsyncTask子类的回调方法

AsyncTask主要有如下几个方法:

doInBackground:必须重写,异步执行后台线程要完成的任务,耗时操作将在此方法中完成.

onPreExecute:执行后台耗时操作前被调用,通常用于进行初始化操作.

onPostExecute:当doInBackground方法完成后,系统将自动调用此方法,并将doInBackground方法返回的值传入此方法.通过此方法进行UI的更新.

onProgressUpdate:当在doInBackground方法中调用publishProgress方法更新任务执行进度后,将调用此方法.通过此方法我们可以知晓任务的完成进度.

Intent

四大组件间的枢纽——Intent(意图),Android通信的桥梁

本工程使用Intent作Activity之间简单的数据传输(如用户名)

startActivity(Intent)/startActivityForResult(Intent):来启动一个Activity

每一个Activity都要在AndroidManifest.xml中注册

AndroidManifest.xml文件中添加访问网络的权限:

<uses-permission android:name="android.permission.INTERNET" />

部分代码(login.java)

package com.example.lab4;
import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask;
//import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.method.HideReturnsTransformationMethod;
import android.text.method.PasswordTransformationMethod;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import org.json.*;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.regex.Pattern;

public class login extends Activity {
    //private static final int VISIABLE = 0;
    //变量声明
    private EditText username;
    private EditText password;
    private TextView err;
    private Button button1;
    private Button button2;
    private CheckBox cb;
    private Button changebutton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.login);
        username = (EditText)findViewById(R.id.username);
        password = (EditText)findViewById(R.id.password);
        err    = (TextView)findViewById(R.id.err);
        button1  = (Button)findViewById(R.id.log_button_1);
        button2  = (Button)findViewById(R.id.log_button_2);
        cb = (CheckBox) findViewById(R.id.checkBox1);
        changebutton = (Button) findViewById(R.id.changebutton);

        //CheckBox勾选按钮用于显示密码

        cb.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener(){
            public void onCheckedChanged(CompoundButton buttonView,boolean isChecked){
                if(cb.isChecked()){
                    // show the password
                    password.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
                }
                else{
                    // hide the password
                    password.setTransformationMethod(PasswordTransformationMethod.getInstance());
                }
            }
        });
    }

    //登录按钮绑定的监听事件:检测用户名和密码是否为空,符合条件则调用后台服务
    public void login(View view){
          String user = username.getText().toString();
          String pass = password.getText().toString();
          if(user.isEmpty()||pass.isEmpty()){
             err.setText("用户名和密码不能为空");
             err.setVisibility(View.VISIBLE);
          }

          else{
              String pattern1 = "^[a-zA-Z][a-zA-Z0-9_]*$";    //用户名必须以英文字母开头,只允许包含英文字母、数字以及_
              boolean match1 = Pattern.matches(pattern1, user);
              if(!match1){
                  err.setText("该用户名不存在");
                  err.setVisibility(View.VISIBLE);
                  return;
              }
              err.setText(null);
              err.setVisibility(View.INVISIBLE);
              //Toast参数:1:上下文对象 2:显示的内容  3:显示的时间,只有LONG和SHORT两种会生效
              Toast.makeText(this,"loging......",Toast.LENGTH_SHORT).show();
              new SignInProcess().execute(user,pass);   //调用后台服务,请求登录
          }
   }

    //注册按钮绑定的事件:跳转到注册界面
   public void sign(View view){
       Intent intent = new Intent(login.this, register.class);
       startActivity(intent);
   }

   //修改密码,跳到修改密码界面
   public void change(View view){
       Intent intent = new Intent(login.this, ChangePassword.class);
       startActivity(intent);
   }
   //后台线程,执行异步任务,这里是和后台服务通讯并接收返回的数据 SignInProcess发送到signin
   /*AsyncTask:
    * 第1个参数:启动任务时输入的参数类型
    * 第2个参数:后台任务执行中返回进度值得类型
    * 第3个参数:后台任务执行完成后返回的结果类型
    * 方法
    * doInBackground:必须重写,异步执行后台线程要完成的任务,耗时操作将在此方法中完成.
      onPreExecute:执行后台耗时操作前被调用,通常用于进行初始化操作.
      onPostExecute:当doInBackground方法完成后,系统将自动调用此方法,并将doInBackground方法返回的值传入此方法.通过此方法进行UI的更新.
      onProgressUpdate:当在doInBackground方法中调用publishProgress方法更新任务执行进度后,
                          将调用此方法.通过此方法我们可以知晓任务的完成进度
    */
    private class SignInProcess extends AsyncTask<String, String, String> {

    //private static final int VISIBLE = 0;
    //private static final int VISIABLE = 0;

    @Override
       protected String doInBackground(String... params) {
           String username = params[0];
           String password = params[1];
           String result = "";
           /*
            * 192.168.197.1:8080  192.168.1.122:8080  要在真机上使用的话需要查找计算机在局域网中的IPV4地址 ipconfig/all
            * 前面的用自己的IP地址替换  后面是发布在Tomcat上的项目名以及对应的文件夹/文件
            */

           String s_url = "http://10.63.22.33:8080/testAndroidJSP/index.jsp";
           s_url = s_url + "?username=" + username + "&password=" + password;    //记得带参数,Manifest设置网络连接权限!
           /*HttpURLConnection
            * step1:URL url = new URL(https://www.baidu.com);                             创建URL对象
            * step2:HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 获取对象实例
            * step3:conn.setRequestMethod("GET");                                         设置请求方式
            * step4:conn.setConnectTimeout(6*1000); conn.setReadTimeout(6 * 1000);
            * 设置连接超时,读取超时的毫秒数,以及服务器希望得到的一些消息头
            * 此处可增加一些其他判断操作,如状态码的判断......
            * step5:InputStream in = conn.getInputStream();
            * 调用getInputStream()方法获得服务器返回的输入流,然后输入流进行读取
            * step6:conn.disconnect();  关闭HTTP连接
            */
           try {
               URL url = new URL(s_url);
               HttpURLConnection conn = (HttpURLConnection) url.openConnection();
               conn.setRequestMethod("GET");
               conn.setConnectTimeout(3000); // 设置超时时间 ms
               conn.setReadTimeout(3000);
               conn.connect();
               if(conn.getResponseCode() == 200){               //状态码
                   InputStream is = conn.getInputStream();      //InputStream获得字节输入流
                   InputStreamReader reader = new InputStreamReader(is, "UTF-8");  //字节到字符的桥梁,规定使用的字符集为UTF-8
                   int temp;
                   while((temp=reader.read()) != -1) {          //字节转字符方法
                       result += (char)temp;
                   }
               }
           } catch(Exception e) {
               e.printStackTrace();
           }

           return result;   //服务器以JSON转换的字符串传回来,但是因为是字节输入流所以要转换成字符
       }

    @Override
       protected void onPostExecute(String result) {   //doInBackground的返回值将传入此方法,更新UI
           try {
               JSONObject result_json = new JSONObject(result); //字符串转JSONObject
               if(result_json.has("error")) {                   //键->error? 有error的键
                   String error_code;
                   error_code = result_json.getString("error");  //获得值
                   err.setText(error_code);
                   err.setVisibility(View.VISIBLE);             //接受错误信息并通过TextView显示
                   password.setText(null);
               } else {
                   SignInSuccess(result_json);

               }
           } catch (Exception e) {
               e.printStackTrace();
           }
       }

   }
   //成功接收到数据后的操作,数据以JSON形式传递
   private void SignInSuccess(JSONObject info) {
       Intent intent = new Intent(this, Welcome.class);
       Toast.makeText(this,"登录成功",Toast.LENGTH_SHORT).show();
       try {
           intent.putExtra("username", username.getText().toString());
           //intent.putExtra("name", info.getString("name"));
           /*
            * 送到的Activity怎么取Indent?
            * 1.Intent intent =getIntent();  取得启动该Activity的Intent对象
            * 2.String first = intent.getStringExtra("et1");  调用getStringExtra("键")
            */
       } catch (Exception e) {
           e.printStackTrace();
       }
       startActivity(intent);   //跳转到Welcome的页面  数据存储在intent中
   } 

}

模块3:R.java(这个写.xml时自动生成)


模块名称


模型模块


功能描述


R.java文件自动生成,用来定义Android程序中所有各类型的资源的索引。(它是只读的,开发人员不对其修改)。


接口与属性


android工程所有资源信息(组件、图片、字符等等)都是由HashMap<Integer,Object>来存储的key值就是R.java中的静态变量值,value就是相对应的各种对象信息(组件、图片、字符等等)。


数据结构

与算法


R.java文件中默认有attr、drawable、layout、string等四个静态内部类,每个静态内部类分别对应着一种资源,如layout静态内部类对应layout中的界面文件,其中每个静态内部类中的静态常量分别定义一条资源标识符,如public static final int main=0x7f030000;对应的是layout目录下的main.xml文件。


补充说明


R.java及本地资源文件,使用“@+”声明的资源,系统会自动在R.java中创建。

模块4:.jsp(后台处理)

Tips:前后端通信主要注意HTTP请求的URL是否正确以及参数是否携带上。


模块名称


控制模块


功能描述


这里主要是客户端的程序,具体体现为Index.jsp、register.jsp、ChangePassword.jsp,请求内容通过Tomcat以JSON的形式返回给客户端,实现客户端和服务器的通信,注意这里需要访问数据库中的数据。


接口与属性


前端和后台之间通过HttpURLConnection连接后台,后台通过将项目部署到Tocmat上,之后通过JDBC连接数据库,之后将响应的数据返回前端

HttpURLConnection. setRequestMethod中的GET请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),以?分割URL和传输数据,参数之间以&相连

当配置正确时,Apache 为HTML页面服务,而Tomcat 实际上运行JSP 页面和Servlet。另外,Tomcat和IIS等Web服务器一样,具有处理HTML页面的功能,另外它还是一个Servlet和JSP容器,独立的Servlet容器是Tomcat的默认模式。

每种数据库的驱动程序都应该提供一个实现java.sql.Driver接口的类,简称Driver类。

java.sql.DriverManager类负责管理JDBC驱动程序的基本服务,是JDBC的管理层,作用于用户和驱动程序之间,负责跟踪可用的驱动程序,并在数据和驱动程序之间建立连接。java.sql.Connection接口代表与特定数据库的连接,在接连的上下文中可以执行SQL语句并返回结果,还可以通过getMetaData()方法获得由数据库提供的相关信息。java.sql.Statement接口用来执行静态SQL语句,并返回执行结果。java.sql.PreparedStatement接口继承并扩展了Statement接口,用来执行动态的SQL语句,即包含参数的SQL语句。java.sql.ResultSet接口类似于一个数据表,通过该接口的实例可以获得检索结果集,以及对应数据表的相关信息,ResultSet实例通过执行查询数据库的语句生成。


数据结构

与算法


控制模块主要简单抽象出了数据访问层,并实现数据库的操作。同时将数据转化为JSON格式便于通信。

通过现有的 OutputStream 创建新的 PrintWriter。此便捷构造方法创建必要的中间 OutputStreamWriter,后者使用默认字符编码将字符转换为字节。 参数: out - 输出流 autoFlush - boolean 变量;如果为 true,则 println、printf 或 format 方法将刷新输出缓冲区

Index.jsp

实现登录模块的数据处理,将移动端的数据发送到服务器上,用JDBC将服务器上的数据和页面上的比对,将结果返回到移动端上

register.jsp

实现登录模块的数据处理,将移动端的数据发送到服务器上,用JDBC连接数据库,判断用户名是否存在,若不存在则创建用户名和密码,将结果返回到数据库

ChangePassword.jsp

实现修改密码模块的数据处理,服务器接受用户名、旧密码和新密码,将用户名和旧密码进行匹配,若正确则修改密码,否则返回错误,将结果返回

在数据传递过程中,设置数据的发送格式为JSON,读入请求数据,在控制台打印请求内容(为JSON格式),然后用传过来的JSON格式的请求数据作为参数创建JSON数组。然后通过用户名创建对象。判断密码是否符合数据库中的内容,如果符合则将数据库中其他类型的数据封装进JSON数组中作为响应的内容发送回客户端。


补充说明


JSON有两种表示结构,对象和数组。对象结构以”{”大括号开始,以”}”大括号结束。中间部分由0或多个以”,”分隔的”key(关键字)/value(值)”对构成,关键字和值之间以”:”分隔;数组结构以”[”开始,”]”结束。中间由0或多个以”,”分隔的值列表组成。

JSONObject可以看作是一个json对象,这是系统中有关JSON定义的基本单元,其包含一对儿(Key/Value)数值。JSONArray它代表一组有序的数值。将其转换为String输出(toString)所表现的形式是用方括号包裹,数值以逗号”,”分隔(例如[value1,value2,value3])在web.xml中需要配置。

工程中引用的包为com.alibabab中的fastjson数据包。 Fastjson是一个Java语言编写的高性能功能完善的JSON库。它采用一种“假定有序快速匹配”的算法,把JSON Parse的性能提升到极致,是目前Java语言中最快的JSON库。Fastjson接口简单易用,已经被广泛使用在缓存序列化、协议交互、Web输出、Android客户端等多种应用场景

部分代码

<%@ page language="java" import="java.sql.*" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.io.PrintWriter" %>
<%@ page import="com.alibaba.fastjson.JSON" %>
<%@ page import="com.alibaba.fastjson.JSONObject" %>
<%@ page import="com.alibaba.fastjson.JSONObject" %>

<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":"
    + request.getServerPort() + path + "/";
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <% 

        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/lab4_db";   //为lab4建立的数据库名为lab4_db
        String db_username = "root";
        String db_password = "xiaokang";
        String username = request.getParameter("username");  //获取客户端URL携带的数据
        String password = request.getParameter("password");
        Connection conn = null;
        /*
        该后台程序用于登录查询,查询数据库中是否有该用户名再验证密码是否正确
        密码正确则啥都不做,错误的话要讲用户名错误还是密码错误的信息与error组成
        键值对传回去
        */

        Class.forName(driver);
        conn = DriverManager.getConnection(url,db_username,db_password);
        JSONObject message = new JSONObject();

        String sql = "SELECT * FROM users WHERE username = '" + username + "'";
        PreparedStatement pstmt = conn.prepareStatement(sql);
        ResultSet rs = pstmt.executeQuery();
        int flag = 1;
        if(rs.next())
        {
            //存在,继续验证密码
            String temp_check = rs.getString("password");
            if(!temp_check.equals(password))  //主键是username的话(不允许有重复)rs只存了1行,只判断1次
                flag = 0;
        }
        else
        {
            //不存在,返回错误信息,用户名错误
            message.put("error", "用户名不存在");

        }
        if(flag == 0)
        {
            message.put("error","密码错误");
        }
        response.setContentType("text/json;charset=UTF-8"); //数据传输格式...
        PrintWriter outP = response.getWriter();

        outP.println(message.toJSONString());
        outP.close();
        rs.close();
        pstmt.close();
        conn.close();

    %>

</body>
</html>

5.运行流程图 & 效果图

编程新技术实验(三)---Android注册登录功能App实现

运行效果图

数据库中数据:

编程新技术实验(三)---Android注册登录功能App实现

登录界面                                                          注册界面

编程新技术实验(三)---Android注册登录功能App实现
         编程新技术实验(三)---Android注册登录功能App实现

登录操作                                                           登录成功界面(点注销返回登录界面)

编程新技术实验(三)---Android注册登录功能App实现
         编程新技术实验(三)---Android注册登录功能App实现

注册操作

编程新技术实验(三)---Android注册登录功能App实现
          编程新技术实验(三)---Android注册登录功能App实现

确保用户名和密码格式正确                                  注册成功

编程新技术实验(三)---Android注册登录功能App实现
            编程新技术实验(三)---Android注册登录功能App实现

查询数据库

编程新技术实验(三)---Android注册登录功能App实现

6.扩展模块设计

在实验的基础要求上新增了修改密码的模块。

要求用户输入用户名以及原始密码和新密码

* 1.用户名不能为空

* 2.用户名必须是已存在的

* 3.原始密码&修改密码不能为空

* 4.原始密码&修改密码必须为6-12位

* 5.原始密码&修改密码只允许包含英文字母、数字和_

具体效果如下:

编程新技术实验(三)---Android注册登录功能App实现

编程新技术实验(三)---Android注册登录功能App实现

编程新技术实验(三)---Android注册登录功能App实现

7.Tips(踩坑)

①mysql大小写问题

MySQL在Windows下都不区分大小写。

如果想在查询时区分字段值的大小写,则:字段值需要设置BINARY属性,设置的方法有多种:

1.创建时设置:

CREATE TABLE T(

A VARCHAR(10) BINARY

);

2.使用alter修改:

ALTER TABLE `tablename` MODIFY COLUMN `cloname` VARCHAR(45) BINARY;

C、mysql table editor中直接勾选BINARY项。

3.使用sql语句时加binary

如SELECT * FROM users WHERE binary username = …

②Eclipse无法连接到模拟器解决方案

127.0.0.1:62001: 由于目标计算机积极拒绝,无法连接

1)关闭AS和夜神进程

2)cmd下输入  adb version为1.039

3)cmd下输入  Nox_adb version为1.036(PS:输入此命令前提是需要将夜神模拟器中的bin路径添加到path环境变量中)

4)通过版本比对发现AS的版本比夜神版本高,究其原因需要将二者版本整为一致。

5)将AS的adb.exe拷贝至夜神bin目录下,并将adb.exe修改为Nox_adb.exe

6)开启夜神模拟器,在cmd下输入adb devices,此时并显示(我只做了这个操作就OK了)

List of devices attached

127.0.0.1:62001 device

至此问题已解决。

PS:下方Device显示后设置Run Configurations

③模拟器上能连接数据库但是真机上不行的解决方案

找到本机在局域网中的ipv4地址   igconfig/all

将URL地址中的ip改为这个

PS:电脑和笔记本必须同一WIFI

https://www.jianshu.com/p/baa1631a5688

https://blog.csdn.net/stormwy/article/details/8832164

获取本地IP地址(理论上来说App是有方法获取IPV4或者IPV6地址的,然而我用下面的方法没有成功获取到IPV4)

通过InetAddress.getLocalHost()得到始终是“127.0.0.1”,要想得到真正的网络ip地址要通过下面的方法:首先新建一个工程,修改AndroidManifest.xml文件增加用户权限,如下:

<uses-permissionandroid:name="android.permission.INTERNET"/>

//必写<uses-permissionandroid:name="android.permission.ACCESS_NE

通过InetAddress.getLocalHost()得到始终是“127.0.0.1”,要想得到真正的网络ip地址要通过下面的方法:

首先新建一个工程,修改AndroidManifest.xml文件增加用户权限,如下:

<uses-permission android:name="android.permission.INTERNET"/>   //必写

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"></uses-permission>

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>//必写

<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"></uses-permission>

主要函数代码如下:

// 得到本机ip地址
    public String getLocalHostIp()
    {
        String ipaddress = "";
        try
        {
            Enumeration<NetworkInterface> en = NetworkInterface
                    .getNetworkInterfaces();
            // 遍历所用的网络接口
            while (en.hasMoreElements())
            {
                NetworkInterface nif = en.nextElement();// 得到每一个网络接口绑定的所有ip
                Enumeration<InetAddress> inet = nif.getInetAddresses();
                // 遍历每一个接口绑定的所有ip
                while (inet.hasMoreElements())
                {
                    InetAddress ip = inet.nextElement();
                    // 在这里如果不加isIPv4Address的判断,直接返回,在4.0上获取到的是类似于fe80::1826:66ff:fe23:48e%p2p0的ipv6的地址
                    if (!ip.isLoopbackAddress()
                            && InetAddressUtils.isIPv4Address(ip
                                    .getHostAddress()))
                    {
                        return ipaddress = "本机的ip是" + ":" + ip.getHostAddress();
                    }
                }

            }
        }
        catch (SocketException e)
        {
            Log.e("feige", "获取本地ip地址失败");
            e.printStackTrace();
        }
        return ipaddress;

    }

    // 得到本机Mac地址
    public String getLocalMac()
    {
        String mac = "";
        // 获取wifi管理器
        WifiManager wifiMng = (WifiManager) getSystemService(Context.WIFI_SERVICE);
        WifiInfo wifiInfor = wifiMng.getConnectionInfo();
        mac = "本机的mac地址是:" + wifiInfor.getMacAddress();
        return mac;
    }
Tags: