Android音乐播放器开发(2)—登录

Android音乐播放器开发(2)—登录

1. 说明

本音乐播放器基于Android开发,原为我和另外两个小伙伴在上学期间一起做的一个小项目,近来有时间整理一下。之前我有文章已经介绍了播放界面的功能实现(Android音乐播放器开发),但介绍的比较粗糙,接下来会做更细致化的整理。源码已同步到Gitee仓库GitHub仓库,觉得还不错的话帮忙点个“star”吧,非常感谢。

服务端使用的是比较传统的servlet和jdbc传递数据,整理完之后,新版本会修改为SSM框架,更加简洁高效。安卓端使用的也都是基础的工具,比如音乐播放功能的实现也是借助于入门级的MediaPlayer类,目前关于安卓端没有什么更改的想法。

服务端:Android音乐播放器开发–服务端

(适用于平时做个小课设的小伙伴们)

2. 登录界面设计

  1. 新建一个空白activity

image-20201019225008602

  1. 分析一下需求

一般的登录界面都需要输入账户和密码,还需要发起登录申请的按钮,另外,还需要启动注册和修改密码的按钮。

具体如下图所示。

image-20201019210041639

布局文件(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:background="@drawable/b" android:orientation="vertical">
    <!--标题栏-->
    <include layout="@layout/main_title_bar"></include>
    <!--显示头像,这里把头像定为了固定图像,这里完全可以为每个账户挑选各自的头像-->
    <ImageView android:id="@+id/iv_head" android:layout_width="70dp" android:layout_height="70dp" android:layout_marginTop="25dp" android:layout_gravity="center_horizontal" android:background="@drawable/x"/>
    <!--输入框-->
    <EditText android:id="@+id/et_user_name" android:layout_width="fill_parent" android:layout_height="48dp" android:layout_marginTop="35dp" android:layout_marginLeft="35dp" android:layout_marginRight="35dp" android:layout_gravity="center_horizontal" android:background="@drawable/login_user_name_bg" android:drawableLeft="@drawable/user_name_icon" android:drawablePadding="10dp" android:paddingLeft="8dp" android:gravity="center_vertical" android:hint="请输入用户名" 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_height="48dp" android:layout_gravity="center_horizontal" android:layout_marginLeft="35dp" android:layout_marginRight="35dp" android:background="@drawable/login_psw_bg" android:drawableLeft="@drawable/psw_icon" android:drawablePadding="10dp" android:paddingLeft="8dp" android:gravity="center_vertical" android:hint="请输入密码" android:inputType="textPassword" android:singleLine="true" android:textColor="#000000" android:textColorHint="#a3a3a3" android:textSize="14sp"/>
    <!--上面inputType设置为textPassword,在输入密码时就会隐藏密码 -->
        
    <!--按钮-->
    <Button android:id="@+id/btn_login" android:layout_width="fill_parent" android:layout_height="40dp" android:layout_marginTop="15dp" android:layout_marginLeft="35dp" android:layout_marginRight="35dp" android:layout_gravity="center_horizontal" android:background="@drawable/register_selector" android:text="登 录" android:textColor="@android:color/white" android:textSize="18sp"/>
    <!--显示tv register , find_psw -->
    <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_marginTop="8dp" android:layout_marginLeft="35dp" android:layout_marginRight="35dp" android:gravity="center_horizontal" android:orientation="horizontal">
        <TextView android:id="@+id/tv_register" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center_horizontal" android:padding="8dp" android:text="立即注册" android:textColor="@android:color/white" android:textSize="14sp" />
        <!--layout_weight="1" layout_width="0dp"实现均分效果-->
        <TextView android:id="@+id/tv_find_psw" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center_horizontal" android:padding="8dp" android:text="修改密码" android:textColor="@android:color/white" android:textSize="14sp" />
    </LinearLayout>

</LinearLayout>

大致界面和布局如下图所示:(左为登录界面,右为布局效果)

image-20201019224237924

标题栏使用了一个单独的xml文件(main_title_bar.xml),使用时直接使用include导入即可。

<?xml version="1.0" encoding="utf-8"?>
<!--标题栏与返回键的创建,独立在main_title_bar.xml中-->
<!--标题栏设置高度为50dp,宽度为match_parent,设置背景颜色为透明 @android:color/transparent-->
<!--RelativeLayout为相对布局-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/title_bar" android:layout_width="match_parent" android:layout_height="50dp" android:background="@android:color/transparent">
    <!--设置返回键TextView为高度50dp,宽度为50dp;id为android:id="@+id/tv_back"-->
    <!--layout_alignParentLeft为与父控件左对齐-->
    <!--layout_centerVertical为控件垂直居中-->
    <!--标题栏界面中的返回键在按下与弹起时,返回键会有明显的区别,这种效果通过背景选择器进行实现-->
    <TextView android:id="@+id/tv_back" android:layout_width="50dp" android:layout_height="50dp" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:background="@drawable/go_back_selector"/>
    <!--设置id为android:id="@+id/tv_main_title-->
    <!--该TextView为显示文本-->
    <!--layout_centerInparent为居中显示-->
    <TextView android:id="@+id/tv_main_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@android:color/white" android:textSize="20sp" android:layout_centerInParent="true"/>
</RelativeLayout>

3. 登录界面功能实现

先贴全部代码

public class LoginActivity extends Activity {
   

    //一些声明
    private TextView mMainTitle;   //主标题
    private TextView mBack;   //返回
    private TextView mRegister;  //注册
    private TextView mChangePwd;  //修改密码
    private Button mLogin;   //登录按钮
    private EditText mUserName;  //输入账户
    private EditText mPwd;   //输入密码
    private String userName, pwd;     //登录所需的账户和密码

    @Override
    protected void onCreate(Bundle savedInstanceState) {
   
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        initView();  //初始化一些控件

        initEvent();  //初始化相关事件
    }

    private void initEvent(){
   

        //监听返回键的点击事件
        mBack.setOnClickListener(new View.OnClickListener() {
   
            @Override
            public void onClick(View view) {
   
                //登录界面销毁
                LoginActivity.this.finish();
            }
        });

        //注册按钮
        mRegister.setOnClickListener(new View.OnClickListener() {
   
            @Override
            public void onClick(View view){
   
                //跳转到注册界面
                Intent intent=new Intent(LoginActivity.this,RegisterActivity.class);
                startActivity(intent);
            }
        });

        //修改密码按钮
        mChangePwd.setOnClickListener(new View.OnClickListener() {
   
            @Override
            public void onClick(View view) {
   
                //跳转到修改密码界面
                Intent intent = new Intent(LoginActivity.this,ChangePwdActivity.class);
                startActivity(intent);
            }
        });

        //登录按钮
        mLogin.setOnClickListener(new View.OnClickListener() {
   
            @Override
            public void onClick(View view) {
   
                userName=mUserName.getText().toString().trim();  //获取用户名。.trim是为了去除字符串两侧对于空格
                pwd=mPwd.getText().toString().trim();  //获取登录密码

                //检测是否为空字符串
                if(TextUtils.isEmpty(userName)){
   
                    Toast.makeText(LoginActivity.this, "请输入用户名", Toast.LENGTH_SHORT).show();
                    return;
                }else if(TextUtils.isEmpty(pwd)){
   
                    Toast.makeText(LoginActivity.this, "请输入密码", Toast.LENGTH_SHORT).show();
                    return;
                }

                loginThread();
            }
        });
    }

    private void loginThread(){
   
        //涉及到网络的请求都需要在子线程内完成
        new Thread(){
   
            public void run(){
   
                try {
   
                    JSONObject result = RequestServlet.login(userName, pwd);

                    //将数据传递到主线程
                    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(Message msg) {
   
            if (msg.what == 1) {
   
                JSONObject result = (JSONObject) msg.obj;

                if(result == null){
   
                    Toast.makeText(LoginActivity.this, "账户错误", Toast.LENGTH_SHORT).show();
                }
                else{
   
                    String password = result.optString("password");
                    if(pwd.equals(password)){
   
                        //密码正确
                        Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();

                        //传递数据
                        Intent intent = new Intent(LoginActivity.this, MainActivity.class);
                        intent.putExtra("result",result.toString());   //把用户信息传递到播放界面

                        //销毁登录界面
                        LoginActivity.this.finish();

                        //跳转到主界面,登录成功的状态传递到 MainActivity 中
                        startActivity(intent);
                        return;
                    }
                    else{
   
                        Toast.makeText(LoginActivity.this, "密码错误", Toast.LENGTH_SHORT).show();
                    }
                }
            }
        }
    };

    private void initView(){
   
        mMainTitle = this.findViewById(R.id.tv_main_title);
        mBack = this.findViewById(R.id.tv_back);
        mRegister = this.findViewById(R.id.tv_register);
        mChangePwd = this.findViewById(R.id.tv_change_psw);
        mLogin = this.findViewById(R.id.btn_login);
        mUserName = this.findViewById(R.id.et_user_name);
        mPwd = this.findViewById(R.id.et_psw);

        mMainTitle.setText("登录");
    }
}

声明一些变量,是为了绑定界面中的控件,实现功能

private TextView mMainTitle;   //主标题
private TextView mBack;   //返回
private TextView mRegister;  //注册
private TextView mChangePwd;  //修改密码
private Button mLogin;   //登录按钮
private EditText mUserName;  //输入账户
private EditText mPwd;   //输入密码
private String userName, pwd;     //登录所需的账户和密码

加载时就需要初始化界面和一些事件

protected void onCreate(Bundle savedInstanceState) {
   
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_login);

    initView();  //初始化一些控件

    initEvent();  //初始化相关事件
}

初始化界面,首先需要绑定界面中的控件,如果需要在加载界面时操作界面变化,都是在这里设置

private void initView(){
   
    mMainTitle = this.findViewById(R.id.tv_main_title);
    mBack = this.findViewById(R.id.tv_back);
    mRegister = this.findViewById(R.id.tv_register);
    mChangePwd = this.findViewById(R.id.tv_change_psw);
    mLogin = this.findViewById(R.id.btn_login);
    mUserName = this.findViewById(R.id.et_user_name);
    mPwd = this.findViewById(R.id.et_psw);

    mMainTitle.setText("登录");
}

初始化事件,主要是监听各个按钮,按钮被点击时做出事件响应

private void initEvent(){
   

    //监听返回键的点击事件
    mBack.setOnClickListener(new View.OnClickListener() {
   
        @Override
        public void onClick(View view) {
   
            //登录界面销毁
            LoginActivity.this.finish();
        }
    });

    //注册按钮
    mRegister.setOnClickListener(new View.OnClickListener() {
   
        @Override
        public void onClick(View view){
   
            //跳转到注册界面
            Intent intent=new Intent(LoginActivity.this,RegisterActivity.class);
            startActivity(intent);
        }
    });

    //修改密码按钮
    mChangePwd.setOnClickListener(new View.OnClickListener() {
   
        @Override
        public void onClick(View view) {
   
            //跳转到修改密码界面
            Intent intent = new Intent(LoginActivity.this,ChangePwdActivity.class);
            startActivity(intent);
        }
    });

    //登录按钮
    mLogin.setOnClickListener(new View.OnClickListener() {
   
        @Override
        public void onClick(View view) {
   
            userName=mUserName.getText().toString().trim();  //获取用户名。.trim是为了去除字符串两侧对于空格
            pwd=mPwd.getText().toString().trim();  //获取登录密码

            //检测是否为空字符串
            if(TextUtils.isEmpty(userName)){
   
                Toast.makeText(LoginActivity.this, "请输入用户名", Toast.LENGTH_SHORT).show();
                return;
            }else if(TextUtils.isEmpty(pwd)){
   
                Toast.makeText(LoginActivity.this, "请输入密码", Toast.LENGTH_SHORT).show();
                return;
            }

            loginThread();
        }
    });
}

返回按钮如果被点击,销毁当前界面,返回终端主界面。注册、修改密码按钮被点击,跳转到各自界面。登录按钮被点击,需要向数据库请求数据,因为在Android操作中,所有网络操作必须放在子线程中,所以这里单独写了一个方法。

private void loginThread(){
   
    //涉及到网络的请求都需要在子线程内完成
    new Thread(){
   
        public void run(){
   
            try {
   
                JSONObject result = RequestServlet.login(userName, pwd);

                //将数据传递到主线程
                Message msg = new Message();
                msg.what=1;
                msg.obj = result;
                handler.sendMessage(msg);
            }catch (Exception e){
   
                e.printStackTrace();
            }
        }
    }.start();
}

与服务端进行交互的部分单独写了一个类RequestServlet,稍后会做介绍。这里介绍一下Message,由于子线程不能操作UI,只有主线程可以进行UI操作,因此子线程获取到的数据需要传递到主线程,而Message就是主线程和子线程传递数据的载体,它封装了需要传递的数据。

主线程使用Handler解析封装的数据。上面调用login方法,会在服务端获取一个User对象,封装了用户信息。如果返回的用户信息为null,那么在数据库中并不存在当前账户,需要做出提示”账户错误”。如果用户信息不为空,则需要进一步对比密码信息,如果密码错误,提示错误信息,如果密码也正确,就跳转到音乐播放界面(Intent),同时将用户信息一并传递到播放界面。

private Handler handler = new Handler() {
   
    public void handleMessage(Message msg) {
   
        if (msg.what == 1) {
   
            JSONObject result = (JSONObject) msg.obj;

            if(result == null){
   
                Toast.makeText(LoginActivity.this, "账户错误", Toast.LENGTH_SHORT).show();
            }
            else{
   
                String password = result.optString("password");
                if(pwd.equals(password)){
   
                    //密码正确
                    Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();

                    //传递数据
                    Intent intent = new Intent(LoginActivity.this, MainActivity.class);
                    intent.putExtra("result",result.toString());   //把用户信息传递到播放界面

                    //销毁登录界面
                    LoginActivity.this.finish();

                    //跳转到主界面,登录成功的状态传递到 MainActivity 中
                    startActivity(intent);
                    return;
                }
                else{
   
                    Toast.makeText(LoginActivity.this, "密码错误", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }
};

RequestServlet

一个类,用于连接服务端,获取信息。

先贴出所有代码(目前只有login方法,后续完善其它功能时再做补充)。

public class RequestServlet {
   

    private static final String LOGIN_SERVLET = "http://192.168.43.xxx:8080/musicplayer/login"; 
    private static HttpURLConnection conn;
    private static JSONObject JSONobj;

    public static HttpURLConnection getConn(String path){
   
        try {
   
            URL url = new URL(path);
            conn = (HttpURLConnection) url.openConnection();
            //设置请求方式
            conn.setRequestMethod("GET");
            //超时时间
            conn.setConnectTimeout(5000);
        }catch (Exception e){
   
            e.printStackTrace();
        }
        return conn;
    }

    private static JSONObject getJSON(String str){
   
        try {
   
            JSONobj = new JSONObject(str);
        }catch (Exception e){
   
            e.printStackTrace();
        }
        return JSONobj;
    }

    //解析输入流为String
    public static String streamToString(InputStream is) {
   
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));

        //new一个StringBuffer用于字符串拼接
        StringBuffer sb = new StringBuffer();
        String line = null;
        try {
   
            //当输入流内容读取完毕时
            while ((line = reader.readLine()) != null) {
   
                sb.append(line);
            }
            //关闭流数据
            is.close();
            reader.close();
            return sb.toString();
        } catch (IOException e) {
   
            e.printStackTrace();
        }
        return null;
    }

    //登录
    public static JSONObject login(String account, String password){
   
        JSONObject result = null;
        String path = LOGIN_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;
    }
}

在这里,LOGIN_SERVLET是连接servlet固定不变的部分,因此将其作为一个常量,后面接相应参数。

private static final String LOGIN_SERVLET = "http://192.168.43.xxx:8080/musicplayer/login"; 

如果使用虚拟机测试,连接servlet一般使用”http://localhost:8080″或”http://127.0.0.1:8080″。这里我使用了一个局域网做测试,需要将url修改为服务端的ip。windows系统查询本地ip的方法为:cmd–>ipconfig

image-20201024165236866

login方法中,首先需要根据url获取网络连接HttpURLConnection,根据连接获取文件流,再将文件流转为string型数据,最后转为json型数据,返回用户信息。

public static JSONObject login(String account, String password){
   
    JSONObject result = null;
    String path = LOGIN_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;
}

4. 测试

测试使用真机测试。环境:Android 10

AndroidManifest.xml中将登录界面设置为软件启动界面

image-20201024171146782

这里需要启动手机开发者模式,打开手机调试,连接Android studio

服务端启动Tomcat,客户端输出“cun”的账户名和密码做测试。

image-20201024171926116

测试结果表明,程序运行成功。

image-20201024171945287

5. 测试中遇到的问题系列

  1. 报错:java.io.IOException: Cleartext HTTP traffic to localhost not permitted

从Android 6.0开始引入了对Https的推荐支持,与以往不同,Android P的系统上面默认所有Http的请求都被阻止了。

解决方法:在AndroidManifest.xml中添加设置,允许使用Http请求。

android:usesCleartextTraffic="true"

image-20201024172711090

  1. 报错:java.net.SocketException: socket failed: EACCES

没有下载文件的权限

解决方法:在AndroidManifest.xml中添加权限

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

6.

近来发现一些小伙伴不看开头啊。

程序已经放到两个仓库了,觉得有帮助到你的话,就请给本文点个赞,给仓库点个star吧!万分感谢!

Gitee仓库

GitHub仓库

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论