1. 说明
本音乐播放器基于Android开发,原为我和另外两个小伙伴在上学期间一起做的一个小项目,近来有时间整理一下。之前我有文章已经介绍了播放界面的功能实现(Android音乐播放器开发),但介绍的比较粗糙,接下来会做更细致化的整理。源码已同步到Gitee仓库,GitHub仓库,觉得还不错的话帮忙点个“star”吧,非常感谢。
当初代码写的很随意,目的只为实现功能。现在更倾向于代码可读性和简洁性,因此会在原来的程序基础上做一些小修改。也有可能不会一步到位,计划慢慢修改,以增强自己的理解。
服务端使用的是比较传统的servlet和jdbc传递数据,整理完之后,新版本会修改为SSM框架,更加简洁高效。安卓端使用的也都是基础的工具,比如音乐播放功能的实现也是借助于入门级的MediaPlayer类,目前关于安卓端没有什么更改的想法。
(适用于平时做个小课设的小伙伴们)
2. 注册界面设计
在写登录功能时,注册按钮的点击事件是启动本界面。
分析一下需求:
相对于登录界面,注册界面并没有很大的区别,直接贴全部的代码了(activity_register.xml)
<?xml version="1.0" encoding="utf-8"?>
<!--注册界面-->
<!--这里的布局放置是: 1 个 ImageView 控件,用于显示用户头像;3 个 EditText 控件,用于输入用户名、密码、再次输入密码;1 个 Button 控件为注册按钮-->
<!--修改 activity_register.xml 为 LinearLayout 布局-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_register"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/c"
android:orientation="vertical">
<include layout="@layout/main_title_bar"></include><!--引入标题栏-->
<ImageView
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="25dp"
android:src="@drawable/x"/>
<!--三个编辑框-->
<EditText
android:id="@+id/et_user_name"
android:layout_width="fill_parent"
android:layout_height="48dp"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="35dp"
android:layout_marginRight="35dp"
android:layout_marginTop="35dp"
android:background="@drawable/register_user_name_bg"
android:drawableLeft="@drawable/user_name_icon"
android:drawablePadding="10dp"
android:gravity="center_vertical"
android:hint="请输入用户名"
android:paddingLeft="8dp"
android:singleLine="true"
android:textColor="#000000"
android:textColorHint="#a3a3a3"
android:textSize="14sp"/>
<EditText
android:id="@+id/et_psw"
android:layout_width="fill_parent"
android:layout_gravity="center_horizontal"
android:layout_height="48dp"
android:layout_marginLeft="35dp"
android:layout_marginRight="35dp"
android:background="@drawable/register_psw_bg"
android:drawableLeft="@drawable/psw_icon"
android:drawablePadding="10dp"
android:hint="请输入密码"
android:inputType="textPassword"
android:paddingLeft="8dp"
android:singleLine="true"
android:textColor="#000000"
android:textColorHint="#a3a3a3"
android:textSize="14sp"/>
<EditText
android:id="@+id/et_psw_again"
android:layout_width="fill_parent"
android:layout_height="48dp"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="35dp"
android:layout_marginRight="35dp"
android:background="@drawable/register_psw_again_bg"
android:drawableLeft="@drawable/psw_icon"
android:drawablePadding="10dp"
android:hint="请再次输入密码"
android:inputType="textPassword"
android:paddingLeft="8dp"
android:singleLine="true"
android:textColor="#000000"
android:textColorHint="#a3a3a3"
android:textSize="14sp"/>
<Button
android:id="@+id/btn_register"
android:layout_width="fill_parent"
android:layout_height="40dp"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="35dp"
android:layout_marginRight="35dp"
android:layout_marginTop="15dp"
android:background="@drawable/register_selector"
android:text="注 册"
android:textColor="@android:color/white"
android:textSize="18sp"/>
</LinearLayout>
这里使用了与登录实现的同一个标题(main_title_bar.xml)
3. 注册功能实现
先贴全部代码
package cn.sjcup.musicplayer;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import org.json.JSONObject;
import cn.sjcup.musicplayer.servlet.RequestServlet;
public class RegisterActivity extends Activity {
private TextView mTitle; //标题
private TextView mBack; //返回按钮
private Button mRegister; //注册按钮
private EditText mName, mPwd, mPwdAgain; //用户名,密码,再次输入的密码控件
private String userName, pwd, pwdAgain; //用户名,密码,再次输入的密码的控件的值
private JSONObject registerInfo; //注册返回信息
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
initView(); //初始化界面
initEvent(); //初始化事件
}
private void initView(){
mTitle = this.findViewById(R.id.tv_main_title);
mBack = this.findViewById(R.id.tv_back);
mRegister = this.findViewById(R.id.btn_register);
mName = this.findViewById(R.id.et_user_name);
mPwd = this.findViewById(R.id.et_psw);
mPwdAgain = this.findViewById(R.id.et_psw_again);
mTitle.setText("注册");
}
private void initEvent(){
//返回按钮
mBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
RegisterActivity.this.finish();
}
});
//注册按钮
mRegister.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//获取输入框中的值
userName = mName.getText().toString().trim();
pwd = mPwd.getText().toString().trim();
pwdAgain = mPwdAgain.getText().toString().trim();
//先判断输入框是否有值
if (TextUtils.isEmpty(userName)) {
Toast.makeText(RegisterActivity.this, "请输入用户名", Toast.LENGTH_SHORT).show();
return;
} else if (TextUtils.isEmpty(pwd)) {
Toast.makeText(RegisterActivity.this, "请输入密码", Toast.LENGTH_SHORT).show();
return;
} else if (TextUtils.isEmpty(pwdAgain)) {
Toast.makeText(RegisterActivity.this, "请再次输入密码", Toast.LENGTH_SHORT).show();
return;
}
//判断两次输入的密码是否相同
if (!pwd.equals(pwdAgain)) {
Toast.makeText(RegisterActivity.this, "输入两次的密码不一样", Toast.LENGTH_SHORT).show();
return;
}
isExistThread(userName, pwd); //先检测用户名是否已经存在
}
});
}
private void isExistThread(final String account, final String password){
new Thread(){
public void run(){
try {
JSONObject result = RequestServlet.login(account, password);
Message msg = new Message();
msg.what=2;
msg.obj = result;
handler2.sendMessage(msg);
}catch (Exception e){
e.printStackTrace();
}
}
}.start();
}
private Handler handler2 = new Handler() {
public void handleMessage(android.os.Message msg) {
if (msg.what == 2) {
JSONObject result = (JSONObject) msg.obj;
if(result == null || !result.optString("account").equals(userName)){
//账户名未被注册
registerThread(userName, pwd); //注册新账户
}else{
//账户名已被注册
Toast.makeText(RegisterActivity.this, "用户名已被占用!", Toast.LENGTH_SHORT).show();
}
}
}
};
private void registerThread(final String account, final String password){
new Thread(){
public void run(){
try {
JSONObject result = RequestServlet.Register(account, password);
Message msg = new Message();
msg.what=1;
msg.obj = result;
handler.sendMessage(msg);
}catch (Exception e){
e.printStackTrace();
}
}
}.start();
}
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
if (msg.what == 1) {
registerInfo = (JSONObject) msg.obj;
String result = registerInfo.optString("result");
if(result.equals("注册成功!")){
Toast.makeText(RegisterActivity.this, "注册成功!", Toast.LENGTH_SHORT).show();
RegisterActivity.this.finish();
}else if(result.equals("注册失败!")){
Toast.makeText(RegisterActivity.this, "注册失败,请重新注册", Toast.LENGTH_SHORT).show();
}
}
}
};
}
同登录一样,先声明一些必要的变量
private TextView mTitle; //标题
private TextView mBack; //返回按钮
private Button mRegister; //注册按钮
private EditText mName, mPwd, mPwdAgain; //用户名,密码,再次输入的密码控件
private String userName, pwd, pwdAgain; //用户名,密码,再次输入的密码的控件的值
private JSONObject registerInfo; //注册返回信息
在加载时就初始化界面和事件
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
initView(); //初始化界面
initEvent(); //初始化事件
}
初始化界面,首先需要绑定界面中的控件,如果需要在加载界面时操作界面变化,都是在这里设置
private void initView(){
mTitle = this.findViewById(R.id.tv_main_title);
mBack = this.findViewById(R.id.tv_back);
mRegister = this.findViewById(R.id.btn_register);
mName = this.findViewById(R.id.et_user_name);
mPwd = this.findViewById(R.id.et_psw);
mPwdAgain = this.findViewById(R.id.et_psw_again);
mTitle.setText("注册");
}
初始化事件,主要是监听各个按钮,按钮被点击是做出事件响应
private void initEvent(){
//返回按钮
mBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
RegisterActivity.this.finish();
}
});
//注册按钮
mRegister.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//获取输入框中的值
userName = mName.getText().toString().trim();
pwd = mPwd.getText().toString().trim();
pwdAgain = mPwdAgain.getText().toString().trim();
//先判断输入框是否有值
if (TextUtils.isEmpty(userName)) {
Toast.makeText(RegisterActivity.this, "请输入用户名", Toast.LENGTH_SHORT).show();
return;
} else if (TextUtils.isEmpty(pwd)) {
Toast.makeText(RegisterActivity.this, "请输入密码", Toast.LENGTH_SHORT).show();
return;
} else if (TextUtils.isEmpty(pwdAgain)) {
Toast.makeText(RegisterActivity.this, "请再次输入密码", Toast.LENGTH_SHORT).show();
return;
}
//判断两次输入的密码是否相同
if (!pwd.equals(pwdAgain)) {
Toast.makeText(RegisterActivity.this, "输入两次的密码不一样", Toast.LENGTH_SHORT).show();
return;
}
isExistThread(userName, pwd); //先检测用户名是否已经存在
}
});
}
返回按钮如果被点击,销毁当前界面,返回登录界面。注册按钮被点击,需要向数据库请求数据,因为在Android操作中,所有网络操作必须放在子线程中,所以这里单独写了一个方法。
注册时,首先判断一下输入框内是否都有值。如果都有值,判断两次输入的密码是否相同。在注册前,这里将账户名与数据库内的数据进行了比对,如果存在该用户名,不能再次进行注册。
这里的判断用户名是否已存在与前面介绍的登录在功能实现上是一样的,请求login方法,获取到用户数据。
private void isExistThread(final String account, final String password){
new Thread(){
public void run(){
try {
JSONObject result = RequestServlet.login(account, password);
Message msg = new Message();
msg.what=2;
msg.obj = result;
handler2.sendMessage(msg);
}catch (Exception e){
e.printStackTrace();
}
}
}.start();
}
使用Message把信息传递到主线程,主线程在接收到数据后,对数据进行比对。
private Handler handler2 = new Handler() {
public void handleMessage(android.os.Message msg) {
if (msg.what == 2) {
JSONObject result = (JSONObject) msg.obj;
if(result == null || !result.optString("account").equals(userName)){
//账户名未被注册
registerThread(userName, pwd); //注册新账户
}else{
//账户名已被注册
Toast.makeText(RegisterActivity.this, "用户名已被占用!", Toast.LENGTH_SHORT).show();
}
}
}
};
在判断出账户名未被注册时,启动注册线程。这里调用了RequestServlet类中的Register方法(稍后介绍)
private void registerThread(final String account, final String password){
new Thread(){
public void run(){
try {
JSONObject result = RequestServlet.Register(account, password);
Message msg = new Message();
msg.what=1;
msg.obj = result;
handler.sendMessage(msg);
}catch (Exception e){
e.printStackTrace();
}
}
}.start();
}
子线程将服务端返回的数据传递到主线程上,主线程解析数据后显示是否注册成功。
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
if (msg.what == 1) {
registerInfo = (JSONObject) msg.obj;
String result = registerInfo.optString("result");
System.out.println(result);
if(result.equals("注册成功!")){
Toast.makeText(RegisterActivity.this, "注册成功!", Toast.LENGTH_SHORT).show();
RegisterActivity.this.finish();
}else if(result.equals("注册失败!")){
Toast.makeText(RegisterActivity.this, "注册失败,请重新注册", Toast.LENGTH_SHORT).show();
}
}
}
};
值得注意的是,服务端返回的信息是符合json规范的键值对形式的数据,因此在解析时,取出的也是result关键词对应的值。
RequestServlet
在登录时,调用了这里的login方法,注册调用Register方法,程序与登录程序并无明显区别。
private static final String REGISTER_SERVLET ="http://192.168.43.xxx:8080/musicplayer/SignUp";
//注册
public static JSONObject Register(String account, String password){
JSONObject result = null;
String path = REGISTER_SERVLET+"?account="+account+"&password="+password;
HttpURLConnection conn;
try {
conn = getConn(path);
int code = conn.getResponseCode(); //http相应状态吗,200代表相应成功
if (code == 200){
InputStream stream = conn.getInputStream();
String str = streamToString(stream);
result = getJSON(str);
conn.disconnect();
}
}catch (Exception e){
e.printStackTrace();
}
return result;
}
如果使用虚拟机测试,连接servlet一般使用"http://localhost:8080"或"http://127.0.0.1:8080"。这里我使用了一个局域网做测试,需要将url修改为服务端的ip。windows系统查询本地ip的方法为:cmd–>ipconfig
在login方法中,首先需要根据url获取网络连接HttpURLConnection,根据连接获取文件流,再将文件流转为string型数据,最后转为json型数据,返回用户信息。
4. 测试
测试使用真机测试。环境:Android 10
这里需要启动手机开发者模式,打开手机调试,连接Android studio 服务端启动Tomcat
测试两次密码不同
测试使用已存在的账户名再次注册
使用’qwe’注册
注册成功后,返回登录界面,并提示“注册成功!”
使用’qwe’账户登录
显示登录成功,并跳转到了音乐播放界面
服务端数据库
查看服务端的数据库,可以找到有关账户“qwe”的注册信息
5.
近来发现一些小伙伴不看开头啊。
程序已经放到两个仓库了,觉得有帮助到你的话,就请给本文点个赞,给仓库点个star吧!万分感谢!