Android学习项目之简易版微信为例(二)

1 概述

从这篇开始,正式进入简易版微信的开发。深入学习前,想谈谈个人对Android程序开发一些理解,不一定正确,只是自己的一点想法。Android程序开发不像我们在大学时候写C控制台程序那样,需要从main开始写代码逻辑,大部分逻辑控制代码都由自己来实现。事实上,Android已经为我们提供了一个程序运行的框架,我们只需要往框架中填入我们所需的内容即可,这里的内容主要是:四大组件——Activity、Service、ContentProvider、BroadCast。在这四大组件中,可以实现前端界面显示和后端数据处理相关的代码控制逻辑。关于前端界面显示主要涉及到:组件的生命周期回调管理、注册视图(View)的事件监听器、集合类型视图的数据适配器(Adapter)、不同窗口界面的跳转等等。关于后台数据的交互处理,主要涉及到:异步任务(AsyncTask)、Handler/Message、网络编程(HTTP或Socket)、数据库操作(SQLiteOpenHelper或ContentProvider)等等。所以,对我们初学者来说,学习Android主要就是学习Android框架中各个类的作用和使用方法。

好,下面开始本文内容。当第一次使用微信(或其他常用的Android应用)的时候,首先就是注册、登录,本文就来实现这两个基本功能。由于刚接触Android开发,所以需要了解很多基础知识点。我们将通过这两个功能的实现,学习以下几个Android开发的知识点:

Layout布局:制作用户界面,Android中使用XML文件描述UI布局,类似HTML+CSS方式的界面组件方式。对后端的童鞋来说,按UI设计稿进行布局或按需求来定制一个控件或许是学习前端最大的障碍之一。关于UI布局,本文起一个头,随着我们的简易版微信应用深入开发,我们就会慢慢熟悉Android的UI布局了;关于自己动手开发一个视图(View),这应该也是Android开发中的难点,我们将在后续文章中慢慢深入学习。

Activity概念及其生命周期:布局完成后,要将布局得到的UI界面显示出来,这就需要引入Activity组件——负责UI界面的显示和用户的交互。Activity应该是Android应用最重要的组件了 —— 一个应用可以没有四大组件中的其他三大组件(即:内容提供者ContentProvider、服务Service、广播BroadCast),但不能没有Activity —— 这个组件类似Windows编程中的窗口,在Windows中如果没有窗口怎么与用户交互?

登录、注册功能的实现:讲完Activity后,就需要通过Activity来加入我们需要的逻辑。Android应用程序一般都是C(客户端)/S(服务端)结构的,注册、登陆功能的实现包括客户端逻辑的编写和服务器端逻辑的编写,我们将在第4小节介绍这两个功能的客户端和服务端的逻辑实现。

最后总结本篇博文内容,并预告下篇博文内容,那就让我们开启Android学习的第二课吧!

2 Android的MVC结构

当学习一门新技术时,我们很少会思考这门技术重点学习什么,应该怎么去学习之类的问题。大多数童鞋常常会一开始就一头扎到知识点的海洋中,最后自己也搞不清学会了什么。比如学习Java,一上来就从变量命名开始学、接着学习表达式、控制流、面向对象,如果初学者也许这是合适的,但如果你已经学会C或C++,有些知识点似乎就不需要学习了。比如我之前包括现在主要用的是C++,那一上来就会学习I/O流、集合类这些常用的知识点,就可以开发一些小程序了。有时间的话再去看看多线程、垃圾收集以及源代码。

学习Android也一样,首先应该弄清楚应该学一些什么,这就要从高一些的层次来看Android。从架构上来说,和很多UI框架一样,Android用的是主流的MVC结构,这应该是比较成熟的前端框架了。MVC框架结构如下图:

MVC结构分为三部分:

控制器(Controller)部分:接收用户输入,通过事件分发机制确定接收者。这部分在Android中已有框架完成,我们只需在Activity中向View视图实例对象注册特定监听器即可,监听器实现的具体逻辑由我们来写;而且监听器只需要知道有这么回事就行,用到去API查就可以。

模型(Model)部分:这部分主要实现业务逻辑的处理和数据的更新。这部分应该是Android编程的重点,四大组件中的Service(服务)、ContentProvide(内容提供者)都是Model(模型)有关的,另外数据存储,如数据库、文件等也属于Model范畴,这部分应该是Android学习的重点。

视图(View)部分:这部分就是用于显示模型数据。这部分在Android中就是使用View视图进行UI布局,有时框架提供的View部件不满足需求时,得根据需求重写View,实现我们需要的效果。

这样划分之后,我们就大体上知道了一个Android软件由哪些部分组成以及它们之间如何是交互的,Android框架已经为我们实现了哪些功能 ,哪些功能需要我们扩展的,这样我们学习起来才会有的放矢。

3 Layout布局及分析

关于做软件UI,博主曾经有一段比较痛苦的回忆。记得那是在大三上学期学习完《数据库系统概论》这门课程之后,老师要求用ASP.NET做一个网站。当时博主做的是一个在线购书系统,不懂怎么制作网页界面,于是就在Visual Studio中以拖拽控件的方式来布局,最后虽然把系统倒腾出来了(过程可以说是十分痛苦),但界面看了实在无法让人产生购买的欲望。经历过这么一出之后,博主对前端界面产生了恐惧感和厌恶感。不过,进入公司参加工作以来,慢慢接触到了软件UI的设计与实现过程,同时自己也动手实现了一些界面布局后,才让这种恐惧感和厌恶感慢慢减少了。在这里,博主想来一句经验之谈:要想做一个漂亮的UI布局,不是通过拖拽控件能拖出来的。当然,对初学者来说,可以通过通过拖拽控件的方式来学习Android框架。

Android制作UI界面有两种方式:

(1)通过XML配置文件的方式,博主一般称它为“声明式布局”(不知对不对):这种方式就是把UI要显示的控件及这些控件的显示方式声明在XML文件中,然后通过Activity的SetContentView接口将布局的描述文件设置给Activity;

(2)通过Java类来添加布局控件,并设置显示相关的属性,博主一般称这一布局方式为“命令式布局”。

第(1)种布局方式,即声明式布局,一般用于变化不大UI的布局;第(2)种布局方式,即命令式布局,一般用于程序运行时不断变化的UI界面的布局。本篇博文将实现的登陆、注册功能采用的是声明式布局,所以本小节仅介绍声明式布局,命令式布局将在后续博文中用到时再做详细阐述。

好了,理论的东西就不扯太多了,搞软件开发的最怕听到一大堆理论了,下面让我们来看看登陆和注册的布局界面的实现效果吧(可能还不是很完美,以后边学习边完善吧!)。首先是登陆页面(这也是打开软件后的第一个页面):

注册页面:

注册、登录之间交互与登录成功后的界面,这里登录成功后的界面上什么都没有,所以在此就没单独贴出来了。图片有点糊,凑合看看哈~

下面以登录界面的代码,来看看Android中如何实现界面布局的,整个UI布局代码如下(代码路径:$res/layout/activity_login.xml):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical">

 <!--Top Panel-->
 <TextView
 android:layout_width="match_parent"
 android:layout_height="50dp"
 android:background="@color/colorTopPanelBackground"
 android:gravity="center"
 android:text="@string/string_login"
 android:textSize="@dimen/font_size_large"
 android:textColor="@color/colorSpecialWhite" />

 <LinearLayout
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:layout_margin="@dimen/activity_horizontal_margin"
 android:orientation="vertical">

 <LinearLayout
 android:layout_width="match_parent"
 android:layout_height="50dip"
 android:orientation="horizontal"
 android:layout_marginTop="50dp">

 <TextView
 android:layout_width="50dip"
 android:layout_height="50dip"
 android:gravity="center_vertical|right"
 android:text="+86"
 android:textColor="@color/colorSpecialBlack"
 android:textSize="@dimen/font_size_medium" />

 <EditText
 android:id="@+id/edt_login_cellphone_number"
 android:layout_width="0dp"
 android:layout_height="50dip"
 android:layout_weight="1"
 android:layout_marginLeft="25dp"
 android:background="@null"
 android:hint="你的手机号码"
 android:textSize="@dimen/font_size_medium"
 android:textColorHint="@color/colorHintText"/>

 </LinearLayout>

 <View
 android:id="@+id/dvd_login_username"
 android:layout_width="match_parent"
 android:layout_height="2px"
 android:background="@color/colorDefault" />

 <LinearLayout
 android:layout_width="match_parent"
 android:layout_height="50dip"
 android:orientation="horizontal">

 <TextView
 android:layout_width="50dip"
 android:layout_height="50dip"
 android:gravity="center_vertical|right"
 android:text="@string/string_pass_word"
 android:textColor="@color/colorSpecialBlack"
 android:textSize="@dimen/font_size_medium" />

 <EditText
 android:id="@+id/edt_login_password"
 android:layout_width="0dp"
 android:layout_height="50dip"
 android:layout_weight="1"
 android:layout_marginLeft="25dp"
 android:background="@null"
 android:inputType="textPassword"
 android:textSize="@dimen/font_size_medium"
 android:hint="填入密码"
 android:textColorHint="@color/colorHintText"/>

 </LinearLayout>

 <View
 android:id="@+id/dvd_login_password"
 android:layout_width="match_parent"
 android:layout_height="2px"
 android:background="@color/colorDefault" />

 <Button
 android:id="@+id/btn_login"
 android:layout_width="match_parent"
 android:layout_height="@dimen/button_general_height"
 android:layout_marginTop="50dip"
 android:background="@drawable/btn_common_selector"
 android:text="@string/string_login"
 android:textSize="@dimen/font_size_medium"
 android:textColor="@color/colorSpecialWhite"/>

 <Button
 android:id="@+id/btn_register"
 android:layout_width="match_parent"
 android:layout_height="@dimen/button_general_height"
 android:layout_marginTop="20dip"
 android:background="@drawable/btn_implicit_selector"
 android:text="@string/string_register"
 android:textSize="@dimen/font_size_medium" />

 </LinearLayout>

</LinearLayout>

上述代码一层套一层,最终形成一个树状结构,如下图所示:

图中每个矩形就是一个控件(或称为视图),每个控件都有一套与它相关的外观属性(类似Web编程中的CSS),控制着该控件的显示效果。下面对逐个控件及其外观属性进行深入分析,从根节点开始:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical">

根节点是一个布局控件,在这里用的是线性布局(LinearLayout),其它的布局控件还有相对布局(RelativeLayout)和帧布局(FrameLayout)。布局控件的作用就是堆砌(专业点的说法叫布置Arrange)控件:线性布局,顾名思义,布局方式只能按一个方向(水平horizontal或垂直vertical)堆砌控件,如上述代码块中,android:orientation属性用于说明LinearLayout是水平横向的线性布局;相对布局,这一布局方式比线性布局要复杂一下,控件之间位置关系不像线性布局那样只能沿着一个方向,这种布局下,控件的位置根据已有的其他控件来确定的(该布局的具体实例将在后续博文中阐述);帧布局。另外,上述代码块中还有两个属性:android:layout_width和android:layout_height,用来描述该控件的宽与高,这也是每个控件都要填的属性。这两个属性的值指定的是一个长度值,可以用像素(px)、点(pt)、设备独立像素(dp或dip),这里用的是一个特殊值:match_parent——匹配父窗口,即长或宽和父窗口一样;另外一个特殊值是:wrap_content——内容包裹,即长或宽和空间中内容匹配,内容所占区域有多大,控件的长或宽就是多大。

<!--Top Panel-->
 <TextView
 android:layout_width="match_parent"
 android:layout_height="50dp"
 android:background="@color/colorTopPanelBackground"
 android:gravity="center"
 android:text="@string/string_login"
 android:textSize="@dimen/font_size_large"
 android:textColor="@color/colorSpecialWhite" />

这是第一个可视控件,为文本视图(TextView),可以看到有很多属性控制它的外观显示,如之前讲过的宽度和高度,android:background描述该控件的背景色(很多Android控件也有这一属性),这里采用的是引用资源的方式,采用这种方式可以提高代码的可维护性,颜色资源具体定义在$res/values/colors.xml文件中(可以把它理解成程序设计中的常量),除了上述背景色资源,我们还定义了其他一些颜色资源,在下面的代码中会用到:

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <color name="colorPrimary">#11D31D</color>
 <color name="colorPrimaryDark">#308E0E</color>
 <color name="colorAccent">#FF4081</color>

 <!--color in default status-->
 <color name="colorDefault">#999999</color>

 <!--color in active status-->
 <color name="colorActive">@color/colorPrimary</color>

 <!--background color of top panel-->
 <color name="colorTopPanelBackground">#525252</color>

 <!--color of hint text-->
 <color name="colorHintText">#DDDDDD</color>

 <!--some special color-->
 <color name="colorSpecialBlack">#000000</color>
 <color name="colorSpecialWhite">#FFFFFF</color>

</resources>

之后一个属性android:grivity用于描述控件中内容的对齐方式,此处就是TextView中文本的对齐方式(为居中对齐)。再接下来一个属性是android:text,用来指定TextView中的文本内容,这里同样是引用另一个资源文件中的字符串资源,文件位于$res\values\string。xml中,这个文件专门用来定义字符串常量,除了上述字符串外,还定义了一些其他字符串资源:

<resources>
 <string name="app_name">MyChat</string>

 <!-- TODO: Remove or change this placeholder text -->
 <string name="hello_blank_fragment">Hello blank fragment</string>

 <!--constant string used in resource-->
 <string name="string_nick_name">昵称</string>
 <string name="string_pass_word">密码</string>
 <string name="string_login">登录</string>
 <string name="string_register">注册</string>
 <string name="string_dialog_title">提示</string>
 <string name="string_dialog_tips_prefix">正在</string>
 <string name="string_dialog_tips_suffix">,请稍等...</string>

</resources>

接下来两个属性分别定义了文本的大小和颜色,同样使用索引资源的方式,其中文本颜色使用的是前面已经讲过的颜色资源,文本大小的资源定义在$res\valuse\dimens.xml文件中,这一文件就是用来定义和尺寸有关的资源(如长度、大小等),在这个文件中还定义了其它一些尺寸资源,如下:

<resources>
 <!-- Default screen margins, per the Android Design guidelines. -->
 <dimen name="activity_horizontal_margin">16dp</dimen>
 <dimen name="activity_vertical_margin">16dp</dimen>

 <dimen name="horizontal_line_margin">20dp</dimen>
 <dimen name="contact_image_width">50dp</dimen>
 <dimen name="contact_image_height">50dp</dimen>
 <dimen name="context_image_top_buttom_margin">15dp</dimen>
 <dimen name="activity_registration_vertical_margin">16dp</dimen>

 <!--following tags define font size-->
 <dimen name="font_size_medium">16sp</dimen>
 <dimen name="font_size_small">14sp</dimen>
 <dimen name="font_size_large">18sp</dimen>
 <dimen name="font_size_xsmall">12sp</dimen>
 <dimen name="font_size_xlarge">20sp</dimen>

 <!--following tags defi-->
 <dimen name="button_general_height">40dp</dimen>

</resources>

到这里,我们就把第一个控件——顶部标题的TextView控件——分析完了。可以看到,为了提高代码的可维护性和复用性,我们将大多数属性值都定义在相应的资源文件中。下面的控件分析起来应该就简单多了,接下来又是一个布局控件,里面存放的是一个登陆表单:

<LinearLayout
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:layout_margin="@dimen/activity_horizontal_margin"
 android:orientation="vertical">

这里没什么可以讲的,这里有一个地方需要注意,新增了一个android:layout_margin属性,用于描述控件的上、下、左、右外边距,如下:

上、下、左、右外边距也可以独立控制,对于的属性分别为:android:margin_Top、android:margin_Buttom、android:margin_Left和android:margin_Right。接下来就是表单的内容区域了,首先要显示两个输入框及其说明文字,输入框使用的是EditText控件,说明文本使用的是TextView,它们是水平排列的,所以需要用线性布局把它们套起来,代码如下:

<LinearLayout
 android:layout_width="match_parent"
 android:layout_height="50dip"
 android:orientation="horizontal"
 android:layout_marginTop="50dp">

 <TextView
 android:layout_width="50dip"
 android:layout_height="50dip"
 android:gravity="center_vertical|right"
 android:text="+86"
 android:textColor="@color/colorSpecialBlack"
 android:textSize="@dimen/font_size_medium" />

 <EditText
 android:id="@+id/edt_login_cellphone_number"
 android:layout_width="0dp"
 android:layout_height="50dip"
 android:layout_weight="1"
 android:layout_marginLeft="25dp"
 android:background="@null"
 android:hint="你的手机号码"
 android:textSize="@dimen/font_size_medium"
 android:textColorHint="@color/colorHintText"/>

 </LinearLayout>

 <View
 android:id="@+id/dvd_login_username"
 android:layout_width="match_parent"
 android:layout_height="2px"
 android:background="@color/colorDefault" />

 <LinearLayout
 android:layout_width="match_parent"
 android:layout_height="50dip"
 android:orientation="horizontal">

 <TextView
 android:layout_width="50dip"
 android:layout_height="50dip"
 android:gravity="center_vertical|right"
 android:text="@string/string_pass_word"
 android:textColor="@color/colorSpecialBlack"
 android:textSize="@dimen/font_size_medium" />

 <EditText
 android:id="@+id/edt_login_password"
 android:layout_width="0dp"
 android:layout_height="50dip"
 android:layout_weight="1"
 android:layout_marginLeft="25dp"
 android:background="@null"
 android:inputType="textPassword"
 android:textSize="@dimen/font_size_medium"
 android:hint="填入密码"
 android:textColorHint="@color/colorHintText"/>

 </LinearLayout>

 <View
 android:id="@+id/dvd_login_password"
 android:layout_width="match_parent"
 android:layout_height="2px"
 android:background="@color/colorDefault" />

上述代码中的大部分属性在前面都已经介绍过了,新增的属性有只有三个,下面分别介绍。android:inputType用于描述输入框的输入类型,如这里用到的是密码类型:textPassword,这样就可以将输入的字母变成一个个小点点,如下:

对于EditText编辑框控件,还有其他输入类型(input type),如下:

(1)text

(2)textEmailAddress

(3)textUri

(4)number

(5)phone

设置不同的输入类型,运行时效果就是输入文本时,弹出的软键盘不同,如inputType设置为textEmailAddress时,则键盘上多了一个@符号,

当inputType设置为number或phone时,则软键盘为:

剩下的两个属性都和输入框都和提示文字有关,分别为android:hint和android:textColorHint,分别用于描述提示文字的文本内容和文本颜色,如下:

上图中输入框下面有一条绿色的横线,这里采用的做法是设置一个高度为2px(一般来说,1px就可以,不过个人感觉在这里不够明显,所以就设为2px了)、宽度为match_parent的View,如下:

<View
 android:id="@+id/dvd_login_password"
 android:layout_width="match_parent"
 android:layout_height="2px"
 android:background="@color/colorDefault" />

再接下来就是两个Button按钮,分别用于触发登录、注册操作:

代码如下:

<Button
 android:id="@+id/btn_login"
 android:layout_width="match_parent"
 android:layout_height="@dimen/button_general_height"
 android:layout_marginTop="50dip"
 android:background="@drawable/btn_common_selector"
 android:text="@string/string_login"
 android:textSize="@dimen/font_size_medium"
 android:textColor="@color/colorSpecialWhite"/>

 <Button
 android:id="@+id/btn_register"
 android:layout_width="match_parent"
 android:layout_height="@dimen/button_general_height"
 android:layout_marginTop="20dip"
 android:background="@drawable/btn_implicit_selector"
 android:text="@string/string_register"
 android:textSize="@dimen/font_size_medium" />

这些代码在之前都有所涉及,在这里也不再赘述了。这里有一点特殊:设置背景用了一个称为选择器(selector)的资源——因为按钮按下和弹起的时候,其背景是不一样的,如下为按下状态:

和前面的弹起时的状态比较一下,背景色变深了,这就用选择器资源了。该资源为$res\color目录下(注:创建Android工程时,默认是没有这个文件夹的,需要手动创建),内容如下:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
 <item android:state_pressed="true"
 android:color="#308E0E"/> <!-- pressed -->
 <item android:color="#11D31D"/> <!-- default -->
</selector>

分别指定了按下和默认的颜色。到此为止,我们将登陆布局页面讲完了,注册页面和登陆页面没什么区别,在这里就不在阐述了。最后,这里还想分享一些个人经验:

(1)布局其实就是两步:第一步:确定用什么控件;第二步:为控件配上属性;

(2)这么多属性,书写时最好要有一个次序,我的做法是:第一个是id属性;其次是必要的宽、高属性,接下来是布局相关的属性,如内外边距等,再接着是背景色、内容的对齐方式等,最后是控件内容,如文字内容、颜色、大小等,这是一个由外到内、从通用属性到特殊属性的一个书写次序。

(3)尽量将一些属性值定义在资源文件中,便于代码的后期维护和复用。

3 Activity概念及其生命周期

第2小结详细讲述了登录界面,介绍了涉及到的View组件及相关外观样式属性。定义在xml中的UI布局文件只是一个静态文件,需要加载到应用程序中,才能被渲染并显示出来,这就要用到本节所讲的Activity(活动)。本节主要总结Activity的一些理论知识,包括Activity的概念和生命周期。

3.1 什么是Activity

很多关于Android编程的书籍对Activity的概念都或多或少有一些阐述,但个人感觉都不是很系统。本文在这里抛出一块砖,总结一下自己对Android中Activity组件的理解(当然有很多不完善的地方,以后我会慢慢补充)如下:

(I)从MVC模式的角度看,Activity相当于Controller——一边是接收用户请求,另一边将请求分发(dispatch)到各处理单元中;也就是在一个Android中,Activity起到一个核心的作用:

图中标出了通过Activity启动各大组件的APIs,这些APIs都定义在Activity中。当然,上述图示仅是一个简单的模型,在接收用户请求时可能还会用到其他组件,在图中并没有一一给出,这些组件会在后续博文中深入学习。

(II)从设计模式模式的角度看,Activity可以看做是一个门面模式(Facade Pattern)。在Activity中聚合了很多组件,见下图:

对内封装复杂组件,对外提供简单的接口,同时也能独立获取这些组件实例,这就是门面模式的典型应用吧!这里需要注意的是,Activity继承自ContextThemeWrapper,在ContextThemeWrapper中聚合了Resource,通过它访问程序资源;而ContextThemeWrapper继承自ContextWrapper,通过它可以得到内容解析器(ContentResolver)等组件。总的来说,Activity提供了很多功能,封装了很多组件,使用起来也非常灵活。

(III)从一个Android开发者角度看,Activity是一个状态机。Activity定义了管理一个活动的生命周期的一系列事件,通过这些事件可以保存应用程序的状态,这些事件将在3.2中阐述。

3.2 Activity的生命周期

Activity的生命周期是每一本讲Android编程的书必讲的内容,也是Android程序设计的重点。Android的四大组件都有生命周期的概念,但Activity的生命周期最复杂,下图是来自Android SDK文档的一张Activity生命周期事件回调函数的调用次序:

这个图有点像操作系统课中进程状态转换图——各种状态切来切去。初看这张图的时候,应该会感觉有点乱,其实理清楚了的话,图中显示了也就是四条状态变换路径:

(I)中间垂直方向走下来:Activity launched → onCreate→ onStart→ onResume→ running→ onPause→ onStop→ onDestroy→ Activity shutdown

   注:这种情况下,用户打开应用程序首页,做了事情后就退出应用了。

(2)内圈:Activity launched → onCreate→ onStart→ onResume→ running→ onPause→ User navigates to the activity→ onResume→ ……

(3)中圈:Activity launched → onCreate→ onStart→ onResume→ running→ onPause→ onStop→ User navigates to the activity→ onRestart→ onResume→ ……    

(4)外圈:Activity launched → onCreate→ onStart→ onResume→ running→ onPause [→ onStop→]App Process Killed→User navigates to the activity→ onCreate→ onRestart→ onResume→ ……

   注:标红加方括号的onStop表示可有可无,也就是外圈有两条路线——Pause状态下被Kill掉和Stop状态下被Kill掉。

正因为有这么多状态变换路径,就是因为用户交互复杂导致。下面,对生命周期的事件回调做以下简单说明:

(1)onPause和onResume对应,onStop和onStart/onRestart对应;

(2)执行过onPause、onStop和onDestroy的Activity在系统内存不足的情况下都有可能被Kill掉,当然Kill的优先级不同,Destroy的最先被Kill,其次是Stop的,实在没辙了才Kill Pause状态的Activity。

(3)对于被Kill掉的Activity,如果用户重新回到那个Activity的话,需要再次调用onCreate方法,创建Activity实例;

(4)onStop不一定始终都被执行,如Pause状态的Activity也可能被Kill掉,所以保存应用程序状态数据的代码,应该写在onPause中;

(5)各事件回调函数的调用时机及Activity状态如下:

4 登录功能的实现详解

从这节开始,我们正式进入登录、注册功能的实现。由于使用C/S结构,所以分为客户端和服务器端两个部分,客户端和服务器端之间交互采用HTTP协议,如下:

4.1 客户端逻辑

客户端继承Activity得到两个子类,LoginActivity和ResgiterActivity。注册功能的客户端代码与登录类似,在此不再赘述。下面来看LoginActivity的代码逻辑,在LoginActivity中主要重写了onCreate方法,这一方法中首先加载该Activity的UI,即$res/layout/activity_login.xml(在第2小节已经详细分析过了),然后对注册、登陆两个按钮添加监听器,代码逻辑如下:

@Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);

 setContentView(R.layout.activity_login);

 mLoginButton = (Button) findViewById(R.id.btn_login);
 mRegisterButton = (Button) findViewById(R.id.btn_register);
 mEditTextUserName = (EditText) findViewById(R.id.edt_login_cellphone_number);
 mEditTextPassword = (EditText) findViewById(R.id.edt_login_password);
 mDvdPassword = findViewById(R.id.dvd_login_password);
 mDvdUserName = findViewById(R.id.dvd_login_username);

 mLoginButton.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {

 Log.d("OnClick", "Enter the click callback of Login Button");

 String cellphone_number = mEditTextUserName.getText().toString().trim();
 String pass_word = mEditTextPassword.getText().toString().trim();

 Map<String, String> params = new HashMap<String, String>();
 params.put("url", LOGIN_PATH);
 params.put("cellphone_number", cellphone_number);
 params.put("pass_word", pass_word);

 new LoginAsyncTask(LoginActivity.this).execute(params);
 }
 });

 mRegisterButton.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {
 // Enter into the register activity
 Intent intent = new Intent(LoginActivity.this, RegisterActivity.class);
 startActivity(intent);
 }
 });

 mEditTextPassword.setOnFocusChangeListener(new View.OnFocusChangeListener() {
 @Override
 public void onFocusChange(View v, boolean hasFocus) {
 if (hasFocus) {
  mDvdPassword.setBackgroundColor(getResources().getColor(R.color.colorPrimary));
 } else {
  mDvdPassword.setBackgroundColor(getResources().getColor(R.color.colorDefault));
 }
 }
 });

 mEditTextUserName.setOnFocusChangeListener(new View.OnFocusChangeListener() {
 @Override
 public void onFocusChange(View v, boolean hasFocus) {
 if (hasFocus) {
  mDvdUserName.setBackgroundColor(getResources().getColor(R.color.colorPrimary));
 } else {
  mDvdUserName.setBackgroundColor(getResources().getColor(R.color.colorDefault));
 }
 }
 });
 }

说明:

(1)第5行,调用setContentView设置UI布局文件,这句代码必须先写上,否则后面的View视图都没法获得;

(2)第7~12行,调用findViewById方法得到对应ID的View,此处的ID在编写UI布局时指定;

(3)通过匿名内部类的方式,向登录按钮注册Click事件监听器,在该事件监听器的内部逻辑中,首先从手机号的编辑框和密码的编辑框中获得文本内容,然后通过一个异步任务(AsyncTask)将内容发送到服务器端,异步任务的代码如下:

public class LoginAsyncTask extends AsyncTask<Map<String, String>, Void, Boolean> {

 private ProgressDialog mDialog;
 private Context mContext;

 public LoginAsyncTask(Context context) {
 mDialog = new ProgressDialog(context);
 mDialog.setTitle("提示信息");
 mDialog.setMessage("正在登录,请稍等...");

 mContext = context;
 }

 @Override
 protected Boolean doInBackground(Map<String, String>... params) {
 String url = params[0].get("url");

 Map<String, String> mapParams = new Hashtable();
 for (Map.Entry<String, String> entry : params[0].entrySet()) {
 if (!entry.getKey().equals("url")) {
 mapParams.put(entry.getKey(), entry.getValue());
 }
 }

 String result = null;
 try {
 result = HttpUtil.sendPostRequest(url, mapParams, "utf-8");
 } catch (Exception e) {
 e.printStackTrace();
 }

 return result.equals("True") ? true : false;
 }

 @Override
 protected void onPostExecute(Boolean result) {
 super.onPostExecute(result);
 if (mDialog.isShowing()) mDialog.dismiss();
 if (result) {
 // jump to Main page
 Intent intent = new Intent(mContext, MainActivity.class);
 mContext.startActivity(intent);
 } else {
 Toast.makeText(mContext, "登录失败!", Toast.LENGTH_LONG).show();
 }
 }
}

异步任务,顾名思义就是要开启一个线程来执行的代码逻辑。在Android中,显示应用程序界面和接受用户输入的代码都是在UI线程中执行,所以UI线程一般不允许阻塞,否则会造成用户体验差。对一些耗时操作,需要由非UI线程来执行,执行完成后的结果由UI线程来更新。在这里,因为提交用户名和密码的网络操作耗时较长,如果直接在UI线程中执行的话,会导致UI线程阻塞,引起Android Not Responding异常(见下图,很熟悉吧),所以得放在异步任务中执行。

Android框架中在Java多线程框架之上,引入了AsyncTask(异步任务)和Handler/Message/Loop两种机制来实现多线程编程。下面对异步任务做一个简单说明(详细讲的话可能需要一小节的内容),Handler/Message/Loop机制较复杂,后续用到了我们再介绍。AsyncTask是一个抽象类,必须要重写的方法为doInBackground方法,这个方法运行在后台线程中,在上述代码中就是执行发送网络请求,并对返回结果进行解析。另外还有两个函数也比较重要,onPreExecute和onPostExecute。onPreExecute运行在UI线程中,并且在doInBackground之前调用,主要用于异步任务的初始化,例如显示进度对话框等;onPostExecute在doInBackground之后调用,也是运行于UI线程,主要用于异步任务结果在UI中的更新显示。上述代码中主要是根据返回结果,判断登陆是否成功,如果成功,则跳转到MainActivity,即通过startActivity开启一个新的Activity,即应用程序的主界面;如果失败,则弹出一个Toast提示用户登录失败。

另外可以看到AsyncTask是一个模板类,有三个模板参数,如上述程序中AsyncTask<Map<String, String>, Void, Boolean>,其中第一个模板参数,如Map<String, String>,用于指定doInBackground的入参类型;第三个模板参数,如此处的Boolean,是doInBackground的返回值类型,同时是onPostExecute的入参类型;第二个模板参数,用于指定进度值的类型(可以为Integer或Float等),也就是说,异步任务可以将进度值更新到UI线程中显示,在这里由于没有用到进度条刻度信息,所以类型设为Void。

发送命令,获取服务器的返回结果的逻辑,我们封装在HttpUtil类中,如下:

public class HttpUtil {

 public static String sendPostRequest(
 String path, Map<String, String> params, String encoding)
 throws Exception {

 StringBuilder sb = new StringBuilder();
 if (params != null && !params.isEmpty()) {
 for (Map.Entry<String, String> entry : params.entrySet()) {
 sb.append(entry.getKey()).append("=");
 sb.append(URLEncoder.encode(entry.getValue(), encoding));
 sb.append("&");
 }
 sb.deleteCharAt(sb.length() - 1);
 }

 HttpURLConnection conn = (HttpURLConnection) new URL(path).openConnection();
 conn.setConnectTimeout(5000);
 conn.setRequestMethod("POST");
 conn.setDoOutput(true);

 OutputStream os = conn.getOutputStream();
 os.write(sb.toString().getBytes());
 os.flush();

 if (conn.getResponseCode() == 200) {
 String result = StreamTool.readStream(conn.getInputStream());
 return result;
 } else {
 return null;
 }
 }
}

主要逻辑就是把命令参数用&符号链接起来,写到HttpURLConnection中,并通过HttpURLConnection发送到服务器端;服务器返回后,读取状态码,如果为200,则表明连接执行成功,此时读取从服务器返回的值,通过StreamTool从返回的流中读取,代码逻辑如下:

public class StreamTool {

 public static String readStream(InputStream stream) throws IOException {

 StringBuilder sb = new StringBuilder();
 BufferedReader in = new BufferedReader(new InputStreamReader(stream));

 String line;
 while ((line = in.readLine()) != null) {
 sb.append(line);
 System.out.println("===>" + line);
 }

 return sb.toString();
 }

}

至此,登陆功能的客户端代码就写完了。有兴趣的童鞋可以到云盘上去下载源代码:http://pan.baidu.com/s/1skHOkxB(有空去注册一个github,感觉用百度云盘分享太low,^__^)。

4.2 服务端逻辑

服务器端就是写两个Servlet,LoginServlet和RegisterServlet。我们将数据库查询和修改操作封装在UserDAOImpl中,在LoginServlet和RegisterServlet中调用UserDAOImpl的接口,实现用户信息的验证和添加,下面仍然以LoginServlet为例。

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 request.setCharacterEncoding("utf-8");
 response.setCharacterEncoding("utf-8");

 // parse parameters from client.
 String cellphone_number = request.getParameter("cellphone_number");
 String pass_word = request.getParameter("pass_word");

 PrintWriter writer = response.getWriter();

 try {
 UserDAOInterface userDAO = new UserDAOImpl();
 User user = userDAO.queryByCellPhoneNumber(cellphone_number);

 if (user != null && user.getPassWord().equals(pass_word))
 {
 writer.append("True");
 System.out.println("True");
 }
 else
 {
 writer.append("False");
 System.out.println("False");
 }
 } catch (SQLException | ClassNotFoundException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 } finally {

 }
 }

简单解释一下:获取从客户端提交的参数,分别是手机号和用户密码,通过手机号从数据库查询对应的用户(封装在User实例中)记录,如果用户不为空,且用户密码正确,则返回True,否则返回为空。服务器端的代码就是Java Web编程,所以在这里就不详细讨论了。

5 总结

最后总结一下,本文我们主要学习了Layout布局以及展示布局的组件——Activity,对涉及到的View进行较详细的分析,对Activity的概念和生命周期回调也进行了介绍,最后以介绍了登录功能的代码实现,注册功能和登陆功能类似,感兴趣的童鞋可以把代码down下来瞅瞅。

下一次将介绍好友列表功能的实现,敬请关注^__^

源码下载:http://xiazai.jb51.net/201606/yuanma/MyChat(jb51.net).rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android编程实现二维码的生成与解析

    本文实例讲述了Android编程实现二维码的生成与解析.分享给大家供大家参考,具体如下: 直接上代码,代码上面有具体的解析,并且提供jar供下载:二维码Jar包.rar. 根据文本生成对应的二维码: // 生成QR图 private void createImage() { try { // 需要引入core包 QRCodeWriter writer = new QRCodeWriter(); String text = qr_text.getText().toString(); Log.i(T

  • 月下载量上千次Android实现二维码生成器app源码分享

    在360上面上线了一个月,下载量上千余次.这里把代码都分享出来,供大家学习哈!还包括教大家如何接入广告,赚点小钱花花,喜欢的帮忙顶一个,大神见了勿喷,小学僧刚学Android没多久.首先介绍这款应用:APP是一款二维码生成器,虽然如何制作二维码教程网上有很多,我这里再唠叨一下并把我的所有功能模块代码都分享出来. 在这里我们需要一个辅助类RGBLuminanceSource,这个类Google也提供了,我们直接粘贴过去就可以使用了 package com.njupt.liyao; import c

  • 基于Android实现个性彩色好看的二维码

    我编码的风格,先给大家展示下效果图,亲们感觉效果还不错,很满意的话,请继续往下阅读. 之前呢,也写过用安卓实现二维码生成彩色的二维码和带logo的二维码,也知道可以使用QRCode和ZXing两种方式,然后这一篇呢也是写二维码使用BarcodeFormat.QR_CODE,主要也是看见很多的非常漂亮的二维码,这里呢主要模仿qq的二维码,并且也高仿实现了长按发送给朋友和保存到图库的功能,觉得不错呢就请多支持下,哪里不好呢也可以说出来.好了我们一步一步来. 第一步:简单二维码实现 先来个最简单的二维

  • Android基于google Zxing实现各类二维码扫描效果

    随着微信的到来,二维码越来越火爆,随处能看到二维码,比如商城里面,肯德基,餐厅等等,对于二维码扫描我们使用的是google的开源框架Zxing,我们可以去http://code.google.com/p/zxing/下载源码和Jar包,之前我项目中的二维码扫描功能只实现了扫描功能,其UI真的是其丑无比,一个好的应用软件,其UI界面也要被大众所接纳,不然人家就不会用你的软件啦,所以说应用软件功能和界面一样都很重要,例如微信,相信微信UI被很多应用软件所模仿,我也仿照微信扫描二维码效果进行模仿,虽然

  • android 微信 sdk api调用不成功解决方案

    最近一直在调用微信的API,却发现一直调用不成功,纠结了好久,各方面找教程,找官方,官方里的文档也只是写得很模糊,说是按三步走. 1.申请App_ID 2.填写包名3. 获取程序签名的md5值, 这三步只要你走对了就能调通,可是大家都不知道有时候我们打包的keystore和我们打包的keystore获取到的程序签名的md5是不一样的.我们在申请的时候填的程序签名值是正式打包的,但我们在eclipse部署上去的却是用的我们默认的debug.keystore.而这样导致的后果就是程序签名不一样,会一

  • Android学习项目之简易版微信为例(一)

    这是"Android学习之路"系列文章的开篇,可能会让大家有些失望--这篇文章中我们不介绍简易版微信的实现(不过不是标题党哦,我会在后续文章中一步步实现这个应用程序的).这里主要是和广大朋友们聊聊一个非Java程序员对Android操作系统的理解以及一个Android工程的目录结构,为进一步学习做准备. 1 缘起 智能手机的出现与普及为人们的生活.工作带来了极大的便利,我们可以用手机随时随地.随心所欲地购物.玩游戏.聊天.听音乐等等.一个个精心设计.体验良好的移动客户端应用,让用户们爱

  • Android仿微信二维码和条形码

    本文实例为大家分享了Android仿微信二维码和条形码的具体代码,供大家参考,具体内容如下 package your.QRCode.namespace; import java.io.File; import java.io.FileOutputStream; import java.util.HashMap; import java.util.Map; import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHi

  • Android 第三方应用接入微信平台研究情况分享(二)

    微信平台开放后倒是挺火的,许多第三方应用都想试下,毕竟可以利用微信建立起来的关系链来拓展自己的应用还是挺不错的,可以节约很多在社交方面的开销,我最近由于实习需要也在研究这个东西,不过发现网上的相关资料还是挺少的,这里把我的整个研究情况给出来,希望可以共同学习. 第三方应用接入微信平台(1) 二.第三方应用与微信通信的时序图 2.接收微信的请求信息 前面四步和之前的"1.向微信发送消息"是一样的,不需要重复执行,这里给出来只是为了 流程的整体性.当我们注册后,应用图标会出现在微信聊天的列

  • Android上使用ZXing识别条形码与二维码的方法

    目前有越来越多的手机具备自动对焦的拍摄功能,这也意味着这些手机可以具备条码扫描的功能.手机具备条码扫描的功能,可以优化购物流程,快速存储电子名片(二维码)等. 本文所述实例就使用了ZXing 1.6实现条码/二维码识别.ZXing是个很经典的条码/二维码识别的开源类库,早在很久以前,就有开发者在J2ME上使用ZXing了,只不过需要支持JSR-234规范(自动对焦)的手机才能发挥其威力,而目前已经有不少Android手机具备自动对焦的功能. 本文代码运行的结果如下,使用91手机助手截图时,无法截

  • Android学习项目之简易版微信为例(二)

    1 概述 从这篇开始,正式进入简易版微信的开发.深入学习前,想谈谈个人对Android程序开发一些理解,不一定正确,只是自己的一点想法.Android程序开发不像我们在大学时候写C控制台程序那样,需要从main开始写代码逻辑,大部分逻辑控制代码都由自己来实现.事实上,Android已经为我们提供了一个程序运行的框架,我们只需要往框架中填入我们所需的内容即可,这里的内容主要是:四大组件--Activity.Service.ContentProvider.BroadCast.在这四大组件中,可以实现

  • Android版微信跳一跳小游戏利用技术手段达到高分的操作方法

    本文主要来讲个个好玩的东西,近来微信刚出的跳一跳微信小程序的游戏很火,看到很多人都达到了二三百分就各种刷朋友圈了. 甩手一个表情 最终我们达到的分数却是这样的: 羡慕吧 一定会有人拍手叫好,"黄金右手"!说真的,我已经不用右手好多年,而且我玩这个游戏压根就没用手,而是意念!哈哈,别喷我,开个玩笑而已,肯定是利用技术手段啦,什么技术?python喽-哈哈,不过不是我写的,我自己是做Android开发的,我对于python从来没有接触,只是恰好在蛋哥公众号看到关于这个游戏的文章,觉得有意思

  • Android实现简易版打地鼠

    本文实例为大家分享了Android实现简易版打地鼠的具体代码,供大家参考,具体内容如下 目标效果: 1.activity_main.xml页面: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schema

  • Android使用ViewBinding的详细步骤(Kotlin简易版)

    ViewBinding 是什么 2020年的3月份 巨佬 JakeWharton 开源的 butterknife 被官宣 停止维护,在github 上 说明 Attention: This tool is now deprecated. Please switch to view binding. Existing versions will continue to work, obviously, but only critical bug fixes for integration with

  • python游戏实战项目之智能五子棋简易版

    目录 导语 正文 总结 导语 前段时间不是制作了一款升级版本五子棋的嘛! 但是居然有粉丝私信我说: "准备拿到代码玩一下ok过去了!太难了准备放收藏夹落灰q@q~" 所噶,今天先放一个简易版本的五子棋给大家看看!学习嘛~从简单到难 还是慢慢来撒~ 学玩这篇可以学下一篇难一点的撒: Pygame实战:下五子棋吗?信不信我让你几步你也赢不了?​​​​​​​ 正文 嘿嘿!这五子棋只有人机对战了哈!不要看人机对战感觉很简单,其实代码量也超多滴. 主要代码:都有注释的撒!就不一步一步介绍了. i

  • 安卓版微信跳一跳辅助 跳一跳辅助Java代码

    安卓版微信跳一跳辅助,java实现,具体内容如下 已经看到网上有大神用各种方式实现了,我这是属于简易版ADB命令式实现. 操作方法 1.光标移动到起始点,点击FORM 2.光标移动到目标点,点击TO 3.小人已经跳过去了 原理说明 安装APP,通过设置起点和目标点位置,获得弹跳的毫秒数,发送请求到连接手机的电脑中,电脑执行adb命令起跳. 具体实现 本人的测试设备是Mate9,android版本为7.0,由于在非Root环境下,普通安卓应用并不能通过Runtime.getRuntime().ex

  • 如何在Android App中集成支付宝和微信支付功能

    前言 本文主要介绍如何在 Android App 里集成支付宝和微信支付的功能,文中将实现的步骤一步步介绍的非常详细,对同样遇到这个问题的朋友相信会是一个很好的参考,下面话不多说了,来一起看看详细的介绍吧. 集成支付宝支付 没想到现在 App 里集成支付宝是这么的简单,我还折腾了好久- 好了,开始,假设你已经完成了支付宝那些繁杂的申请啥的工作,进入开发了. 首先,去下载官方的 DEMO : App支付客户端DEMO&SDK. 导入开发资源 解压后把里面的 jar 包拿出来放到你工程的 lib 目

  • C#调用OpenCV开发简易版美图工具【推荐】

    前言 在C#调用OpenCV其实非常简单,因为C#中有很多OPenCV的开源类库. 本文主要介绍在WPF项目中使用OpenCVSharp3-AnyCPU开源类库处理图片,下面我们先来做开发前的准备工作. 准备工作 首先,我们先创建一个WPF项目. 然后,在Nuget上搜索OpenCVSharp,如下图: 接着,我们选择OpenCVSharp3-AnyCPU选项进行安装 . 安装了OpenCVSharp3-AnyCPU后,我们的项目会自动引入4个类库,如下图: 到这里,我们的准备工作就完成了,非常

  • 利用python实现简易版的贪吃蛇游戏(面向python小白)

    引言 作为python 小白,总是觉得自己要做好百分之二百的准备,才能开始写程序.以至于常常整天在那看各种语法教程,学了几个月还是只会print('hello world'). 这样做效率太低,正确的做法,是到身边找问题,然后编程实现.比如说,我学了高等数学,我是不是应该考虑下如何去用编程实现求导或者积分操作,如果想不出怎么办,是不是应该 baidu 一下,别人是如何实现数值积分或是符号积分的.我们每天买东西都要用到加减甚至乘除,那么我是否能编写个简单的计算器,如果命令行太丑的话,我是否能够快速

随机推荐