【一步一个脚印】Tomcat+MySQL为自己的APP打造服务器(3-1)Android 和 Service 的交互之GET方式

来源:互联网 发布:linux系统密码忘记 编辑:程序博客网 时间:2024/06/05 19:09

      好久没更新了,罪过罪过。最对不起的人莫过于那些支持和等待在下拙文的诸位,在此道一声抱歉。管窥之见,侥幸博得各位认同,给了我莫大的鼓励。

      话休絮烦,文接前章。

      到【一步一个脚印】Tomcat+MySQL为自己的APP打造服务器(2-3)Servlet连接MySQL数据库为止,我们已经将服务端的部分走通了:通过 Servlet 连接 MySQL ,分析业务需求进行响应的增删改查操作返回对应的处理结果。(上一篇结尾是说接下来该说POST请求了,但是在准备这篇文章时发现POST再推后一篇,等我们把 Android 通过 GET 方式和 Servlet 服务器交互全部走完了,回过头来对比着说 POST 会更加明了,所以决定修正一下之前的思路,本章我们继续完成 GET 的剩下内容)

      很明显,想要 Android 和服务器进行交互,必然要使用到网络,为了解决后顾之忧,我们先下手为强,在 Manifest 文件中声明网络访问权限

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

      这个权限可不是平白无故就去申请的,因为我们要通过网络和服务器交互,要完成这一交互过程,就要用到 Android 网络技术。Android 网络技术包含目前所有主流网络技术,比如你听过的TCP/IP(Socket、ServiceSocket)、UDP... ...(妈的,不写了。讲真,网络这块其实我已经不懂了,曾经真的懂过,反正大学时候网络基础学的挺嗨,几年不接触已经恍如隔世了。以免误人子弟,或者是遇到真正的大神被拆穿就尴尬了大笑)。我们最常用的应该算是 HTTP 和 WebView 了,这里就以最常用的 HTTP 通信为例来说明:

      在 Android 上发送 HTTP 请求的方式一般有两种:HttpURLConnectionHttpClient,我们都来试用一下:


(一)HttpURLConnection 进行 HTTP 请求

      先是 HttpURLConnection,直接上代码吧,用法有注释:

public class HttpURLConActivity extends AppCompatActivity {    private TextView tvContent;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_http_urlcon);        tvContent = (TextView) findViewById(R.id.tv_content); // 这里页面上就一个简单的TextView,用于展示获取到报文内容        requestUsingHttpURLConnection();    }    private void requestUsingHttpURLConnection() {        // 网络通信属于典型的耗时操作,开启新线程进行网络请求        new Thread(new Runnable() {            @Override            public void run() {                HttpURLConnection connection = null;                try {                    URL url = new URL("https://www.baidu.com"); // 声明一个URL,注意——如果用百度首页实验,请使用https                    connection = (HttpURLConnection) url.openConnection(); // 打开该URL连接                    connection.setRequestMethod("GET"); // 设置请求方法,“POST或GET”,我们这里用GET,在说到POST的时候再用POST                    connection.setConnectTimeout(8000); // 设置连接建立的超时时间                    connection.setReadTimeout(8000); // 设置网络报文收发超时时间                    InputStream in = connection.getInputStream();  // 通过连接的输入流获取下发报文,然后就是Java的流处理                    BufferedReader reader = new BufferedReader(new InputStreamReader(in));                    StringBuilder response = new StringBuilder();                    String line;                    while ((line = reader.readLine()) != null){                        response.append(line);                    }                    tvContent.setText(response.toString()); // 地雷                } catch (MalformedURLException e) {                    e.printStackTrace();                } catch (IOException e) {                    e.printStackTrace();                }            }        }).start();    }}
      Run,Fuck!报错——

      子线程操作UI报错

        典型的子线程试图操作 UI 元素报错,为啥,因为网络请求是在新开的子线程中运行,当然不能直接拿到结果就给 TextView 赋值了!怎么做?Android 的Handler消息机制这不就用上了嘛!

    /**     * 消息处理     */    private Handler handler = new Handler(){        @Override        public void handleMessage(Message msg) {            if(msg.what == 1){                tvContent.setText(msg.obj.toString());            }        }    };    private void requestUsingHttpURLConnection() {        ......        /* 获取返回报文部分省略,将原来         * tvContent.setText(response.toString())替换为         * 给handler发送消息         */        Message msg = new Message();        msg.what = 1;        msg.obj = response.toString();        Log.e("WangJ", response.toString());        handler.sendMessage(msg);        ......    }
        重新 Run,结果

        百度首页报文

      什么?看不懂,什么鬼!其实服务器返回的百度首页就是这样的 HTML 代码,只是平时我们使用浏览器打开的时候,浏览器引擎帮我们把这些代码解析和展示成了花花绿绿的页面,仅此而已。


(二)HttpClient 进行 HTTP 请求

      HttpClient 是Apache 提供的 HTTP 网络访问接口,但是原生 Android 系统内置了这套借口,所以不用引入第三方 jar 就可以直接用。他可以和 HttpURLConnection 完成几乎一模一样的效果,但是两者的使用方法还是有一些区别的。下面我们用代码来说明:

public class HttpClientActivity extends AppCompatActivity {    private TextView tvContent;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_http_client);        tvContent = (TextView) findViewById(R.id.tv_content);        requestUsingHttpClient();    }    // 同样的消息机制    private Handler handler = new Handler() {        @Override        public void handleMessage(Message msg) {            if (msg.what == 1) {                tvContent.setText(msg.obj.toString());            }        }    };    private void requestUsingHttpClient() {        new Thread(new Runnable() {            @Override            public void run() {                HttpClient client = new DefaultHttpClient(); // HttpClient 是一个接口,无法实例化,所以我们通常会创建一个DefaultHttpClient实例                HttpGet get = new HttpGet("https://www.baidu.com"); // 发起GET请求就使用HttpGet,发起POST请求则使用HttpPost,这里我们先使用HttpGet                try {                    HttpResponse httpResponse = client.execute(get); // 调用HttpClient对象的execute()方法                    // 状态码200说明响应成功                    if (httpResponse.getStatusLine().getStatusCode() == 200) {                        HttpEntity entity = httpResponse.getEntity(); // 取出报文的具体内容                        String response = EntityUtils.toString(entity, "utf-8"); // 报文编码                        // 发送消息                        Message msg = new Message();                        msg.what = 1;                        msg.obj = response;                        handler.sendMessage(msg);                    }                } catch (IOException e) {                    e.printStackTrace();                }            }        }).start();    }}

        怎么样,还是挺简洁的吧!Run,和前一个图一样,节省篇幅图就不贴了。


(三)HttpClient 的窘境

      年轻人,是不是要到问题了偷笑(没遇到问题的请自觉忽略,如果你用的 Android Studio比较新,compileSdkVersion >= 23,相信你会遇到的)?是不是在写代码中找不到 HttpClient 类?那就对了!因为从 Android 6.0(API 23) 往后 Google 又把 HttpClient 给干掉了,为什么?说是因为它接口、方法太多,API太过复杂,升级维护难以在当前版本API上进行,这就会导致 Android 版本兼容上出现难以解决的问题,所以就把它干掉了,因为使用 HttpURLConnection 也能达到同样的效果,并且易于维护。啰嗦个屁呀,问题咋解决呢?改 SDK 版本呗,让他SDK <= 22 就可以了。什么!业务不允许?要求最新版 SDK?别怕,到 Apache 下载最新 jar 包导入工程,还像以前一样用,不过方法名可能不一样了。

      就这样,Android 发送 HTTP 请求就完成了。什么?消息处理机制太麻烦了?是的!我也举得麻烦,其实 Android 官方也觉得麻烦,所以 Android 为了降低这个开发难度,提供了AsyncTask。AsyncTask就是一个封装过的后台任务类,顾名思义就是异步任务,其实现原理也是基于异步消息处理机制,只是 Android给我们做了很好的封装而已,相对于 Handler 更轻量,适用于简单的异步处理,但是在面对多个异步任务更新同一个或同一组 UI 时的同步就比较困难,不了解不要紧,以后用用就知道问题在哪了,一口也吃不成个大胖子。下面我们在 http 请求时就用AsyncTask来处理吧——


(四)Android 和 Servlet 服务器通过 HTTP GET 模式进行交互

      来吧,来到了今天的主题。首先,请确保你的 Tomcat 上部署的 Servlet 已经启动,确保数据库服务正常启动并且数据库连接正常,有问题请参考之前的 【一步一个脚印】Tomcat+MySQL为自己的APP打造服务器。

      (双12换了电脑,环境都是新装的,如与之前的数据不符,请以自己的为准

      数据库表就是这么一个简单的表

        数据库表结构

      下边是服务器Servlet的代码,其实和之前文章中的代码原理上一模一样,但是为了写一个完整的交互,我这里重写了一个:

      处理“注册”逻辑的Servlet:

@WebServlet(description = "注册使用的Servlet", urlPatterns = { "/RegisterServlet" })public class RegisterServlet extends HttpServlet {private static final long serialVersionUID = 1L;/** * Default constructor. */public RegisterServlet() {LogUtil.log("RegisterServlet construct...");}@Overrideprotected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {String method = request.getMethod();if ("GET".equals(method)) {LogUtil.log("请求方法:GET");doGet(request, response);} else if ("POST".equals(method)) {LogUtil.log("请求方法:POST");doPost(request, response);} else {LogUtil.log("请求方法分辨失败!");}}/** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse *      response) */protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {String code = "";String message = "";String account = request.getParameter("account");String password = request.getParameter("password");LogUtil.log(account + ";" + password);Connection connect = DatabaseUtil.getConnection();try {Statement statement = connect.createStatement();String sql = "select account from " + DatabaseUtil.Table_Account + " where account='" + account + "'";LogUtil.log(sql);ResultSet result = statement.executeQuery(sql);if (result.next()) { // 能查到该账号,说明已经注册过了code = "100";message = "该账号已存在";} else {String sqlInsert = "insert into " + DatabaseUtil.Table_Account + "(account, password) values('"+ account + "', '" + password + "')";LogUtil.log(sqlInsert);if (statement.executeUpdate(sqlInsert) > 0) { // 否则进行注册逻辑,插入新账号密码到数据库code = "200";message = "注册成功";} else {code = "300";message = "注册失败";}}} catch (SQLException e) {e.printStackTrace();}response.getWriter().append("code:").append(code).append(";message:").append(message);}/** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse *      response) */protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {}@Overridepublic void destroy() {LogUtil.log("RegisterServlet destory.");super.destroy();}}
      处理“登录”逻辑的Servlet:
@WebServlet(description = "登录", urlPatterns = { "/LoginServlet" })public class LoginServlet extends HttpServlet {private static final long serialVersionUID = 1L;/** * @see HttpServlet#HttpServlet() */public LoginServlet() {super();}/** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse *      response) */protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {String code = "";String message = "";String account = request.getParameter("account");String password = request.getParameter("password");LogUtil.log(account + ";" + password);Connection connect = DatabaseUtil.getConnection();try {Statement statement = connect.createStatement();String sql = "select account from " + DatabaseUtil.Table_Account + " where account='" + account+ "' and password='" + password + "'";LogUtil.log(sql);ResultSet result = statement.executeQuery(sql);if (result.next()) { // 能查到该账号,说明已经注册过了code = "200";message = "登陆成功";} else {code = "100";message = "登录失败,密码不匹配或账号未注册";}} catch (SQLException e) {e.printStackTrace();}response.getWriter().append("code:").append(code).append(";message:").append(message);}/** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse *      response) */protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {LogUtil.log("不支持POST方法");}}
      /*请求和响应编码格式设置数据库连接代码和之前的一样,这里就不重复贴了 */

     接下来是Android客户端的代码:

     常量类

public class Constant {    public static String URL = "http://192.168.1.109:8080/FirstServletService/"; // IP地址请改为你自己的IP    public static String URL_Register = URL + "RegisterServlet";    public static String URL_Login = URL + "LoginServlet";}
      Activity的界面
<?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:gravity="center"    android:orientation="vertical"    android:padding="@dimen/activity_vertical_margin">    <EditText        android:id="@+id/et_account"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:hint="请输入账号" />    <EditText        android:id="@+id/et_password"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:hint="请输入登录密码"        android:inputType="textPassword" />    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:gravity="center_horizontal"        android:orientation="horizontal">        <Button            android:id="@+id/btn_register"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="Register" />        <Button            android:id="@+id/btn_login"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="Login" />    </LinearLayout>    <!-- 用来显示报文返回结果 -->    <TextView        android:id="@+id/tv_result"        android:layout_width="match_parent"        android:layout_height="wrap_content" /></LinearLayout>
      Activity的代码
public class MainActivity extends Activity {    private EditText etAccount;    private EditText etPassword;    private TextView tvResult;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        etAccount = (EditText) findViewById(R.id.et_account);        etPassword = (EditText) findViewById(R.id.et_password);        tvResult = (TextView) findViewById(R.id.tv_result);        Button btnRegister = (Button) findViewById(R.id.btn_register);        btnRegister.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                if (!StringUtil.isEmpty(etAccount.getText().toString())                        && !StringUtil.isEmpty(etPassword.getText().toString())) {                    Log.e("WangJ", "都不空");                    register(etAccount.getText().toString(), etPassword.getText().toString());                } else {                    Toast.makeText(MainActivity.this, "账号、密码都不能为空!", Toast.LENGTH_SHORT).show();                }            }        });        Button btnLogin = (Button) findViewById(R.id.btn_login);        btnLogin.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                if (!StringUtil.isEmpty(etAccount.getText().toString())                        && !StringUtil.isEmpty(etPassword.getText().toString())) {                    Log.e("WangJ", "都不空");                    login(etAccount.getText().toString(), etPassword.getText().toString());                } else {                    Toast.makeText(MainActivity.this, "账号、密码都不能为空!", Toast.LENGTH_SHORT).show();                }            }        });    }    private void register(String account, String password) {        String registerUrlStr = Constant.URL_Register + "?account=" + account + "&password=" + password;        new MyAsyncTask(tvResult).execute(registerUrlStr);    }    private void login(String account, String password) {        String registerUrlStr = Constant.URL_Login + "?account=" + account + "&password=" + password;        new MyAsyncTask(tvResult).execute(registerUrlStr);    }    /**     * AsyncTask类的三个泛型参数:     * (1)Param 在执行AsyncTask是需要传入的参数,可用于后台任务中使用     * (2)后台任务执行过程中,如果需要在UI上先是当前任务进度,则使用这里指定的泛型作为进度单位     * (3)任务执行完毕后,如果需要对结果进行返回,则这里指定返回的数据类型     */    public static class MyAsyncTask extends AsyncTask<String, Integer, String> {        private TextView tv; // 举例一个UI元素,后边会用到        public MyAsyncTask(TextView v) {            tv = v;        }        @Override        protected void onPreExecute() {            Log.w("WangJ", "task onPreExecute()");        }        /**         * @param params 这里的params是一个数组,即AsyncTask在激活运行是调用execute()方法传入的参数         */        @Override        protected String doInBackground(String... params) {            Log.w("WangJ", "task doInBackground()");            HttpURLConnection connection = null;            StringBuilder response = new StringBuilder();            try {                URL url = new URL(params[0]); // 声明一个URL,注意如果用百度首页实验,请使用https开头,否则获取不到返回报文                connection = (HttpURLConnection) url.openConnection(); // 打开该URL连接                connection.setRequestMethod("GET"); // 设置请求方法,“POST或GET”,我们这里用GET,在说到POST的时候再用POST                connection.setConnectTimeout(80000); // 设置连接建立的超时时间                connection.setReadTimeout(80000); // 设置网络报文收发超时时间                InputStream in = connection.getInputStream();  // 通过连接的输入流获取下发报文,然后就是Java的流处理                BufferedReader reader = new BufferedReader(new InputStreamReader(in));                String line;                while ((line = reader.readLine()) != null) {                    response.append(line);                }            } catch (MalformedURLException e) {                e.printStackTrace();            } catch (IOException e) {                e.printStackTrace();            }            return response.toString(); // 这里返回的结果就作为onPostExecute方法的入参        }        @Override        protected void onProgressUpdate(Integer... values) {            // 如果在doInBackground方法,那么就会立刻执行本方法            // 本方法在UI线程中执行,可以更新UI元素,典型的就是更新进度条进度,一般是在下载时候使用        }        /**         * 运行在UI线程中,所以可以直接操作UI元素         * @param s         */        @Override        protected void onPostExecute(String s) {            Log.w("WangJ", "task onPostExecute()");            tv.setText(s);        }    }}
      代码很简单,没什么解释的。本文的主要内容是将数据库、服务器、Android串联起来这一过程。

      *注意* 这里我们不可能通过移动网络来连接我们的服务器,因为我们的服务器部署在本机本地,没有公网IP,所以这里我们用自己的电脑开启一个共享热点,然后用手机连上这个热点来进行访问本机服务器,IP地址通过ipconfig命令查看。开启网络热点自己百度吧,命令行不行直接找360免费WIFI等等,这篇已经够啰嗦了,就不再加入其他干扰内容了,请关注本文的重点。

      运行前再检查一遍:(1)数据库连接正常(2)服务器运行正常(3)Android端访问的本机服务器地址可连通。然后Run,看结果

        运行结果

      就这样就完事了,其实篇幅不短,内容却不多。到此GET方式的交互就完成了,同理,POST交互也是依葫芦画瓢,下一篇我们就来说说POST方式的交互。

      佶屈聱牙,作抛砖引玉之用,水平有限,如有不足欢迎留言指正!

        

8 0
原创粉丝点击