我的Android 4 学习系列之使用 Internet 资源
目录
- 连接Internet资源
- 分析XML资源
- 使用Download Manager下载文件
- 查询Download manager
- 使用Account Manager 对 Google App Engine 进行身份认证
连接Internet资源
在访问internet之前,需要在应用程序清单中添加一个INTERNET uses-permission节点:
<uses-permission android:name=”android.permission.INTERNET” />
String myFeed = getString(R.id.action_settings);
try{
URL url = new URL(myFeed);
// 创建新的HTTP URL链接
URLConnection urlConnection = url.openConnection();
HttpURLConnection httpURLConnection = (HttpURLConnection)urlConnection;
int responseCode = httpURLConnection.getResponseCode();
if(responseCode == HttpURLConnection.HTTP_OK){
InputStream stream = httpURLConnection.getInputStream();
processStream(stream);
}
}
catch(MalformedURLException e){
Log.d(TAG, "Malformed URL Exception.");
}
catch(IOException e){
Log.d(TAG, "IO Exception.");
}
分析XML资源
与DOM分析器不同,Pull Parser 用一个顺序的事件和标记序列呈现文档的元素。
你在文档中的位置是由当前的事件表示的。通过调用 getEventType,可以确定当前的事件。每个文档都从START_DOCMENT事件开始,到END_DOCMENT事件结束。
要遍历标记,只需要调用next,这会遍历一系列匹配的(并且通常是嵌套)START_TAG 和 END_TAG 事件。通过调用getName可以提取每个标记的名称,通过调用getNextText可以提取每组标记之间的文本。
private void processStream(InputStream stream) {
// 创建XML Pull 分析器
XmlPullParserFactory factory;
try{
factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser xpp = factory.newPullParser();
//分配新的输入流
xpp.setInput(stream, null);
int eventType = xpp.getEventType();
//继续直至文档末尾
while (eventType != XmlPullParser.END_DOCUMENT) {
//检查结果标记的开始标记
if(eventType == XmlPullParser.START_TAG && xpp.getName().equals("result")){
eventType = xpp.next();
String name = "";
//处理结果标记中的每个结果
while (!(eventType == XmlPullParser.END_TAG && xpp.getName().equals("result"))) {
//检查结果标记中名称标记
if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("name")) {
//提取POI名称
name = xpp.nextText();
//移动到下一个标记
eventType = xpp.next();
}
//... 对每个POI名称执行某些操作
}
//移动到下一个标记
eventType = xpp.next();
}
}
}
catch(XmlPullParserException e){
Log.d("PULPARSER", "XML Pull Parser Exception", e);
} catch (IOException e) {
Log.d("PULLPARSER", "IO Exception." ,e);
e.printStackTrace();
}
}
创建一个地震查看器
1. 首先,创建Earthquake项目,并创建EarthquakeActivity。
2. 创建一个ListFragment的扩展EarthquakeListFragment。该Fragment用于显示地震列表。
public class EarthquakeListFragment extends ListFragment {
}
3. 修改main.xml布局资源来包含第2步创建的Fragment。一定要保证对它的进行的命名,这样就可以对它在Activity中引用。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.earthquake.EarthquakeActivity" >
<fragment android:name="com.example.earthquake.EarthquakeListFragment"
android:id="@+id/EarthquakeListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
4. 创建一个新的公有Quake类。这个类用来存储每一次地震的详细的数据(日期、细节、位置、震级、链接)。重写toString包装数据的输出格式。
package com.example.earthquake;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.annotation.SuppressLint;
import android.location.Location;
@SuppressLint("SimpleDateFormat") public class Quake {
private Date date;
private String details;
private Location location;
private double magnitue;
private String link;
public Date getDate(){ return date; }
public String getDetails(){ return details; }
public Location getLocation(){ return location; }
public String getLink(){ return link; }
public Quake(Date _date, String _details, Location _location, double _magnitue, String _link){
this.date = _date;
this.details = _details;
this.location = _location;
this.magnitue = _magnitue;
this.link = _link;
}
@Override
public String toString() {
return new SimpleDateFormat("HH.mm").format(date) + ": " + this.magnitue + " " + this.details;
}
}
5. 在EarthquakeListFragment中,重写onActivityCreated事件存储Quake对象列表ArrayList,并使用一个ArraryAdapter将这个数组绑定到底层的ListView。
ArrayAdapter<Quake> aa;
ArrayList<Quake> earthquakes = new ArrayList<Quake>();
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
aa = new ArrayAdapter<Quake>(getActivity(), android.R.layout.simple_list_item_1, earthquakes);
}
6. 接下来处理地震源。Demo中使用的数据源是1天内USGS提供的震级大于2.5的地震数据。这里使用资源的方式将源的位置加进来:
<string name="quake_feed">http://earthquake.usgs.gov/eqcenter/catalogs/1day-M2.5.xml</string>
7. 访问Internet需要加入相应的访问权限。
<uses-permission android:name="android.permission.INTERNET" />
8. 返回到EarthquakeListFragment,创建refreshEarthquake方法。该方法链接到地震源并对其数据进行分析。
private static final String TAG = "EARTHQUAKE";
private Handler handler = new Handler();
public void refreshEarthquake(){
//获得xml
URL url;
try {
String quakeFeed = getString(R.string.quake_feed);
url = new URL(quakeFeed);
HttpURLConnection cn = (HttpURLConnection)url.openConnection();
int responseCode = cn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream stream = cn.getInputStream();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
//分析地震源
org.w3c.dom.Document doc = db.parse(stream);
Element element = doc.getDocumentElement();
//清除旧的地震数据源
earthquakes.clear();
//获得每个地震项的列表
NodeList nodeList = element.getElementsByTagName("entry");
if (nodeList != null && nodeList.getLength() > 0) {
for (int i = 0; i < nodeList.getLength(); i++) {
Element entry = (Element)nodeList.item(i);
Element title = (Element)entry.getElementsByTagName("title").item(0);
Element g = (Element)entry.getElementsByTagName("georss:point").item(0);
if(g == null) continue;
Element when = (Element)entry.getElementsByTagName("updated").item(0);
Element link = (Element)entry.getElementsByTagName("link").item(0);
String details = title.getFirstChild().getNodeValue();
String hostname = "http://earthquak.usgs.gov";
String linkString = hostname + link.getAttribute("href");
String point = g.getFirstChild().getNodeValue();
String dt = when.getFirstChild().getNodeValue();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
Date qDate = new GregorianCalendar(0,0,0).getTime();
try {
qDate = sdf.parse(dt);
} catch (ParseException e) {
Log.d(TAG, "Date Parse Exception.", e);
}
String[] location = point.split(" ");
Location l = new Location("dummyGPS");
l.setLatitude(Double.parseDouble(location[0]));
l.setLongitude(Double.parseDouble(location[1]));
String magnitudeString = details.split(" ")[1];
int end = magnitudeString.length() - 1;
double magnitude = Double.parseDouble(magnitudeString.substring(0, end));
details = details.split(",")[1].trim();
final Quake quake = new Quake(qDate, details, l, magnitude, linkString);
//处理一个发现的地震
handler.post(new Runnable() {
@Override
public void run() {
addNewQuake(quake);
}
});
}
}
}
} catch(MalformedURLException e){
Log.d(TAG, "MalformedURLException");
}
catch(IOException e){
Log.d(TAG, "IO Exception.");
}
catch(ParserConfigurationException e){
Log.d(TAG, "ParserConfigurationException");
}
catch (SAXException e) {
Log.d(TAG, "SAXException");
}
finally{
}
}
9. 更新AddNewEarthquake()
private void addNewQuake(Quake quake) {
//将新地震添加到地震列表中
earthquakes.add(quake);
//向Array Adapter通知改变
aa.notifyDataSetChanged();
}
10. 修改 onActivityCreated 方法,在启动时调用 refreshEarthquakes。
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
aa = new ArrayAdapter<Quake>(getActivity(), android.R.layout.simple_list_item_1, earthquakes);
setListAdapter(aa);
Thread t = new Thread(new Runnable() {
@Override
public void run() {
refreshEarthquake();
}
});
t.start();
}
运行效果图:
使用Download Manager下载文件
1. 下载文件
DownloadManager 是文件下载的管理类。
String seviceString = Context.DOWNLOAD_SERVICE;
DownloadManager dm = (DownloadManager)getSystemService(seviceString);
Uri uri = Uri.parse("http://deveoper.android.com/shareables/icon_templates-v4.0.zip");
DownloadManager.Request request = new Request(uri);
long reference = dm.enqueue(request);
根据返回的引用值,可以对某个下载进行进一步的操作或者查询,包括查看状态,取消下载。
也可以给下载指定限定条件,setAllowedNetworkType方法可以限制下载类型为Wi-Fi或者移动网络,而手机漫游,setAllowedOverRomaing方法可以有预见性的阻止下载。
request.setAllowedNetworkTypes(Request.NETWORK_WIFI);
监控下载的完成:
IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
long reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if(myDownloadReferenc == reference){
// 对下载的文件进行的操作
}
}
};
registerReceiver(receiver, filter);
2. 自定义 Download Manager Notification
默认情况,会为Download Manager 显示一个持续的Notification。每个Notification都会显示一个下载文件名和下载进度。
Download Manager 可以为每个下载请求自定义Notification,包括把它完全隐藏。
request.setTitle(“Earthquake”);
request.setDescription(“Earthquake XML”);
通过setNotificationVisibility方法并使用下面的标识之一来控制何时以及是否应该为请求显示一个Notification:
Request.VISIBILITY_VISIBLE 默认选项。持续显示Notification,下载完成移除Notification。
Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED 下载时持续显示,完成后继续显示Notification。
Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETED 只有下载完显示Notification。
Request.VISIBILITY_HIDDEN 不为此下载显示Notification。为了使用这个标识,必须在manifest中指定uses-permission为DOWNLOAD_WITHOUT_NOTIFICATION
3. 指定下载位置
默认情况下,Download Manager会把下载的文件保存到共享下载缓存中,而且使用系统生成的文件名。每一个请求对象都可以指定一个下载路径位置,想让下载文件存储到外部存储器必须在manifest中注册uses-permission:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
指定下载路径:
request.setDestinationUri(uri);
4. 取消下载,删除下载
dm.remove(reference, reference1);
查询Download manager
query 一个下载的请求的状态、进度、和详细信息。该方法会返回下载的Cursor对象。
query 方法接收DownloadManager.Query 对象作为参数。使用setFieldById 方法给 Query 对象指定一个下载引用 ID 的序列,或者使用 setFilterByStatus 方法过滤下载的状态,该方法使用某个DownloadManager.STATUS_* 常量来指定下载的运行、暂停、失败、或者成功。
DownloadManager包含了很多COLUMN_*静态常量,可以用他们来查询结果Cursor。可以得到每个下载的详细信息,包括状态、文件大小、目前下载的字节数、标题、描述、URI、本地文件名和URI、媒体类型以及Media Provider下载URI。
@Override
public void onReceive(Context context, Intent intent) {
long reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if(myDownloadReferenc == reference){
// 对下载的文件进行的操作
Query q = new Query();
q.setFilterById(reference);
Cursor download = dm.query(q);
if (download.moveToFirst()) {
int fileNameIdx = download.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME);
int fileUriIdx = download.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI);
String fileName = download.getString(fileNameIdx);
String fileUri = download.getString(fileUriIdx);
}
download.close();
}
}
使用Account Manager 对 Google App Engine 进行身份认证
使用 Google Play Store。
如果应用程序连接到 Goolge App Engine 的后端来存储和检索一个特定用户的相关数据,那么可以使用Account Manager来处理身份验证。
要想使用这一功能,需要添加uses-permission标签:android.permission.GET_ACCOUNTS
1. 连接到 Google App Engine
请求一个token:
String acctSvc = Context.ACCOUNT_SERVICE;
AccountManager am = (AccountManager)getSystemService(acctSvc);
Account[] accounts = am.getAccountsByType("com.google");
if (accounts.length > 0) {
am.getAuthToken(accounts[0], "ah", false, callback, null);
}
2. 下载数据而不会耗尽电量的最佳实践
移动设备的无线电的激活状态时会消耗大量的电量,因此,考虑应用程序的连接模式如何影响它所应用的无线电硬件的操作是很重要的工作。
- 主动预取
- 将连接和下载绑定
- 重用现有连接而不是创建新连接
- 尽可能少调用重复下载