提取课表功能

转自http://blog.csdn.net/sbsujjbcy

如果你是在校大学生,或许你用多了各种课程表,比如课程格子,超级课程表。它们都有一个共同点就是可以一键导入教务处的课程。那么一直都是用户的我们,没有考虑过它是如何实现的。那么现在就来模仿一款”超级课程表“。

PS:由于超级课程表是商用软件,原本提取了一些图片,但是为了避免涉及侵权问题,所有图片均已使用一张绿色圆圈代替,背景图片也以颜色代替,缺乏美观,如果你觉得太丑,可以自己寻找图片代替。

那么说了这么久,先来看看这款高仿的软件长什么样子。本文的代码做过精简,所以界面可能有出入。

好了,界面太丑,不忍直视,先暂时忽略,本文的重点不是UI,而是如何提取课程。

先做下准备工作。

1、HttpWatch抓包分析工具。此工具的使用后文介绍

2、Litepal数据持久化orm,郭大神的大作,挺好用的orm,用法详见郭霖博客。

3、Async-android-http 数据异步请求框架,这里主要用到这个框架的异步请求以及session保持的功能,或许大多数人没有使用过这个框架的会话保持功能,反正个人觉得就是一神器,操作十分简单,就1句话,不然用HttpClient可能就没那么简单了,要自己写好多内容。具体用法参见github

4、Jsoup网页内容解析框架,可支持jquery选择器。可以支持从本地加载html,远程加载html,支持数据抽取,数据修改等功能,如果能灵活运用这个框架,那么你想抓取什么东西都不在话下。


既然要导入课程表,那么一定要登录教务处,结论是需要教务处的账号密码,这个好办,每个学生都有账号密码。那么怎么登录呢,这个当然不是我们人工登录了,只要提供账号密码,由程序来帮我们完成登录过程以及课程的提取过程。如果登录?首先打开教务处登录界面,打开HttpWatch进行跟踪。输入账号,密码,验证码(验证码视具体学校不同,有些学校不含验证码,有些学校含验证码,验证码的处理后文进行说明),输入完成后点击登录,再点击查看课程的菜单,之后停止HttpWatch录制,把文件保存一下进行分析。打开保存后的文件,查看登录时提交的参数及一些信息,记录下来,同时记录查看课程页提交的参数及信息。


先看登录页面提交的参数,参数均是POST提交,这可以通过HttpWatch看到提交方式

__VIEWSTATE:有这个值页面生成的,这里我直接使用这个固定值而不去抓取,这个值是.net根据表单参数自动生成的。理论上同一个页面是不会变动的。

Button1:传空值即可

hidPdrs:传空值即可

lbLanguage:传空值即可

RadioButtonList1:图上是乱码,通过查看网页源代码可知该值是学生,因为我们是以学生的角色登录的

TextBox2:这个值是密码,传密码即可

txtSecrect:这个值是验证码,传对应的验证码即可

txtUserName:这个值是学号,传学号即可

你以为只要提交这些参数就好了吗,那么你就错了,我们还有设置请求头信息,如下图

我们不必设置所有请求头信息,只需要设置Host,Referer,User-Agent(可不设)。


请求头设置完毕了,那么来说一个重大的问题,就是验证码的问题,这里有三种方式供选择。

1、在登录之前抓取验证码,显示出来,供用户输入。

2、使用正方的bug1,为什么是bug1呢,因为后面一种方法利用了bug2,bug1,bug2不一定所有学校适用,正方的默认登录页面是default2.aspx,如果这个页面有验证码,你可以试试default1.aspx-default6.aspx六个页面,运气好的话可能会有不需要验证码的页面。这时候你使用该页面进行登录即可(提交参数会不同,具体自己抓包分析)

3、使用正方的bug2,不得不说这个bug2,大概是某个程序猿在某男某月某日无意间留下的把,那么怎么使用这个bug呢,很简单,登录的时候直接传验证码为空值或者空字符串过去就好了,有人说,你他妈逗我,这都行,恩,真的行。为什么行呢,原因可能是正方后台程序没有判断传过来的值是不是空。我们模拟登录的时候并没有去请求验证码的页面,所有不会产生验证码(此时为空字符串或者空值)和cookie,当我们提交空验证码时,后台接收到的值就是空字符串,两个空字符串做比较当然相等了,以上只是猜测,毕竟正方是.net的,.net的处理机制本人不是很清楚。


说了这么多理论知识,来点实际的把,先完成登录界面的代码

[html]  view plain copy
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     tools:context="${relativePackage}.${activityClass}" >  
  6.   
  7.       
  8.     <ImageView   
  9.         android:id="@+id/logo"  
  10.         android:layout_width="wrap_content"  
  11.         android:layout_height="wrap_content"  
  12.         android:background="@drawable/icon"  
  13.         android:layout_alignParentTop="true"  
  14.         android:layout_centerHorizontal="true"  
  15.         android:layout_marginTop="30dp"  
  16.         />  
  17.     <EditText  
  18.         android:id="@+id/username"  
  19.         android:layout_width="match_parent"  
  20.         android:layout_height="wrap_content"  
  21.         android:layout_below="@id/logo"  
  22.         android:layout_marginTop="50dp"  
  23.         android:drawableLeft="@drawable/username"  
  24.         android:hint="教务处账号"  
  25.         android:text="" />  
  26.   
  27.     <EditText  
  28.         android:id="@+id/password"  
  29.         android:layout_width="match_parent"  
  30.         android:layout_height="wrap_content"  
  31.         android:layout_below="@id/username"  
  32.         android:drawableLeft="@drawable/password"  
  33.         android:hint="教务处密码"  
  34.         android:text=""  
  35.         android:password="true" />  
  36.   
  37.     <LinearLayout  
  38.         android:id="@+id/ll_code"  
  39.         android:layout_width="match_parent"  
  40.         android:layout_height="wrap_content"  
  41.         android:layout_below="@id/password"  
  42.         android:orientation="horizontal"   
  43.         android:visibility="gone"  
  44.         >  
  45.   
  46.         <EditText  
  47.             android:id="@+id/secrectCode"  
  48.             android:layout_width="100dp"  
  49.             android:layout_height="wrap_content"  
  50.             android:hint="验证码" />  
  51.   
  52.         <ImageView  
  53.             android:id="@+id/codeImage"  
  54.             android:layout_width="72dp"  
  55.             android:layout_height="36dp"  
  56.             android:layout_marginLeft="10dp"  
  57.             android:layout_marginRight="10dp"  
  58.             android:scaleType="fitStart" />  
  59.   
  60.         <Button  
  61.             android:id="@+id/getCode"  
  62.             android:layout_width="100dp"  
  63.             android:layout_height="40dp"  
  64.             android:background="@drawable/btn_login_selector"  
  65.             android:text="刷新验证码"  
  66.             android:textColor="#fff" />  
  67.     </LinearLayout>  
  68.   
  69.     <Button  
  70.         android:id="@+id/login"  
  71.         android:layout_width="180dp"  
  72.         android:layout_height="45dp"  
  73.         android:layout_alignParentBottom="true"  
  74.         android:layout_below="@drawable/password"  
  75.         android:layout_centerHorizontal="true"  
  76.         android:layout_marginBottom="100dp"  
  77.         android:background="@drawable/btn_login_selector"  
  78.         android:text="登录"  
  79.         android:textColor="#fff" />  
  80.   
  81. </RelativeLayout>  

很简单,就是账号,密码,以及验证码,这里验证码被我隐藏了,因为我使用了bug2,不需要请求验证码,对应的界面隐藏掉,但是如果你把他显示出来,获取验证码让用户输入也是可以的。

在登录之前先初始化一下cookie,这一步必须在请求之前设置。


[java]  view plain copy
  1. /** 
  2.      * 初始化Cookie 
  3.      */  
  4.     private void initCookie(Context context) {  
  5.         //必须在请求前初始化  
  6.         cookie = new PersistentCookieStore(context);  
  7.         HttpUtil.getClient().setCookieStore(cookie);  
  8.     }  

那么HttpUtil又是什么呢,很简单,就是一个请求用的工具类

[java]  view plain copy
  1. package cn.lizhangqu.kb.util;  
  2.    
  3. import org.apache.http.Header;  
  4.    
  5. import android.app.ProgressDialog;  
  6. import android.content.Context;  
  7. import android.widget.Toast;  
  8. import cn.lizhangqu.kb.service.LinkService;  
  9.    
  10. import com.loopj.android.http.AsyncHttpClient;  
  11. import com.loopj.android.http.AsyncHttpResponseHandler;  
  12. import com.loopj.android.http.BinaryHttpResponseHandler;  
  13. import com.loopj.android.http.RequestParams;  
  14.    
  15. /** 
  16.  * Http请求工具类 
  17.  * @author lizhangqu 
  18.  * @date 2015-2-1 
  19.  */  
  20. /** 
  21.  * @author Administrator 
  22.  * 
  23.  */  
  24. public class HttpUtil {  
  25.     private static AsyncHttpClient client = new AsyncHttpClient(); // 实例话对象  
  26.     // Host地址  
  27.     public static final String HOST = "***.***.***.***";  
  28.     // 基础地址  
  29.     public static final String URL_BASE = "http://***.***.***.***/";  
  30.     // 验证码地址  
  31.     public static final String URL_CODE = "http://***.***.***.***/CheckCode.aspx";  
  32.     // 登陆地址  
  33.     public static final String URL_LOGIN = "http://***.***.***.***/default2.aspx";  
  34.     // 登录成功的首页  
  35.     public static String URL_MAIN = "http://***.***.***.***/xs_main.aspx?xh=XH";  
  36.     // 请求地址  
  37.     public static String URL_QUERY = "http://***.***.***.***/QUERY";  
  38.    
  39.     /** 
  40.      * 请求参数 
  41.      */  
  42.     public static String Button1 = "";  
  43.     public static String hidPdrs = "";  
  44.     public static String hidsc = "";  
  45.     public static String lbLanguage = "";  
  46.     public static String RadioButtonList1 = "学生";  
  47.     public static String __VIEWSTATE = "dDwyODE2NTM0OTg7Oz7YiHv1mHkLj1OkgkF90IvNTvBrLQ==";  
  48.     public static String TextBox2 = null;  
  49.     public static String txtSecretCode = null;  
  50.     public static String txtUserName = null;  
  51.    
  52.     // 静态初始化  
  53.     static {  
  54.         client.setTimeout(10000); // 设置链接超时,如果不设置,默认为10s  
  55.         // 设置请求头  
  56.         client.addHeader("Host", HOST);  
  57.         client.addHeader("Referer", URL_LOGIN);  
  58.         client.addHeader("User-Agent",  
  59.                 "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko");  
  60.     }  
  61.    
  62.     /** 
  63.      * get,用一个完整url获取一个string对象 
  64.      * 
  65.      * @param urlString 
  66.      * @param res 
  67.      */  
  68.     public static void get(String urlString, AsyncHttpResponseHandler res) {  
  69.         client.get(urlString, res);  
  70.     }  
  71.    
  72.     /** 
  73.      * get,url里面带参数 
  74.      * 
  75.      * @param urlString 
  76.      * @param params 
  77.      * @param res 
  78.      */  
  79.     public static void get(String urlString, RequestParams params,  
  80.             AsyncHttpResponseHandler res) {  
  81.         client.get(urlString, params, res);  
  82.     }  
  83.    
  84.     /** 
  85.      * get,下载数据使用,会返回byte数据 
  86.      * 
  87.      * @param uString 
  88.      * @param bHandler 
  89.      */  
  90.     public static void get(String uString, BinaryHttpResponseHandler bHandler) {  
  91.         client.get(uString, bHandler);  
  92.     }  
  93.    
  94.     /** 
  95.      * post,不带参数 
  96.      * 
  97.      * @param urlString 
  98.      * @param res 
  99.      */  
  100.     public static void post(String urlString, AsyncHttpResponseHandler res) {  
  101.         client.post(urlString, res);  
  102.     }  
  103.    
  104.     /** 
  105.      * post,带参数 
  106.      * 
  107.      * @param urlString 
  108.      * @param params 
  109.      * @param res 
  110.      */  
  111.     public static void post(String urlString, RequestParams params,  
  112.             AsyncHttpResponseHandler res) {  
  113.         client.post(urlString, params, res);  
  114.     }  
  115.    
  116.     /** 
  117.      * post,返回二进制数据时使用,会返回byte数据 
  118.      * 
  119.      * @param uString 
  120.      * @param bHandler 
  121.      */  
  122.     public static void post(String uString, BinaryHttpResponseHandler bHandler) {  
  123.         client.post(uString, bHandler);  
  124.     }  
  125.    
  126.     /** 
  127.      * 返回请求客户端 
  128.      * 
  129.      * @return 
  130.      */  
  131.     public static AsyncHttpClient getClient() {  
  132.         return client;  
  133.     }  
  134.    
  135.     /** 
  136.      * 获得登录时所需的请求参数 
  137.      * 
  138.      * @return 
  139.      */  
  140.     public static RequestParams getLoginRequestParams() {  
  141.         // 设置请求参数  
  142.         RequestParams params = new RequestParams();  
  143.         params.add("__VIEWSTATE", __VIEWSTATE);  
  144.         params.add("Button1", Button1);  
  145.         params.add("hidPdrs", hidPdrs);  
  146.         params.add("hidsc", hidsc);  
  147.         params.add("lbLanguage", lbLanguage);  
  148.         params.add("RadioButtonList1", RadioButtonList1);  
  149.         params.add("TextBox2", TextBox2);  
  150.         params.add("txtSecretCode", txtSecretCode);  
  151.         params.add("txtUserName", txtUserName);  
  152.         return params;  
  153.     }  
  154.    
  155.     /** 
  156.      * 接口回调 
  157.      * @author lizhangqu 
  158.      * 
  159.      * 2015-2-22 
  160.      */  
  161.     public interface QueryCallback {  
  162.         public String handleResult(byte[] result);  
  163.     }  
  164.    
  165.     /** 
  166.      * 登录后查询信息封装好的函数 
  167.      * @param context 
  168.      * @param linkService 
  169.      * @param urlName 
  170.      * @param callback 
  171.      */  
  172.     public static void getQuery(final Context context, LinkService linkService,  
  173.             final String urlName, final QueryCallback callback) {  
  174.         final ProgressDialog dialog = CommonUtil.getProcessDialog(context,  
  175.                 "正在获取" + urlName);  
  176.         dialog.show();  
  177.         String link = linkService.getLinkByName(urlName);  
  178.         if (link != null) {  
  179.             HttpUtil.URL_QUERY = HttpUtil.URL_QUERY.replace("QUERY", link);  
  180.         } else {  
  181.             Toast.makeText(context, "链接出现错误", Toast.LENGTH_SHORT).show();  
  182.             return;  
  183.         }  
  184.         HttpUtil.getClient().addHeader("Referer", HttpUtil.URL_MAIN);  
  185.         HttpUtil.getClient().setURLEncodingEnabled(true);  
  186.         HttpUtil.get(HttpUtil.URL_QUERY, new AsyncHttpResponseHandler() {  
  187.             @Override  
  188.             public void onSuccess(int arg0, Header[] arg1, byte[] arg2) {  
  189.                 if (callback != null) {  
  190.                     callback.handleResult(arg2);  
  191.                 }  
  192.                 Toast.makeText(context, urlName + "获取成功!!!", Toast.LENGTH_LONG)  
  193.                         .show();  
  194.                 dialog.dismiss();  
  195.             }  
  196.    
  197.             @Override  
  198.             public void onFailure(int arg0, Header[] arg1, byte[] arg2,  
  199.                     Throwable arg3) {  
  200.                 dialog.dismiss();  
  201.                 Toast.makeText(context, urlName + "获取失败!!!", Toast.LENGTH_SHORT)  
  202.                         .show();  
  203.             }  
  204.         });  
  205.     }  
  206. }  

地址信息被我处理掉了,替换成对应的地址即可,都是几个简单的函数,其中最后一个函数做了一个封装,代码自己读吧,这里就不讲了。。。。。

现在查看登录的代码。

[java]  view plain copy
  1. /** 
  2.      * 登录 
  3.      */  
  4.     private void login() {  
  5.         HttpUtil.txtUserName = username.getText().toString().trim();  
  6.         HttpUtil.TextBox2 = password.getText().toString().trim();  
  7.         //需要时打开验证码注释  
  8.         //HttpUtil.txtSecretCode = secrectCode.getText().toString().trim();  
  9.         if (TextUtils.isEmpty(HttpUtil.txtUserName)  
  10.                 || TextUtils.isEmpty(HttpUtil.TextBox2)) {  
  11.             Toast.makeText(getApplicationContext(), "账号或者密码不能为空!",  
  12.                     Toast.LENGTH_SHORT).show();  
  13.             return;  
  14.         }  
  15.         final ProgressDialog dialog =CommonUtil.getProcessDialog(LoginActivity.this,"正在登录中!!!");  
  16.         dialog.show();  
  17.         RequestParams params = HttpUtil.getLoginRequestParams();// 获得请求参数  
  18.         HttpUtil.URL_MAIN = HttpUtil.URL_MAIN.replace("XH",  
  19.                 HttpUtil.txtUserName);// 获得请求地址  
  20.         HttpUtil.getClient().setURLEncodingEnabled(true);  
  21.         HttpUtil.post(HttpUtil.URL_LOGIN, params,  
  22.                 new AsyncHttpResponseHandler() {  
  23.    
  24.                     @Override  
  25.                     public void onSuccess(int arg0, Header[] arg1, byte[] arg2) {  
  26.                         try {  
  27.                             String resultContent = new String(arg2, "gb2312");  
  28.                             if(linkService.isLogin(resultContent)!=null){  
  29.                                 String ret = linkService.parseMenu(resultContent);  
  30.                                 Log.d("TAG""login success:"+ret);  
  31.                                 Toast.makeText(getApplicationContext(),  
  32.                                         "登录成功!!!", Toast.LENGTH_SHORT).show();  
  33.                                 jump2Main();  
  34.    
  35.                             }else{  
  36.                                 Toast.makeText(getApplicationContext(),"账号或者密码错误!!!", Toast.LENGTH_SHORT).show();  
  37.                             }  
  38.    
  39.                         } catch (UnsupportedEncodingException e) {  
  40.                             e.printStackTrace();  
  41.                         } finally {  
  42.                             dialog.dismiss();  
  43.                         }  
  44.                     }  
  45.                     @Override  
  46.                     public void onFailure(int arg0, Header[] arg1, byte[] arg2,  
  47.                             Throwable arg3) {  
  48.                         Toast.makeText(getApplicationContext(), "登录失败!!!!",  
  49.                                 Toast.LENGTH_SHORT).show();  
  50.                         dialog.dismiss();  
  51.                     }  
  52.                 });  
  53.     }  
通过抓取关键字,判断是否登录成功,登录成功则解析菜单,对应的逻辑被我封装在service层里了
[java]  view plain copy
  1. package cn.lizhangqu.kb.service;  
  2.    
  3. import java.util.List;  
  4.    
  5. import org.jsoup.Jsoup;  
  6. import org.jsoup.nodes.Document;  
  7. import org.jsoup.nodes.Element;  
  8. import org.jsoup.select.Elements;  
  9. import org.litepal.crud.DataSupport;  
  10.    
  11. import cn.lizhangqu.kb.model.Course;  
  12. import cn.lizhangqu.kb.model.LinkNode;  
  13.    
  14. /** 
  15.  * LinNode表的业务逻辑处理 
  16.  * @author lizhangqu 
  17.  * @date 2015-2-1 
  18.  */  
  19. public class LinkService {  
  20.     private static volatile LinkService linkService;  
  21.     private LinkService(){}  
  22.     public static LinkService getLinkService() {  
  23.         if(linkService==null){  
  24.             synchronized (LinkService.class) {  
  25.                 if(linkService==null)  
  26.                     linkService=new LinkService();  
  27.             }  
  28.         }  
  29.    
  30.         return linkService;  
  31.     }  
  32.    
  33.     public String getLinkByName(String name){  
  34.         List<LinkNode> find = DataSupport.where("title=?",name).limit(1).find(LinkNode.class);  
  35.         if(find.size()!=0){  
  36.             return find.get(0).getLink();  
  37.         }else{  
  38.             return null;  
  39.         }  
  40.     }  
  41.     public boolean save(LinkNode linknode){  
  42.         return linknode.save();  
  43.     }  
  44.     /** 
  45.      * 查询所有链接 
  46.      * 
  47.      * @return 
  48.      */  
  49.     public List<LinkNode> findAll() {  
  50.         return DataSupport.findAll(LinkNode.class);  
  51.     }  
  52.     public String parseMenu(String content) {  
  53.         LinkNode linkNode =null;  
  54.         StringBuilder result = new StringBuilder();  
  55.         Document doc = Jsoup.parse(content);  
  56.         Elements elements = doc.select("ul.nav a[target=zhuti]");  
  57.         for (Element element : elements) {  
  58.             result.append(element.html() + "\n" + element.attr("href") + "\n\n");  
  59.             linkNode= new LinkNode();  
  60.             linkNode.setTitle(element.text());  
  61.             linkNode.setLink(element.attr("href"));  
  62.             save(linkNode);  
  63.         }  
  64.         return result.toString();  
  65.    
  66.     }  
  67.     public String isLogin(String content){  
  68.         Document doc = Jsoup.parse(content, "UTF-8");  
  69.         Elements elements = doc.select("span#xhxm");  
  70.         try{  
  71.             Element element=elements.get(0);  
  72.             return element.text();  
  73.         }catch(IndexOutOfBoundsException e){  
  74.             //e.printStackTrace();  
  75.         }  
  76.         return null;  
  77.     }  
  78. }  

判断是否登录成功的判断依据是看页面上是否有某某同学,欢迎你,这段信息在id为xhxm的span里,成功后解析菜单,因为不一定只是抓课表,也可能抓成绩,各种抓,所以这里把链接都记录下来,对应页面的源代码我会和代码一同上传。

如果你要使用验证码,则获取验证码即可,对应代码如下,就是获得验证码后显示在界面上

[java]  view plain copy
  1. /** 
  2.      * 获得验证码 
  3.      */  
  4.     private void getCode() {  
  5.         final ProgressDialog dialog =CommonUtil.getProcessDialog(LoginActivity.this,"正在获取验证码");  
  6.         dialog.show();  
  7.         HttpUtil.get(HttpUtil.URL_CODE, new AsyncHttpResponseHandler() {  
  8.             @Override  
  9.             public void onSuccess(int arg0, Header[] arg1, byte[] arg2) {  
  10.    
  11.                 InputStream is = new ByteArrayInputStream(arg2);  
  12.                 Bitmap decodeStream = BitmapFactory.decodeStream(is);  
  13.                 code.setImageBitmap(decodeStream);  
  14.                 Toast.makeText(getApplicationContext(), "验证码获取成功!!!",Toast.LENGTH_SHORT).show();  
  15.                 dialog.dismiss();  
  16.             }  
  17.    
  18.             @Override  
  19.             public void onFailure(int arg0, Header[] arg1, byte[] arg2,  
  20.                     Throwable arg3) {  
  21.    
  22.                 Toast.makeText(getApplicationContext(), "验证码获取失败!!!",  
  23.                         Toast.LENGTH_SHORT).show();  
  24.                 dialog.dismiss();  
  25.    
  26.             }  
  27.         });  
  28.     }  

LinkUtil里面是一些常量

[java]  view plain copy
  1. package cn.lizhangqu.kb.util;  
  2.    
  3. /** 
  4.  * 首页菜单接口 
  5.  * 用于定义linknode表中的标题 
  6.  * @author lizhangqu 
  7.  * @date 2015-2-1 
  8.  */  
  9. public interface LinkUtil {  
  10.     public static final String ZYXXK="专业选修课";  
  11.     public static final String QXXGXK="全校性公选课(通识限选)";  
  12.     public static final String SYXK="实验选课";  
  13.     public static final String DJKSBM="等级考试报名";  
  14.     public static final String GRXX="个人信息";  
  15.     public static final String MMXG="密码修改";  
  16.     public static final String XSGRKB="学生个人课表";  
  17.     public static final String XSKSCX="学生考试查询";  
  18.     public static final String CJCX="成绩查询";  
  19.     public static final String DJKSCX="等级考试查询";  
  20.     public static final String JCSYXX="教材使用信息";  
  21.     public static final String XSXKQKCX="学生选课情况查询";  
  22.     public static final String XSBKKSCX="学生补考考试查询";  
  23.     public static final String XSXXYPJ="学生信息员评价";  
  24.     public static final String FKJGCX="反馈结果查询";  
  25.     public static final String JWGG="教务公告";  
  26.     public static final String BMJSKBCX="部门教师课表查询";  
  27.     public static final String QXKBCX="全校课表查询";  
  28.     public static final String JXRLCX="教学日历查询";  
  29. }  

接下来是文章的重点,即如何解析课表。

课表是在一张table里的,提取table里的内容进行解析,解析方法不止一种,我在解析过程中也尝试了多种方法,直接看代码把

[java]  view plain copy
  1. /** 
  2.      * 根据网页返回结果解析课程并保存 
  3.      * 
  4.      * @param content 
  5.      * @return 
  6.      */  
  7.     public String parseCourse(String content) {  
  8.         StringBuilder result = new StringBuilder();  
  9.         Document doc = Jsoup.parse(content);  
  10.    
  11.         Elements semesters = doc.select("option[selected=selected]");  
  12.         String[] years=semesters.get(0).text().split("-");  
  13.         int startYear=Integer.parseInt(years[0]);  
  14.         int endYear=Integer.parseInt(years[1]);  
  15.         int semester=Integer.parseInt(semesters.get(1).text());  
  16.    
  17.    
  18.    
  19.         Elements elements = doc.select("table#Table1");  
  20.         Element element = elements.get(0).child(0);  
  21.         //移除一些无用数据  
  22.    
  23.         element.child(0).remove();  
  24.         element.child(0).remove();  
  25.         element.child(0).child(0).remove();  
  26.         element.child(4).child(0).remove();  
  27.         element.child(8).child(0).remove();  
  28.         int rowNum = element.childNodeSize();  
  29.         int[][] map = new int[11][7];  
  30.         for (int i = 0; i < rowNum - 1; i++) {  
  31.             Element row = element.child(i);  
  32.             int columnNum = row.childNodeSize() - 2;  
  33.             for (int j = 1; j < columnNum; j++) {  
  34.                 Element column = row.child(j);  
  35.                 int week = fillMap(column, map, i);  
  36.                 //填充map,获取周几,第几节至第几节  
  37.                 //作用:弥补不能获取这些数据的格式  
  38.                 if (column.hasAttr("rowspan")) {  
  39.                     try {  
  40.                         System.out.println("周"+ week+ " 第"+ (i + 1)+ "节-第"+ (i + Integer.parseInt(column.attr("rowspan"))) + "节");  
  41.                         splitCourse(column.html(), startYear,endYear,semester,week, i + 1,i + Integer.parseInt(column.attr("rowspan")));  
  42.                     } catch (NumberFormatException e) {  
  43.                         e.printStackTrace();  
  44.                     }  
  45.                 }  
  46.             }  
  47.         }  
  48.    
  49.         return result.toString();  
  50.     }  
  51.    
  52.    
  53.    
  54.     /** 
  55.      * 根据传进来的课程格式转换为对应的实体类并保存 
  56.      * @param sub 
  57.      * @param startYear 
  58.      * @param endYear 
  59.      * @param semester 
  60.      * @param week 
  61.      * @param startSection 
  62.      * @param endSection 
  63.      * @return 
  64.      */  
  65.     private Course storeCourseByResult(String sub,int startYear,int endYear,int semester, int week,  
  66.             int startSection, int endSection) {  
  67.         // 周二第1,2节{第4-16周}      二,1,2,4,16,null  
  68.         // {第2-10周|3节/周}            null,null,null,2,10,3节/周  
  69.         // 周二第1,2节{第4-16周|双周}   二,1,2,4,16,双周  
  70.         // 周二第1节{第4-16周}            二,1,null,4,16,null  
  71.         // 周二第1节{第4-16周|双周}         二,1,null,4,16,双周  
  72.         // str格式如上,这里只是简单考虑每个课都只有两节课,实际上有三节和四节,模式就要改动,其他匹配模式请自行修改  
  73.    
  74.         String reg = "周?(.)?第?(\\d{1,2})?,?(\\d{1,2})?节?\\{第(\\d{1,2})-(\\d{1,2})周\\|?((.*周))?\\}";  
  75.    
  76.         String splitPattern = "<br />";  
  77.         String[] temp = sub.split(splitPattern);  
  78.         Pattern pattern = Pattern.compile(reg);  
  79.         Matcher matcher = pattern.matcher(temp[1]);  
  80.         matcher.matches();  
  81.         Course course = new Course();  
  82.         //课程开始学年  
  83.         course.setStartYear(startYear);  
  84.         //课程结束学年  
  85.         course.setEndYear(endYear);  
  86.         //课程学期  
  87.         course.setSemester(semester);  
  88.    
  89.         //课程名  
  90.         course.setCourseName(temp[0]);  
  91.         //课程时间,冗余字段  
  92.         course.setCourseTime(temp[1]);  
  93.         //教师  
  94.         course.setTeacher(temp[2]);  
  95.    
  96.         try {  
  97.             // 数组可能越界,即没有教室  
  98.             course.setClasssroom(temp[3]);  
  99.         } catch (ArrayIndexOutOfBoundsException e) {  
  100.             course.setClasssroom("无教室");  
  101.         }  
  102.         //周几,可能为空,此时使用传进来的值  
  103.         if (null != matcher.group(1)){  
  104.             course.setDayOfWeek(getDayOfWeek(matcher.group(1)));  
  105.         }else{  
  106.             course.setDayOfWeek(getDayOfWeek(week+""));  
  107.         }  
  108.         //课程开始节数,可能为空,此时使用传进来的值  
  109.         if (null != matcher.group(2)){  
  110.             course.setStartSection(Integer.parseInt(matcher.group(2)));  
  111.         }else{  
  112.             course.setStartSection(startSection);  
  113.         }  
  114.    
  115.         //课程结束时的节数,可能为空,此时使用传进来的值  
  116.         if (null != matcher.group(3)){  
  117.             course.setEndSection(Integer.parseInt(matcher.group(3)));  
  118.         }else{  
  119.             course.setEndSection(endSection);  
  120.         }  
  121.    
  122.         //起始周  
  123.         course.setStartWeek(Integer.parseInt(matcher.group(4)));  
  124.         //结束周  
  125.         course.setEndWeek(Integer.parseInt(matcher.group(5)));  
  126.         //单双周  
  127.         String t = matcher.group(6);  
  128.         setEveryWeekByChinese(t, course);  
  129.         save(course);  
  130.         return course;  
  131.     }  
  132.    
  133.    
  134.    
  135.    
  136.     /** 
  137.      * 提取课程格式,可能包含多节课 
  138.      * @param str 
  139.      * @param startYear 
  140.      * @param endYear 
  141.      * @param semester 
  142.      * @param week 
  143.      * @param startSection 
  144.      * @param endSection 
  145.      * @return 
  146.      */  
  147.     private int splitCourse(String str, int startYear,int endYear,int semester,int week, int startSection,  
  148.             int endSection) {  
  149.         String pattern = "<br /><br />";  
  150.         String[] split = str.split(pattern);  
  151.         if (split.length > 1) {// 如果大于一节课  
  152.             for (int i = 0; i < split.length; i++) {  
  153.                 if (!(split[i].startsWith("<br />") && split[i].endsWith("<br />"))) {  
  154.                     storeCourseByResult(split[i], startYear,endYear,semester,week, startSection,  
  155.                             endSection);// 保存单节课  
  156.                 } else {  
  157.                     // <br />文化地理(网络课程)<br />周日第10节{第17-17周}<br />李宏伟<br />  
  158.                     // 以上格式的特殊处理,此种格式在没有教师的情况下产生,即教室留空后<br />依旧存在  
  159.                     int brLength = "<br />".length();  
  160.                     String substring = split[i].substring(brLength,  
  161.                             split[i].length() - brLength);  
  162.                     storeCourseByResult(substring, startYear,endYear,semester,week, startSection,  
  163.                             endSection);// 保存单节课  
  164.                 }  
  165.             }  
  166.             return split.length;  
  167.         } else {  
  168.             storeCourseByResult(str, startYear,endYear,semester,week, startSection, endSection);// 保存  
  169.             return 1;  
  170.         }  
  171.     }  
  172.    
  173.     /** 
  174.      * 填充map,获取周几,第几节课至第几节课 
  175.      * @param childColumn 
  176.      * @param map 
  177.      * @param i 
  178.      * @return 周几 
  179.      */  
  180.     public static int fillMap(Element childColumn, int map[][], int i) {  
  181.         //这个函数的作用自行领悟,总之就是返回周几,也是无意中发现的,于是就这样获取了,作用是双重保障,因为有些课事无法根据正则匹配出周几第几节到第几节  
  182.         boolean hasAttr = childColumn.hasAttr("rowspan");  
  183.         int week = 0;  
  184.         if (hasAttr) {  
  185.             for (int t = 0; t < map[0].length; t++) {  
  186.                 if (map[i][t] == 0) {  
  187.                     int r = Integer.parseInt(childColumn.attr("rowspan"));  
  188.                     for (int l = 0; l < r; l++) {  
  189.                         map[i + l][t] = 1;  
  190.                     }  
  191.                     week = t + 1;  
  192.                     break;  
  193.                 }  
  194.             }  
  195.    
  196.         } else {  
  197.             if (childColumn.childNodes().size() > 1) {  
  198.                 childColumn.attr("rowspan""1");  
  199.             }  
  200.             for (int t = 0; t < map[0].length; t++) {  
  201.                 if (map[i][t] == 0) {  
  202.                     map[i][t] = 1;  
  203.                     week = t + 1;  
  204.                     break;  
  205.                 }  
  206.             }  
  207.         }  
  208.         return week;  
  209.     }  
  210.     /** 
  211.      * 设置单双周 
  212.      * @param week 
  213.      * @param course 
  214.      */  
  215.     public void setEveryWeekByChinese(String week, Course course) {  
  216.         // 1代表单周,2代表双周  
  217.         if (week != null) {  
  218.             if (week.equals("单周"))  
  219.                 course.setEveryWeek(1);  
  220.             else if (week.equals("双周"))  
  221.                 course.setEveryWeek(2);  
  222.         }  
  223.         // 默认值为0,代表每周  
  224.     }  
  225.    
  226.     /**根据中文数字一,二,三,四,五,六,日,转换为对应的阿拉伯数字 
  227.      * @param day 
  228.      * @return int 
  229.      */  
  230.     public int getDayOfWeek(String day) {  
  231.         if (day.equals("一"))  
  232.             return 1;  
  233.         else if (day.equals("二"))  
  234.             return 2;  
  235.         else if (day.equals("三"))  
  236.             return 3;  
  237.         else if (day.equals("四"))  
  238.             return 4;  
  239.         else if (day.equals("五"))  
  240.             return 5;  
  241.         else if (day.equals("六"))  
  242.             return 6;  
  243.         else if (day.equals("日"))  
  244.             return 7;  
  245.         else  
  246.             return 0;  
  247.     }  

课程的实体类

[java]  view plain copy
  1. package cn.lizhangqu.kb.model;  
  2.   
  3. import org.litepal.crud.DataSupport;  
  4.   
  5. /** 
  6.  * 课程实体类 
  7.  * @author lizhangqu 
  8.  * @date 2015-2-1 
  9.  */  
  10. public class Course extends DataSupport{  
  11.     private int id;//主鍵,自增  
  12.     private int startYear;//学年开始年  
  13.     private int endYear;//学年结束年  
  14.     private int semester;//学期  
  15.     private String courseName;//课程名  
  16.     private String courseTime;//课程时间,冗余字段  
  17.     private String classsroom;//教室  
  18.     private String teacher;//老师  
  19.     private int dayOfWeek;//星期几  
  20.     private int startSection;//第几节课开始  
  21.     private int endSection;//第几节课结束  
  22.     private int startWeek;//开始周  
  23.     private int endWeek;//结束周  
  24.     private int everyWeek;//标记是否是单双周,0为每周,1单周,2双周  
  25.     public int getStartYear() {  
  26.         return startYear;  
  27.     }  
  28.     public void setStartYear(int startYear) {  
  29.         this.startYear = startYear;  
  30.     }  
  31.     public int getEndYear() {  
  32.         return endYear;  
  33.     }  
  34.     public void setEndYear(int endYear) {  
  35.         this.endYear = endYear;  
  36.     }  
  37.     public int getSemester() {  
  38.         return semester;  
  39.     }  
  40.     public void setSemester(int semester) {  
  41.         this.semester = semester;  
  42.     }  
  43.     public int getId() {  
  44.         return id;  
  45.     }  
  46.     public void setId(int id) {  
  47.         this.id = id;  
  48.     }  
  49.     public int getEveryWeek() {  
  50.         return everyWeek;  
  51.     }  
  52.     public void setEveryWeek(int everyWeek) {  
  53.         this.everyWeek = everyWeek;  
  54.     }  
  55.     public int getDayOfWeek() {  
  56.         return dayOfWeek;  
  57.     }  
  58.     public void setDayOfWeek(int dayOfWeek) {  
  59.         this.dayOfWeek = dayOfWeek;  
  60.     }  
  61.     public int getStartSection() {  
  62.         return startSection;  
  63.     }  
  64.     public void setStartSection(int startSection) {  
  65.         this.startSection = startSection;  
  66.     }  
  67.     public int getEndSection() {  
  68.         return endSection;  
  69.     }  
  70.     public void setEndSection(int endSection) {  
  71.         this.endSection = endSection;  
  72.     }  
  73.     public int getStartWeek() {  
  74.         return startWeek;  
  75.     }  
  76.     public void setStartWeek(int startWeek) {  
  77.         this.startWeek = startWeek;  
  78.     }  
  79.     public int getEndWeek() {  
  80.         return endWeek;  
  81.     }  
  82.     public void setEndWeek(int endWeek) {  
  83.         this.endWeek = endWeek;  
  84.     }  
  85.       
  86.     public String getCourseName() {  
  87.         return courseName;  
  88.     }  
  89.     public void setCourseName(String courseName) {  
  90.         this.courseName = courseName;  
  91.     }  
  92.     public String getCourseTime() {  
  93.         return courseTime;  
  94.     }  
  95.     public void setCourseTime(String courseTime) {  
  96.         this.courseTime = courseTime;  
  97.     }  
  98.     public String getClasssroom() {  
  99.         return classsroom;  
  100.     }  
  101.     public void setClasssroom(String classsroom) {  
  102.         this.classsroom = classsroom;  
  103.     }  
  104.     public String getTeacher() {  
  105.         return teacher;  
  106.     }  
  107.     public void setTeacher(String teacher) {  
  108.         this.teacher = teacher;  
  109.     }  
  110.       
  111.     @Override  
  112.     public String toString() {  
  113.         return "Course [id=" + id + ", startYear=" + startYear + ", endYear="  
  114.                 + endYear + ", semester=" + semester + ", courseName="  
  115.                 + courseName + ", courseTime=" + courseTime + ", classsroom="  
  116.                 + classsroom + ", teacher=" + teacher + ", dayOfWeek="  
  117.                 + dayOfWeek + ", startSection=" + startSection  
  118.                 + ", endSection=" + endSection + ", startWeek=" + startWeek  
  119.                 + ", endWeek=" + endWeek + ", everyWeek=" + everyWeek + "]";  
  120.     }  
  121.       
  122.       
  123.       
  124.       
  125.   
  126. }  


以上代码是提取课程的关键代码,课程的格式是在一个table里,tr里有很多td,td里就是课程,一个td里可能不止一节课


td有rowspan属性,代表占了几行,2代表占了两行,也就是两节课,有些课不是两节课的,rowspan的值也就对应改变,课程的节数有1,2,3,4节都有。我们可以根据课程的时间提取该课程是周几上课,第几节课到第几节课,有了这些信息就可以在界面上显示出来了,但是,有些格式,如第2-10周|3节/周是没办法提取时间的,这时候就用一定的技巧提取它,这里使用了fillmap函数对一个7*12的数组进行填充,原理是扫描一行,如果具有rowspan值,则填充该行该列为1,如果rowspan大于等于2,则该列下面几行对应的列也填充为1,等到扫描下一行的时候,该位置不会有课程,且不会有td,则如果是一个空td,则填充该行第一个为0的位置。技巧有点难以理解,具体细节稍微自己琢磨领悟下,这样课程的信息就都提取出来了,当然提取方式不止一种。这种方法也不一定能提取所有格式的课程。

提取完毕后进行显示,本来呢是使用LinearLayout简单达到超级课程表的效果的,后来稍微暴力的使用了下自定义ViewGroup,注意了,这个自定义ViewGroup不具有现实使用意义,只是为了展示效果,里面的代码都太暴力了。。。所以看过一遍就无视吧,简直不忍直视

首先是自定义属性

[html]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <declare-styleable name="CourseView">  
  4.         <attr name="courseId" format="integer"/>  
  5.         <attr name="startSection" format="integer"/>  
  6.         <attr name="endSection" format="integer"/>  
  7.         <attr name="weekDay" format="integer"/>  
  8.     </declare-styleable>  
  9.       
  10. </resources>  
然后是课程自定义View,继承Button,增加一些课程信息而已。

[java]  view plain copy
  1. package cn.lizhangqu.kb.view;  
  2.   
  3. import android.content.Context;  
  4. import android.content.res.TypedArray;  
  5. import android.util.AttributeSet;  
  6. import android.widget.Button;  
  7. import cn.lizhangqu.kb.R;  
  8.   
  9. public class CourseView extends Button {  
  10.     private int courseId;  
  11.     private int startSection;  
  12.     private int endSection;  
  13.     private int weekDay;  
  14.   
  15.     public CourseView(Context context) {  
  16.         this(context,null);  
  17.     }  
  18.   
  19.     public CourseView(Context context, AttributeSet attrs) {  
  20.         this(context, attrs,0);  
  21.     }  
  22.     public CourseView(Context context, AttributeSet attrs, int defStyle) {  
  23.         super(context, attrs, defStyle);  
  24.         TypedArray array=context.obtainStyledAttributes(attrs, R.styleable.CourseView);  
  25.         courseId = array.getInt(R.styleable.CourseView_courseId, 0);  
  26.         startSection=array.getInt(R.styleable.CourseView_startSection, 0);  
  27.         endSection=array.getInt(R.styleable.CourseView_endSection, 0);  
  28.         weekDay=array.getInt(R.styleable.CourseView_weekDay, 0);  
  29.         array.recycle();  
  30.     }  
  31.     public int getCourseId() {  
  32.         return courseId;  
  33.     }  
  34.   
  35.     public void setCourseId(int courseId) {  
  36.         this.courseId = courseId;  
  37.     }  
  38.   
  39.     public int getStartSection() {  
  40.         return startSection;  
  41.     }  
  42.   
  43.     public void setStartSection(int startSection) {  
  44.         this.startSection = startSection;  
  45.     }  
  46.   
  47.     public int getEndSection() {  
  48.         return endSection;  
  49.     }  
  50.   
  51.     public void setEndSection(int endSection) {  
  52.         this.endSection = endSection;  
  53.     }  
  54.     public int getWeek() {  
  55.         return weekDay;  
  56.     }  
  57.   
  58.     public void setWeek(int week) {  
  59.         this.weekDay = week;  
  60.     }  
  61. }  
最后是自定义布局,简单暴力, 注意了,这个布局没有处理重复时间的课程,也就是说没有处理单双周的情况,只是用来简单显示

[java]  view plain copy
  1. package cn.lizhangqu.kb.view;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. import android.content.Context;  
  7. import android.content.res.TypedArray;  
  8. import android.util.AttributeSet;  
  9. import android.util.DisplayMetrics;  
  10. import android.view.View;  
  11. import android.view.ViewGroup;  
  12. import android.view.WindowManager;  
  13.   
  14. public class CourseLayout extends ViewGroup {  
  15.     private List<CourseView> courses = new ArrayList<CourseView>();  
  16.     private int width;//布局宽度  
  17.     private int height;//布局高度  
  18.     private int sectionHeight;//每节课高度  
  19.     private int sectionWidth;//每节课宽度  
  20.     private int sectionNumber = 12;//一天的节数  
  21.     private int dayNumber = 7;//一周的天数  
  22.     private int divideWidth = 2;//分隔线宽度,dp  
  23.     private int divideHeight = 2;//分隔线高度,dp  
  24.     public CourseLayout(Context context) {  
  25.         this(context, null);  
  26.     }  
  27.   
  28.     public CourseLayout(Context context, AttributeSet attrs) {  
  29.         this(context, attrs, 0);  
  30.     }  
  31.   
  32.     public CourseLayout(Context context, AttributeSet attrs, int defStyle) {  
  33.         super(context, attrs, defStyle);  
  34.         width = getScreenWidth();//默认宽度全屏  
  35.         height = dip2px(600);//默认高度600dp  
  36.         divideWidth = dip2px(2);//默认分隔线宽度2dp  
  37.         divideHeight = dip2px(2);//默认分隔线高度2dp  
  38.     }  
  39.   
  40.     @Override  
  41.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  42.         setMeasuredDimension(width, height);  
  43.     }  
  44.   
  45.     @Override  
  46.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  47.         courses.clear();//清除  
  48.         sectionHeight = (getMeasuredHeight() - divideWidth * sectionNumber)/ sectionNumber;//计算每节课高度  
  49.         sectionWidth = (getMeasuredWidth() - divideWidth * dayNumber)/ dayNumber;//计算每节课宽度  
  50.   
  51.         int count = getChildCount();//获得子控件个数  
  52.         for (int i = 0; i < count; i++) {  
  53.             CourseView child = (CourseView) getChildAt(i);  
  54.             courses.add(child);//增加到list中  
  55.               
  56.   
  57.             int week = child.getWeek();//获得周几  
  58.             int startSection = child.getStartSection();//开始节数  
  59.             int endSection = child.getEndSection();//结束节数  
  60.   
  61.             int left = sectionWidth * (week - 1) + (week) * divideWidth;//计算左边的坐标  
  62.             int right = left + sectionWidth;//计算右边坐标  
  63.             int top = sectionHeight * (startSection - 1) + (startSection) * divideHeight;//计算顶部坐标  
  64.             int bottom = top + (endSection - startSection + 1) * sectionHeight+ (endSection - startSection) * divideHeight;//计算底部坐标  
  65.   
  66.             child.layout(left, top, right, bottom);  
  67.         }  
  68.     }  
  69.   
  70.     public int dip2px(float dip) {  
  71.         float scale = getContext().getResources().getDisplayMetrics().density;  
  72.         return (int) (dip * scale + 0.5f);  
  73.     }  
  74.   
  75.     public int getScreenWidth() {  
  76.         WindowManager manager = (WindowManager) getContext().getSystemService(  
  77.                 Context.WINDOW_SERVICE);  
  78.         DisplayMetrics displayMetrics = new DisplayMetrics();  
  79.         manager.getDefaultDisplay().getMetrics(displayMetrics);  
  80.         return displayMetrics.widthPixels;  
  81.     }  
  82.   
  83. }  

使用控件,记得增加命名空间

[html]  view plain copy
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     xmlns:custom="http://schemas.android.com/apk/res/cn.lizhangqu.kb"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:orientation="vertical"  
  7.     tools:context="${relativePackage}.${activityClass}" >  
  8.   
  9.     <LinearLayout  
  10.         android:id="@+id/weekend"  
  11.         android:layout_width="match_parent"  
  12.         android:layout_height="30dp"  
  13.         android:orientation="horizontal" >  
  14.   
  15.         <Button  
  16.             android:layout_width="0dp"  
  17.             android:layout_height="match_parent"  
  18.             android:layout_weight="1"  
  19.             android:background="@drawable/kb0"  
  20.             android:text="周一"  
  21.             android:textColor="#2e94da"  
  22.             android:textSize="12sp" />  
  23.   
  24.         <Button  
  25.             android:layout_width="0dp"  
  26.             android:layout_height="match_parent"  
  27.             android:layout_weight="1"  
  28.             android:background="@drawable/kb0"  
  29.             android:text="周二"  
  30.             android:textColor="#2e94da"  
  31.             android:textSize="12sp" />  
  32.   
  33.         <Button  
  34.             android:layout_width="0dp"  
  35.             android:layout_height="match_parent"  
  36.             android:layout_weight="1"  
  37.             android:background="@drawable/kb0"  
  38.             android:text="周三"  
  39.             android:textColor="#2e94da"  
  40.             android:textSize="12sp" />  
  41.   
  42.         <Button  
  43.             android:layout_width="0dp"  
  44.             android:layout_height="match_parent"  
  45.             android:layout_weight="1"  
  46.             android:background="@drawable/kb0"  
  47.             android:text="周四"  
  48.             android:textColor="#2e94da"  
  49.             android:textSize="12sp" />  
  50.   
  51.         <Button  
  52.             android:layout_width="0dp"  
  53.             android:layout_height="match_parent"  
  54.             android:layout_weight="1"  
  55.             android:background="@drawable/kb0"  
  56.             android:text="周五"  
  57.             android:textColor="#2e94da"  
  58.             android:textSize="12sp" />  
  59.   
  60.         <Button  
  61.             android:layout_width="0dp"  
  62.             android:layout_height="match_parent"  
  63.             android:layout_weight="1"  
  64.             android:background="@drawable/kb0"  
  65.             android:text="周六"  
  66.             android:textColor="#2e94da"  
  67.             android:textSize="12sp" />  
  68.   
  69.         <Button  
  70.             android:layout_width="0dp"  
  71.             android:layout_height="match_parent"  
  72.             android:layout_weight="1"  
  73.             android:background="@drawable/kb0"  
  74.             android:text="周日"  
  75.             android:textColor="#2e94da"  
  76.             android:textSize="12sp" />  
  77.     </LinearLayout>  
  78.     <ScrollView  
  79.         android:id="@+id/scrollview"  
  80.         android:layout_width="wrap_content"  
  81.         android:layout_height="wrap_content"  
  82.         android:scrollbars="none" >  
  83.   
  84.         <cn.lizhangqu.kb.view.CourseLayout  
  85.             android:id="@+id/courses"  
  86.             android:layout_width="wrap_content"  
  87.             android:layout_height="wrap_content"  
  88.             android:background="#e8e8e8"  
  89.             >  
  90.         </cn.lizhangqu.kb.view.CourseLayout>  
  91.     </ScrollView>  
  92.   
  93. </LinearLayout>  


[java]  view plain copy
  1. package cn.lizhangqu.kb.activity;  
  2.   
  3. import java.util.List;  
  4.   
  5. import android.app.Activity;  
  6. import android.content.res.ColorStateList;  
  7. import android.graphics.Color;  
  8. import android.os.Bundle;  
  9. import android.util.TypedValue;  
  10. import android.view.Gravity;  
  11. import cn.lizhangqu.kb.R;  
  12. import cn.lizhangqu.kb.model.Course;  
  13. import cn.lizhangqu.kb.service.CourseService;  
  14. import cn.lizhangqu.kb.util.CommonUtil;  
  15. import cn.lizhangqu.kb.view.CourseLayout;  
  16. import cn.lizhangqu.kb.view.CourseView;  
  17.   
  18. /** 
  19.  * @author lizhangqu 
  20.  * @date 2015-2-1 
  21.  */  
  22. public class CourseActivity extends Activity {  
  23.   
  24.     //某节课的背景图,用于随机获取  
  25.     private int[] bg={R.drawable.kb1,R.drawable.kb2,R.drawable.kb3,R.drawable.kb4,R.drawable.kb5,R.drawable.kb6,R.drawable.kb7};  
  26.     private CourseService courseService;  
  27.     private CourseLayout layout;  
  28.     @Override  
  29.     protected void onCreate(Bundle savedInstanceState) {  
  30.         super.onCreate(savedInstanceState);  
  31.         setContentView(R.layout.activity_course);  
  32.         initValue();  
  33.         initView();  
  34.     }  
  35.   
  36.     /** 
  37.      * 初始化变量 
  38.      */  
  39.     private void initValue() {  
  40.         courseService=CourseService.getCourseService();  
  41.     }  
  42.     /** 
  43.      * 初始化视图 
  44.      */  
  45.     private void initView() {  
  46.         //这里有逻辑问题,只是简单的显示了下数据,数据并不一定是显示在正确位置  
  47.         //课程可能有重叠  
  48.         //课程可能有1节课的,2节课的,3节课的,因此这里应该改成在自定义View上显示更合理  
  49.         List<Course> courses=courseService.findAll();//获得数据库中的课程  
  50.         layout=(CourseLayout) findViewById(R.id.courses);  
  51.         Course course=null;  
  52.         //循环遍历  
  53.         for (int i = 0; i < courses.size(); i++) {  
  54.             course=courses.get(i);  
  55.             CourseView view=new CourseView(getApplicationContext());  
  56.             view.setCourseId(course.getId());  
  57.             view.setStartSection(course.getStartSection());  
  58.             view.setEndSection(course.getEndSection());  
  59.             view.setWeek(course.getDayOfWeek());  
  60.             int bgRes=bg[CommonUtil.getRandom(bg.length-1)];//随机获取背景色  
  61.             view.setBackgroundResource(bgRes);  
  62.             view.setText(course.getCourseName()+"@"+course.getClasssroom());  
  63.             view.setTextColor(Color.WHITE);  
  64.             view.setTextSize(12);  
  65.             view.setGravity(Gravity.CENTER);  
  66.             layout.addView(view);  
  67.             }  
  68.     }  
  69. }  

背景图使用的是shape,这里贴出其中一个,其余的就只是颜色不同

[html]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <shape  
  3.     xmlns:android="http://schemas.android.com/apk/res/android"  
  4.     android:shape="rectangle">  
  5.     <solid android:color="#ef9ea0"/>  
  6.     <stroke  
  7.         android:width="1dp"  
  8.         android:color="#ef9ea0"  
  9.         />  
  10.     <corners android:radius="5dp"/>  
  11.    
  12. </shape>  

至此,超级课程表的一键提取课表功能就完成了。显示最终效果见下方



整个过程可简单概括为抓包分析,数据提取,数据显示,其中关键的一步就是数据的提取。这个过程中有个注意点就是抓课程数据的时候header请求头信息里的referer信息请务必设置为登录成功后的网址,即http://***.***.***.***/xs_main.aspx?xh=XH,否则抓数据的时候页面会被循环重定向,将抓不到数据,程序也会报异常。


  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值