FOE电影购票APP之Android客户端


  终于来到本次系列学习的尾声了,这次,我将会和大家一起做一个简单的Android APP,实现通过服务器访问数据库。

  还记得上一个博客给大家提前剧透的demo吗,不记得没关系~我再放一次。



基于Android的影院售票系统 基于android的电影售票app_服务器


  首先,我用的Android开发工具是Android Studio。用Eclispe的读者,你们看懂代码就好。

  我这里就不说怎么配置JDK,ADT之类的,包括layout文件的布局,甚至是跳转过去的listView, adapter之类的知识。我默认你们都会简单的Android开发咯。哈哈。我在本次博客的侧重点是,如何建立一个connection,并且访问服务器,获得我们想要的数据

  首先是第一个页面的登录功能,我们给登录按钮一个监听事件。

Button btnLogin = (Button)findViewById(R.id.ButtonLogin);//获取登录按钮
        btnLogin.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                String usernmae = etUsername.getText().toString();
                String password = etPassword.getText().toString();
                if (usernmae.isEmpty()) {
                    Toast.makeText(MainActivity.this, "用户名为空", Toast.LENGTH_SHORT).show();
                } else if (password.isEmpty()) {
                    Toast.makeText(MainActivity.this, "密码为空", Toast.LENGTH_SHORT).show();
                } else { 
                    Login(usernmae, password); //在用户名和密码不为空的前提下执行Login函数。
                }
            }
        });

  这里给大家再巩固一个知识,就是Android开发中的互联网访问是不可以在主线程中执行的,其实很多其他开发都一样,因为上网需要时间,下载资源等很容易会阻塞主线程,像Uinty的话,也是要放在协程中进行的。而在Android中,也提供了一些方法给我们简单的新开线程并且与界面进行交互。其中,我这次用的是异步任务类(AsyncTask)。

  我们来看看Login函数

private void Login(String username, String password) {
        String loginUrl = LoginURL + "?Username=" + username + "&Password=" + password;
        new MyAsyncTask(MainActivity.this, username).execute(loginUrl);
    }

  很简单,哦,对了,大家记得在manifest中申请网络权限

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

  回过头来,我们的Login函数就是将我们的MainActivity的上下文和username作为MyAyncTask的构造函数参数来构造一个MyAyncTask实例,然后执行execyte(loginUrl)。

  现在我们看一下我们的MyAyncTask类

public static class MyAsyncTask extends AsyncTask<String, Integer, String> {
        private Context context;
        private String uName;

        public  MyAsyncTask(Context context, String username) {
            this.context = context;
            this.uName = username;
        }

        @Override
        protected void onPreExecute() {

        }

        @Override
        protected  String doInBackground(String... params) {
            HttpURLConnection connection = null;
            StringBuilder response = new StringBuilder();
            try {
                URL url = new URL(params[0]);
                connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("GET");
                connection.setConnectTimeout(8000);
                connection.setReadTimeout(8000);
                InputStream in = connection.getInputStream();
                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();
        }

        @Override
        protected  void  onProgressUpdate(Integer... Values) {

        }

        @Override
        protected void onPostExecute(String s) {
            String text = s.substring(s.indexOf("resMsg=")+7);
            Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
            if ("登录成功}".equals(text)) {
                SharedPreferences userSharePreferences = context.getSharedPreferences("userInfo", Activity.MODE_PRIVATE);
                SharedPreferences.Editor editor = userSharePreferences.edit();
                editor.putString("username", uName);
                editor.commit();
                Intent intent = new Intent(context, FilmListActivity.class);
                context.startActivity(intent);
            }
        }
    }

  如果想透彻理解Android中这一个类的读者,可以自己去找相关的资料查阅,这里我说一下几个点,比较关键。

  首先是

public static class MyAsyncTask extends AsyncTask<String, Integer, String>

  我们看到这里有三个参数,分别是<输入, 进度, 输出>.第二个参数我们先不管,输入指的是我们执行该类时输入的参数类型,可以看到是和下列函数的传入参数类型一致,我们Login参数传入的就是一个String。

protected  String doInBackground(String... params)

  而上面函数的返回类型则和输出类型一致,同样和下面一个函数的传入参数类型一致。

protected void onPostExecute(String s)

  现在读者能够基本理解这个类了吧,就是在doInBackground中后台访问网络,然后得到结果后,在onPostExecute中执行,起码读者可以这样简单理解。

  至于访问的url,我们在上一篇中讲过,具体和数据库的交互,是在服务器中完成的。我们的客户端只需要明白我们给服务器的参数是什么,服务器将会返回什么给我们。

  大家可以看到,在登录成功后,我将会跳转到另一个Activity。

  这里,我想和大家说一下,我得到第二个Activity中图片的思路。

  首先我回访问这个url:

//客户端
        String FilmListUrl = "http://192.168.110.1:8080/FoeServlet/FilmList";
        new MyAsyncTask(FilmListActivity.this, list, filmListAdapter).execute(FilmListUrl);

  我们来看一下,在服务端,会做什么。

//服务器
        try {
            Statement statement = (Statement)connection.createStatement();
            String sql = "select * from " + DBUtil.TABLE_FILMLIST;
            ResultSet result = statement.executeQuery(sql);
            while (result.next()) {
                rs.append("name=");
                rs.append(result.getString(1));
                rs.append("introduce=");
                rs.append(result.getString(2));
                rs.append("actor=");
                rs.append(result.getString(3));
                rs.append("filmPic=");
                rs.append(result.getString(4));
                rs.append("\n");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }


        PrintWriter pw = response.getWriter();
        pw.println(rs.toString());
        pw.flush();

  服务器会访问film_list表,然后将里面的内容全部放在一个String里,返回给客户端。这里,我们是否还记得第一篇博客的一副图片,就是该表的内容。

基于Android的影院售票系统 基于android的电影售票app_Android_02

  我们来看看服务器返回给客户端的字符串是怎样的吧。

基于Android的影院售票系统 基于android的电影售票app_基于Android的影院售票系统_03

  哈哈,一眼看下去好像很乱,但其实不是的,当然,返回数据怎样组织由读者决定,反正我写的组织方法就是上图。大牛不要见笑。

  当客户端收到这一串字符串,我们只要这样处理。就可以提取有用的信息。

@Override
        protected void onPostExecute(ArrayList<String> filmList) {
            for (int i = 0; i < filmList.size()-1; i++) {
                String filmData = filmList.get(i);
                String filmName = filmData.substring(filmData.indexOf("name=")+5, filmData.indexOf("introduce="));
                String filmIntroduce = filmData.substring(filmData.indexOf("introduce=")+10, filmData.indexOf("actor="));
                String filmActor = filmData.substring(filmData.indexOf("actor=")+6, filmData.indexOf("filmPic="));
                String filmPicURL = "http://192.168.110.1:8080/FoeServlet/FilmPic?PicID=" + filmData.substring(filmData.indexOf("filmPic=")+8);
                Bitmap filmPic = null;
                list.add(new Film(filmPic, filmName, filmIntroduce, filmActor));
                new MyPicLoad(list, i, filmListAdapter).execute(filmPicURL);
            }
            filmListAdapter.notifyDataSetChanged();
        }

  有兴趣的读者不要怕,晚点我会将源码放到github中,读者可以自己去看。我们看到,现在我们获得的图片只是一个ID,因为,我们没用必要在数据库中存储一张图片,我们只需要存储图片的url。这也是你在随便一个网站按F12,看到里面的图片其实都是src=**的原因。所以,我们需要我们拿到的图片ID,进行第二次的网络访问,获得图片。

  在第二次访问中,我们需要来自服务器的字节资源读取成图片。

@Override
        protected  byte[] doInBackground(String... params) {
            HttpURLConnection connection = null;
            byte[] picByte = null;
            try {
                URL url = new URL(params[0]);
                connection = (HttpURLConnection)url.openConnection();
                connection.setRequestMethod("GET");
                connection.setReadTimeout(8000);

                if (connection.getResponseCode() == 200) {
                    InputStream fis = connection.getInputStream();
                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    byte[] bytes = new byte[1024];
                    int length = -1;
                    while ((length = fis.read(bytes)) != -1) {
                        bos.write(bytes, 0, length);
                    }
                    picByte = bos.toByteArray();
                    bos.close();
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return picByte;
        }

  然后在获得图片后,再更新一次ListView就可以了。

@Override
        protected void onPostExecute(byte[] filmPic) {
            if (filmPic != null) {
                Bitmap bitmap = BitmapFactory.decodeByteArray(filmPic, 0, filmPic.length);
                list.get(i).setFilmBitmap(bitmap);
                filmListAdapter.notifyDataSetChanged();
            }
        }

  本次的博客,就到这里结束了。