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仓库

《嘟嘟音乐》是我自己写的Android,若有不足之处请大家谅解 1.首页实现读取本地Music本地文件夹中的MP3文件然后放进listview里面 2.实现了用户用户登录注册页面,登录过后用户可以进行自己的信息修改,实现了服务器的数据交互问题 3.实现了管理员登录,使用的是与Tomcat服务器进行数据交互验证信息的正确性,我使用的是myeclipse进行布置的服务器信息,此时使用的是SQLserver 2008 数据库存储的管理员的信息,读取完以后然后返回到管理界面 4.在嘟嘟音乐的管理界面,我实现了策划菜单以及卡片式布局来管理普通用户信息,以及用户信息的增删改查。查询使用的是以UserId或者昵称迷糊查询的方式进行查询的。 5.我使用的SQL server2008 的数据库文件我已经全部导出了,大家可以自行进行导入 6.我使用的myeclipse的项目是Servlet进行的验证app管理员信息的邓丽,项目我已经全部导出。放在压缩包里面 7.我使用的是Android Studio,我把文件布局截图放在压缩包里面,还有几个需要注意的地方,特别的坑,尤其是大家需要注意build.gridle(app)这里面大家需要注册导包,你并且配置好自己的SDK。大家还需要注意就是Android注册文件里面也需要注意,关于一些权限的问题 8.哈哈,大概的的就写到这里了,写的不好的地方大家多见谅,我也是在学习阶段,把自己的写的东西给大家贡献出来以供大家参考学习使用。我还在压缩包里面放置了视频演示的链接信息,大家可以看看。如果感觉写的不错的话,请好评哦。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纸照片

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值