Android 简单的Http框架

编写Android程序,离不开和Http打交道。android 的单线程UI模型,使得处理Http这样,耗时的操作的操作变得麻烦。传统的作法有Thread + Handler和AsyncTask 而这两种方式都是需要自己写很多重复的代码如创建HttpClient etc.不符合DRY(Don't repeat yourself),使Activity中需要作的逻辑处理非常多,代码变得臃肿, 导出,可复用性差,后期维护性差 。Activity的生命周期是极其不稳定的。无法控制,无法预判。试想下面的一段场景,用户正在向服务器发送一条信息,由于网速慢,或者网络 出现阻塞,发送到接收持续了几十秒,在这期间来了个电话或者用户决定切换出去换一首歌,这时候线程完成的提交工作并从服务器获得了数据再更新UI的时候,原来的 Activity已经不存在了。更有可能的是系统发现资源不够用了,决定直接把我们的进程杀掉了。 如果你也和我一样遇到了这些问题,这篇文章就是为你写的。
demo下载地址http://115.com/file/e6kn60jk# HttpOperationDemo.zip

Hello World

看段小程序,程序本身很简单,Activity中有三个控件上边是个EditText,中间是个TextView,下边是个Button,用户在EditText中输入一段网址,点击按钮,把 网页中html内容显示到TextView当中。
package com.chon.demo.httpoperation; import java.io.IOException; import com.chon.httpoperation.GetOperation; import com.chon.httpoperation.HandledResult; import com.chon.httpoperation.OperationListener; //省略了一些引入 public class GetDemoActivity extends Activity { private EditText urlEdit; private TextView htmlText; private Button submitButton; OperationListener listener = new OperationListener(){ @Override public void _disibledevent=>long arg0, Bundle arg1, Exception e) { htmlText.setText("E:" + e); } @Override public void _disibledevent=>long arg0, Bundle arg1, IOException e) { htmlText.setText("IOE:" + e); } @Override public void _disibledevent=>long arg0, Bundle arg1, int code, String content) { htmlText.setText("code:" + code + "content:" + content); } @Override public void _disibledevent=>long arg0, Bundle arg1, HandledResult result) { htmlText.setText(result.extras.getString("html")); } }; private MyApplication mApplication; @Override protected void _disibledevent=>super.onCreate(savedInstanceState); this.mApplication = (MyApplication)this.getApplication(); initUI(); } private void initUI(){ this.setContentView(R.layout.get_demo); urlEdit = (EditText)findViewById(R.id.urlEdit); htmlText = (TextView)findViewById(R.id.htmlText); htmlText.setMovementMethod(ScrollingMovementMethod.getInstance()); submitButton = (Button)findViewById(R.id.submitButton); submitButton.setOnClickListener(new _disibledevent=>public void _disibledevent=>new GetOperation(10, urlEdit.getText().toString(), DummyHtmlHandler.class, listener); mApplication.request(getOperation); } }); } }
处理Http请求可以如此简单和清晰。我们来看一下上面的代码都作了些什么
submitButton.setOnClickListener(new _disibledevent=>public void _disibledevent=>new GetOperation(10, urlEdit.getText().toString(), DummyHtmlHandler.class, listener); mApplication.request(getOperation); } });
当按钮被按下时,创建了一个GepOperation对象,在构造函数中传入了一定的参数,第一个是一次请求的id,因为本例中只有一次请求,所以这个参数可以 忽略,第二个是请求的url,第三个是一个类,表示如果处理服务器端返回的内容(如xml解析规则等,因本例不作任何处理,这个处理类也非常简单,一会儿 再说),第四个是一个OperationListener 的对象,一个回调方法,当HttpGet请求处理完毕后会把结果回调给这个对象。因为是从UI线程回调,我们可以直接 对UI进行操作。 下面我来解释下这期间到底发生了什么,GetOperation封装了对HttpGet的操作,当调用mApplication.request(getOperation)的时候,mApplication负责把这个请求 对象转发到后台的Service当中,并在一个线程池中处理运行,当服务器响应后,后台的Service会把服务器返回的数据交给DummyHtmlHanlder这个类处理,并把处理 结果从UI线程回调listener的onResult()返回给当前的Activity。 再看下这个极其简单的DummyHtmlHandler:
package com.chon.demo.httpoperation; import java.io.InputStream; import android.os.Bundle; import com.chon.httpoperation.Handleable; import com.chon.httpoperation.HandledResult; public class DummyHtmlHandler implements Handleable { @Override public int getContentType() { return Handleable.TYPE_STRING; } @Override public HandledResult handle(String content, Bundle bundle) { Bundle extras = new Bundle(); extras.putString("html", content); return new HandledResult(extras, null, null); } @Override public HandledResult handle(InputStream arg0, Bundle arg1) { return null; } }
希望处理服务器响应数据的类需要实现Handleable这个接口,接口定义了三个方法public int getContentType()是用来表示,自己希望得到什么形式的数据 有TYPE_STRING 和 TYPE_STREAM ,Service会根据这个值来回调下面的两个handle中的一种(也就是说如果getContentType返回的是TYPE_STRING, public HandledResult handle(String content, Bundle bundle),如果是TYPE_STREAMpublic HandledResult handle(InputStream arg0, Bundle arg1) 并将服务器返回的数据传入该方法中。处理后的数据通过一个叫HandledResult的类的对你返回给Activity,其实是个很简单的类,里边有三 个成员变量 一个Bundle,一个ArrayList和一个Object。起到信息传输的作用。好了,最后list.onResult(long arg0, Bundle arg1, HandledResult result)被回调,我们从 HandledResult对象中取出我们处理好的数据,更新UI就可以了。 这个小例子可能看不出,多大的好处,我们再看一下RssActivity中的代码。
package com.chon.demo.httpoperation; //省略了一些引入 public class RssActivity extends Activity { private OperationListener listener = new OperationListener(){ @Override public void _disibledevent=>long arg0, Bundle arg1, Exception e) { setTitle("Exception " + e); } @Override public void _disibledevent=>long arg0, Bundle arg1, IOException e) { setTitle("IOException " + e); } @Override public void _disibledevent=>long arg0, Bundle arg1, int code, String arg3) { setTitle("Oh Oh " + code);//404之类的会触发本回调 } @SuppressWarnings("unchecked") @Override public void _disibledevent=>long arg0, Bundle arg1, HandledResult result) { System.out.println("size:" + result.results.size()); setTitle("Loading Complete"); mAdapter.setData((ArrayList) result.results); } }; private MyApplication mApplication; private ListView rssListView; private RssAdapter mAdapter; @Override protected void _disibledevent=>super.onCreate(savedInstanceState); this.mApplication = (MyApplication) this.getApplication(); initUI(); } private void initUI(){ this.setContentView(R.layout.rss); this.rssListView = (ListView)findViewById(R.id.rssListView); rssListView.setOnItemClickListener(new _disibledevent=>public void _disibledevent=>int position, long id) { Intent it = new Intent(Intent.ACTION_VIEW, Uri.parse(mAdapter.getData().get(position).getLink())); it.setClassName("com.android.browser", "com.android.browser.BrowserActivity"); startActivity(it); } }); mAdapter = new RssAdapter(this.getLayoutInflater()); rssListView.setAdapter(mAdapter); findViewById(R.id.button).setOnClickListener(new _disibledevent=>public void _disibledevent=>void loadRss(){ setTitle("Loading....."); String url = "http://rss.sina.com.cn/news/marquee/ddt.xml";//新浪的Rss订阅 GetOperation getOperation = new GetOperation(10,url,RssHandler.class, listener ); mApplication.request(getOperation); } }
你会发现虽然我们的程序复杂了许多,但Activity的代码量并没有增加多少,我们不需要创建Handler,定义许多常量,创建Thread对象 etc.一切都己被处理好了 我们需要作的就只有简单的三步,创建一个对象,编写自己的处理类,被回调时更新UI(或者别的)。 有兴趣的可以看看Rss的解析类,本例中使用了XmlPull,当然你可以使用任何一种解析方式。或者解析json etc.(面向接口编程的好处 :))
package com.chon.demo.rssreader; public class RssHandler implements Handleable { static final String ITEM = "item"; private int currentstate = -1; final int TITLE = 1; final int LINK = 2; final int DESCRIPTION = 3; final int PUBDATE = 4; @Override public int getContentType() { return Handleable.TYPE_STREAM; } @Override public HandledResult handle(String content,Bundle extras) { return null; } @Override public HandledResult handle(InputStream content,Bundle extras) { XmlPullParserFactory xmlPullParserFactory; XmlPullParser xmlPullParser = null; try { xmlPullParserFactory = XmlPullParserFactory.newInstance(); xmlPullParserFactory.setNamespaceAware(true); xmlPullParser = xmlPullParserFactory.newPullParser(); } catch (XmlPullParserException e1) { e1.printStackTrace(); } RssItem rssItem = null; ArrayList rssItemList = new ArrayList(); boolean isItemTAG = false; try { xmlPullParser.setInput(content, "utf-8"); int eventType = xmlPullParser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { if (eventType == XmlPullParser.START_DOCUMENT) { System.out.println("start Document..."); } else if (eventType == XmlPullParser.END_DOCUMENT) { System.out.println("end Document..."); } else if (eventType == XmlPullParser.START_TAG) { if (xmlPullParser.getName().equals("item")) { rssItem = new RssItem(); isItemTAG = true; } if (xmlPullParser.getName().equals("title")) { currentstate = TITLE; } if (xmlPullParser.getName().equals("link")) { currentstate = LINK; } if (xmlPullParser.getName().equals("description")) { currentstate = DESCRIPTION; } if (xmlPullParser.getName().equals("pubDate")) { currentstate = PUBDATE; } } else if (eventType == XmlPullParser.END_TAG) { if (xmlPullParser.getName().equals("item")) { rssItemList.add(rssItem); } } else if (eventType == XmlPullParser.TEXT) { if (isItemTAG) { switch (currentstate) { case TITLE: System.out.println(xmlPullParser.getText()); rssItem.setTitle(clearSpecialChar(xmlPullParser .getText())); currentstate = -1; break; case LINK: rssItem.setLink(clearSpecialChar(xmlPullParser .getText())); currentstate = -1; break; case DESCRIPTION: rssItem.setDescription(clearSpecialChar(xmlPullParser .getText())); currentstate = -1; break; case PUBDATE: rssItem.setPubData(clearSpecialChar(xmlPullParser .getText())); currentstate = -1; break; default: break; } } } eventType = xmlPullParser.next(); } } catch (XmlPullParserException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return new HandledResult(null,rssItemList,null); } private String clearSpecialChar(String s) { Pattern pattern = Pattern.compile("\\s|\\r|\\n|\\t"); Matcher matcher = pattern.matcher(s); return matcher.replaceAll("").trim(); } }
还有一个从网上下载图片的示例大家自己看吧,不在这里进行讲解了。如果你想增加别的功能的支持,有两种选择,比如只是想增加一个功能 可以下载文件并存入sd卡什么的,operation对象,可以接收一个Bundle,你可以把文件路径,名字等放到 这个Bundle当中,并写一个自己的 处理类(实现Handleable的)。或者你想加入对WebService的支持(现在正在作,但功能不是很完善) 要只需要继承AbstractOperations类,覆写handleOperation()方法(逻辑处理创建HttpClient等放在这里,而不是run()), 并把处理结果或者产生的错误等信息,通过父类提供的protected void sendExceptionMsg(final Exception e), protected void sendSuccessMsg(final HandledResult result) 等方法回调给OperationsListener对象。 对了别忘 了继承HttpOperationApplication和HttpOperationService在manifest中注册。并把Service告诉Application。像这样:
package com.chon.httpoperation.test; import com.chon.httpoperation.HttpOperationApplication; public class MyApplication extends HttpOperationApplication { { this.setOperationService(MyService.class); } }
HttpOperationService的子类可以通过setIdleInMinute(),Service空闲多少分钟时自行关闭,不过不用担心 当有心的请求时Service会自动开启。还有一些功,比如有个cachable接口,可以在operation对象中传入实现了 这个接口的对象,当Service执行完后发现如果调用的Activity(确切说是OperationListener)不存在了,会把处理的 结果缓存的这个对象当中。具体操作,以后有时间再写。 个人能力有限难免会有一些错误,如发现什么bug或者有什么建议或者想法,欢迎和我联系 [email protected]
如果有什么疑问,发现bug或者有更好的想法或者建议,请发邮件至[email protected]

Tags: 

延伸阅读

最新评论

发表评论