zoukankan      html  css  js  c++  java
  • Android网络编程之使用HTTP訪问网络资源

    使用HTTP訪问网络资源

           前面介绍了 URLConnection己经能够很方便地与指定网站交换信息,URLConnection另一个子类:HttpURLConnection,HttpURLConnection 在 LIRLConnection的基础上做了进一步改进,添加了一些用于操作http资源的便捷方法。

    1.使用HttpURLConnection

          HttpURLConnection继承了URLConnection,因此也可用于向指定站点发送GET请求 POST请求。它在URLConnection的基础上提供了例如以下便捷的方法。

    1) Int getResponseCode():获取server的响应代码。

    2) String getResponseMessage():获取server的响应消息。

    3) String getRequestMethod():获取发送请求的方法。

    4) void setRequestMethod(String method):设置发送请求的方法。

    以下通过个有用的演示样例来示范使用HttpURLConnection实现多线程下载。

    1.1实例:多线程下载

           使用多线程下载文件能够更快地完毕文件的下载,由于client启动多个线程进行下寒意味着server也须要为该client提供相应的服务。如果server同一时候最多服务100个用户,server中一条线程相应一个用户,100条线程在计算机内并发运行,也就是由CPU划分史 片轮流运行,如果A应用使用了 99条线程下载文件,那么相当于占用了 99个用户的资源自然就拥有了较快的下载速度。

    提示:实际上并非client并发的下载线程越多,程序的下载速度就越快,由于当client开启太多的并发线程之后,应用程序须要维护每条线程的开销、线程同步的开销,这些开销反而会导致下载速度减少.

    1.2为了实现多线程下载,程序可按例如以下步骤进行:

    Ø 创建URL对象。

    Ø 获取指定URL对象所指向资源的大小(由getContentLength()方法实现),此处用了 HttpURLConnection 类。

    Ø 在本地磁盘上创建一个与网络资源同样大小的空文件。

    Ø 计算每条线程应该下载网络资源的哪个部分(从哪个字节開始,到哪个字节结束,依次创建、启动多条线程来下载网络资源的指定部分。

    1.2该程序提供的下载工具类代码例如以下。

    package com.jph.net;
    
    import java.io.InputStream;
    import java.io.RandomAccessFile;
    import java.net.HttpURLConnection;
    import java.net.URL;
    
    /**
     * Description:
     * 创建ServerSocket监听的主类
     * @author  jph
     * Date:2014.08.27
     */
    public class DownUtil
    {
    	/**下载资源的路径**/ 
    	private String path;
    	/**下载的文件的保存位置**/ 
    	private String targetFile;
    	/**须要使用多少线程下载资源**/  
    	private int threadNum;
    	/**下载的线程对象**/  
    	private DownThread[] threads;
    	/**下载的文件的总大小**/ 
    	private int fileSize;
    
    	public DownUtil(String path, String targetFile, int threadNum)
    	{
    		this.path = path;
    		this.threadNum = threadNum;
    		// 初始化threads数组
    		threads = new DownThread[threadNum];
    		this.targetFile = targetFile;
    	}
    
    	public void download() throws Exception
    	{
    		URL url = new URL(path);
    		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    		conn.setConnectTimeout(5 * 1000);
    		conn.setRequestMethod("GET");
    		conn.setRequestProperty(
    			"Accept",
    			"image/gif, image/jpeg, image/pjpeg, image/pjpeg, "
    			+ "application/x-shockwave-flash, application/xaml+xml, "
    			+ "application/vnd.ms-xpsdocument, application/x-ms-xbap, "
    			+ "application/x-ms-application, application/vnd.ms-excel, "
    			+ "application/vnd.ms-powerpoint, application/msword, */*");
    		conn.setRequestProperty("Accept-Language", "zh-CN");
    		conn.setRequestProperty("Charset", "UTF-8");
    		conn.setRequestProperty("Connection", "Keep-Alive");
    		// 得到文件大小
    		fileSize = conn.getContentLength();
    		conn.disconnect();
    		int currentPartSize = fileSize / threadNum + 1;
    		RandomAccessFile file = new RandomAccessFile(targetFile, "rw");
    		// 设置本地文件的大小
    		file.setLength(fileSize);
    		file.close();
    		for (int i = 0; i < threadNum; i++)
    		{
    			// 计算每条线程的下载的開始位置
    			int startPos = i * currentPartSize;
    			// 每一个线程使用一个RandomAccessFile进行下载
    			RandomAccessFile currentPart = new RandomAccessFile(targetFile,
    				"rw");
    			// 定位该线程的下载位置
    			currentPart.seek(startPos);
    			// 创建下载线程
    			threads[i] = new DownThread(startPos, currentPartSize,
    				currentPart);
    			// 启动下载线程
    			threads[i].start();
    		}
    	}
    
    	// 获取下载的完毕百分比
    	public double getCompleteRate()
    	{
    		// 统计多条线程已经下载的总大小
    		int sumSize = 0;
    		for (int i = 0; i < threadNum; i++)
    		{
    			sumSize += threads[i].length;
    		}
    		// 返回已经完毕的百分比
    		return sumSize * 1.0 / fileSize;
    	}
    
    	private class DownThread extends Thread
    	{
    		/**当前线程的下载位置**/ 
    		private int startPos;
    		/**定义当前线程负责下载的文件大小**/ 
    		private int currentPartSize;
    		/**当前线程须要下载的文件块**/ 
    		private RandomAccessFile currentPart;
    		/**定义该线程已下载的字节数**/ 
    		public int length;
    
    		public DownThread(int startPos, int currentPartSize,
    			RandomAccessFile currentPart)
    		{
    			this.startPos = startPos;
    			this.currentPartSize = currentPartSize;
    			this.currentPart = currentPart;
    		}
    
    		@Override
    		public void run()
    		{
    			try
    			{
    				URL url = new URL(path);
    				HttpURLConnection conn = (HttpURLConnection)url
    					.openConnection();
    				conn.setConnectTimeout(5 * 1000);
    				conn.setRequestMethod("GET");
    				conn.setRequestProperty(
    					"Accept",
    					"image/gif, image/jpeg, image/pjpeg, image/pjpeg, "
    					+ "application/x-shockwave-flash, application/xaml+xml, "
    					+ "application/vnd.ms-xpsdocument, application/x-ms-xbap, "
    					+ "application/x-ms-application, application/vnd.ms-excel, "
    					+ "application/vnd.ms-powerpoint, application/msword, */*");
    				conn.setRequestProperty("Accept-Language", "zh-CN");
    				conn.setRequestProperty("Charset", "UTF-8");
    				InputStream inStream = conn.getInputStream();
    				// 跳过startPos个字节,表明该线程仅仅下载自己负责哪部分文件。
    				inStream.skip(this.startPos);
    				byte[] buffer = new byte[1024];
    				int hasRead = 0;
    				// 读取网络数据,并写入本地文件
    				while (length < currentPartSize
    					&& (hasRead = inStream.read(buffer)) > 0)
    				{
    					currentPart.write(buffer, 0, hasRead);
    					// 累计该线程下载的总大小
    					length += hasRead;
    				}
    				currentPart.close();
    				inStream.close();
    			}
    			catch (Exception e)
    			{
    				e.printStackTrace();
    			}
    		}
    	}
    }
    

         上而的DownUtil工具类中包含一个DownloadThread内部类,该内部类的run()方法中负责打开远程资源的输入流,并调用inputStream的skip(int)方法跳过指定数量的字节,这样就让该线程读取由它自己负责下载的部分。

          提供了上面的DownUtil工具类之后,接下来就能够在Activity中调用该DownUtil类来运行下载任务,该程序界面中包括两个文本框,一个用于输入网络文件的源路径,还有一个用于指定下载到本地的文件的文件名称,该程序的界面比較简单,故此处不再给出界面布局代码。该程序的Activity代码例如以下。

    package com.jph.net;
    
    import java.util.Timer;
    import java.util.TimerTask;
    import android.app.Activity;
    import android.content.Context;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Looper;
    import android.os.Message;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.ProgressBar;
    import android.widget.Toast;
    
    /**
     * Description:
     * 多线程下载
     * @author  jph
     * Date:2014.08.27
     */
    public class MultiThreadDown extends Activity
    {
    	EditText url;
    	EditText target;
    	Button downBn;
    	ProgressBar bar;
    	DownUtil downUtil;
    	private int mDownStatus;
    
    	@Override
    	public void onCreate(Bundle savedInstanceState)
    	{
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.main);
    		// 获取程序界面中的三个界面控件
    		url = (EditText) findViewById(R.id.url);
    		target = (EditText) findViewById(R.id.target);
    		downBn = (Button) findViewById(R.id.down);
    		bar = (ProgressBar) findViewById(R.id.bar);
    		// 创建一个Handler对象
    		final Handler handler = new Handler()
    		{
    			@Override
    			public void handleMessage(Message msg)
    			{
    				if (msg.what == 0x123)
    				{
    					bar.setProgress(mDownStatus);
    				}
    			}
    		};
    		downBn.setOnClickListener(new OnClickListener()
    		{
    			@Override
    			public void onClick(View v)
    			{
    				// 初始化DownUtil对象(最后一个參数指定线程数)
    				downUtil = new DownUtil(url.getText().toString(),
    					target.getText().toString(), 6);
    				new Thread()
    				{
    					@Override
    					public void run()
    					{
    						try
    						{
    							// 開始下载
    							downUtil.download();
    						}
    						catch (Exception e)
    						{
    							e.printStackTrace();
    						}
    						// 定义每秒调度获取一次系统的完毕进度
    						final Timer timer = new Timer();
    						timer.schedule(new TimerTask()
    						{
    							@Override
    							public void run()
    							{
    								// 获取下载任务的完毕比率
    								double completeRate = downUtil.getCompleteRate();
    								mDownStatus = (int) (completeRate * 100);
    								// 发送消息通知界面更新进度条
    								handler.sendEmptyMessage(0x123);
    								// 下载全然后取消任务调度
    								if (mDownStatus >= 100)
    								{
    									showToastByRunnable(MultiThreadDown.this, "下载完毕", 2000);
    									timer.cancel();
    								}
    							}
    						}, 0, 100);
    					}
    				}.start();
    			}
    		});
    	}
    	/**
    	 * 在非UI线程中使用Toast
    	 * @param context 上下文
    	 * @param text 用以显示的消息内容
    	 * @param duration 消息显示的时间
    	 * */
    	private void showToastByRunnable(final Context context, final CharSequence text, final int duration) {
    	    Handler handler = new Handler(Looper.getMainLooper());
    	    handler.post(new Runnable() {
    	        @Override
    	        public void run() {
    	            Toast.makeText(context, text, duration).show();
    	        }
    	    });
    	}
    }

           上面的Activity不仅使用了 DownUtil来控制程序下载,并且程序还启动了一个定时器,该定时器控制每隔0.1秒査询一次下载进度,并通过程序中的进度条来显示任务的下载进度。

           该程序不仅须要訪问网络,还须要訪问系统SD卡,在SD卡中创建文件,因此必须授予该程序訪问网络、訪问SD卡文件的权限:

    <!-- 在SD卡中创建与删除文件权限 -->
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
    <!-- 向SD卡写入数据权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <!-- 授权訪问网络 -->
    <uses-permission android:name="android.permission.INTERNET"/>

    程序执行效果图:

     Android多线程下载

    下载到SD的资源

    提示:上面的程序已经实现了多线程下载的核心代码,假设要实现断点下载,则还须要额外添加一个配置文件(大家能够发现全部断点下载工具都会在下载開始生成两个文件:一个是与网络资源同样大小的空文件,一个是配置文件),该配置文件分别记录每一个线程已经下载到了哪个字节,当网络断开后再次開始下载时,每一个线程依据配置文件中记录的位置向后下载就可以。    

    2 使用ApacheHttpClient

            在普通情况下,假设仅仅是须要向Web网站的某个简单页面提交请求并获取server响应, 全然能够使用前面所介绍的HttpURLConnection来完毕。但在绝大部分情况下,Web网站的网页可能没这么简单,这些页面并非通过一个简单的URL就可訪问的,可能须要用户登录并且具有对应的权限才可訪问该页面。在这样的情况下,就须要涉及Session、Cookie的处理了,假设打算使用HttpURLConnection来处理这些细节,当然也是可能实现的,仅仅是处理起来难度就大了。

            为了更好地处理向Web网站请求,包含处理Session、Cookie等细节问题,Apache开源组织提供了一个HttpClient项目,看它的名称就知道,它是一个简单的HTTPclient(并非浏览器),能够用于发送HTTP请求,接收HTTP响应。但不会缓存server的响应,不能运行HTML页面中嵌入的JavaScript代码;也不会对页面内容进行不论什么解析、处理。

    提示:简单来说,HttpClient就是一个增强版的HttpURLConnection ,HttpURLConnection能够做的事情 HttpClient所有能够做;HttpURLConnection没有提供的有些功能,HttpClient也提供了,但它仅仅是关注于怎样发送请求、接收响应,以及管理HTTP连接。|

    Android集成了HttpClient,开发者能够直接在Android应用中使用HttpCHent来訪问提交请求、接收响应。

    2.1使用HttpClient发送清求、接收响应非常easy,仅仅要例如以下几步就可以:

    1) 创建HttpClient对象。

    2) 假设须要发送GET请求,创建HttpGet对象;假设须要发送POST 求,创建HttpPost对象。

    3) 假设须要发送请求參数,可调用HttpGet、HttpPost共同的setParams(HttpParams params)方法来加入请求參数;对于HttpPost对象而言,也可调用setEntity(HttpEntityentity)方法来设置请求參数。

    4) 调用HttpClient对象的execute(HttpUriRequestrequest)发送请求,运行该方法返回一 个 HttpResponse。

    5) 调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取server的响应头;调用HttpResponsegetEntity()方法可获取HttpEntity对象,该对象包装了server的响应内容。程序可通过该对象获取server的响应内容。

    2.2实例:訪问被保护资源

         以下的Android应用须要向指定页面发送请求,但该页面并非一个简单的页面,仅仅有 当用户已经登录,并且登录用户的username是jph时才可訪问该页面。假设使用HttpUrlConnection来訪问该页面,那么须要处理的细节就太复杂了。以下将会借助于 HttpClient来訪问被保护的页面。

          訪问Web应用中被保护的页面,假设使用浏览器则十分简单,用户通过系统提供的登录页面登录系统,浏览器会负责维护与server之间的Session,假设用户登录的username、password符合要求,就能够訪问被保护资源了。

          为了通过HttpClient来訪问被保护页面,程序相同须要使用HttpClient来登录系统,仅仅要应用程序使用同一个HttpClient发送请求,HttpClient会自己主动维护与server之间的Session状态,也就是说程序第一次使用HttpCHent登录系统后,接下来使用HttpCHent就可以訪问被保护页面了。

    提示:尽管此处给出的实例仅仅是訪问被保护的页面,但訪问其它被保护的资源也与此类似,程序仅仅要第一次通过HttpClient登录系统,接下来就可以通过该HttpClient訪问被保护资源了。

    2.3程序代码:

      

    package com.jph.net;
    
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.util.ArrayList;
    import java.util.List;
    import org.apache.http.HttpEntity;
    import org.apache.http.HttpResponse;
    import org.apache.http.NameValuePair;
    import org.apache.http.client.HttpClient;
    import org.apache.http.client.entity.UrlEncodedFormEntity;
    import org.apache.http.client.methods.HttpGet;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.message.BasicNameValuePair;
    import org.apache.http.protocol.HTTP;
    import org.apache.http.util.EntityUtils;
    import android.app.Activity;
    import android.app.AlertDialog;
    import android.content.DialogInterface;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Looper;
    import android.os.Message;
    import android.text.Html;
    import android.view.View;
    import android.widget.EditText;
    import android.widget.TextView;
    import android.widget.Toast;
    /**
     * Description:
     * 使用HttpClient訪问受保护的网络资源
     * @author  jph
     * Date:2014.08.28
     */
    public class HttpClientDemo extends Activity
    {
    	TextView response;
    	HttpClient httpClient;
    	Handler handler = new Handler()
    	{
    		public void handleMessage(Message msg)
    		{
    			if(msg.what == 0x123)
    			{
    				// 使用response文本框显示server响应
    				response.append(Html.fromHtml(msg.obj.toString()) + "
    ");
    			}
    		}
    	};
    	@Override
    	public void onCreate(Bundle savedInstanceState)
    	{
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.main);
    		// 创建DefaultHttpClient对象
    		httpClient = new DefaultHttpClient();
    		response = (TextView) findViewById(R.id.response);
    	}
    	/**
    	 * 此方法用于响应“訪问页面”button
    	 * */
    	public void accessSecret(View v)
    	{
    		response.setText("");
    		new Thread()
    		{
    			@Override
    			public void run()
    			{
    				// 创建一个HttpGet对象
    				HttpGet get = new HttpGet(
    					"http://10.201.1.32:8080/HttpClientTest_Web/secret.jsp");  //①
    				try
    				{
    					// 发送GET请求
    					HttpResponse httpResponse = httpClient.execute(get);//②
    					HttpEntity entity = httpResponse.getEntity();
    					if (entity != null)
    					{
    						// 读取server响应
    						BufferedReader br = new BufferedReader(
    							new InputStreamReader(entity.getContent()));
    						String line = null;
    						
    						while ((line = br.readLine()) != null)
    						{
    							Message msg = new Message();
    							msg.what = 0x123;
    							msg.obj = line;
    							handler.sendMessage(msg);
    						}
    					}
    				}
    				catch (Exception e)
    				{
    					e.printStackTrace();
    				}
    			}
    		}.start();
    	}
    	/**
    	 * 此方法用于响应“登陆系统”button
    	 * */
    	public void showLogin(View v)
    	{
    		// 载入登录界面
    		final View loginDialog = getLayoutInflater().inflate(
    			R.layout.login, null);
    		// 使用对话框供用户登录系统
    		new AlertDialog.Builder(HttpClientDemo.this)
    			.setTitle("登录系统")
    			.setView(loginDialog)
    			.setPositiveButton("登录",
    			new DialogInterface.OnClickListener()
    			{
    				@Override
    				public void onClick(DialogInterface dialog,
    					int which)
    				{
    					// 获取用户输入的username、password
    					final String name = ((EditText) loginDialog
    						.findViewById(R.id.name)).getText()
    						.toString();
    					final String pass = ((EditText) loginDialog
    						.findViewById(R.id.pass)).getText()
    						.toString();
    					new Thread()
    					{
    						@Override
    						public void run()
    						{
    							try
    							{
    								HttpPost post = new HttpPost("http://10.201.1.32:8080/" +
    										"HttpClientTest_Web/login.jsp");//③
    								// 假设传递參数个数比較多的话能够对传递的參数进行封装
    								List<NameValuePair> params = new
    									ArrayList<NameValuePair>();
    								params.add(new BasicNameValuePair
    									("name", name));
    	
    								
    								
    								params.add(new BasicNameValuePair
    									("pass", pass));								
    								// 设置请求參数
    								post.setEntity(new UrlEncodedFormEntity(
    									params, HTTP.UTF_8));
    								// 发送POST请求
    								HttpResponse response = httpClient
    									.execute(post);  //④
    								// 假设server成功地返回响应
    								if (response.getStatusLine()
    									.getStatusCode() == 200)
    								{
    									String msg = EntityUtils
    										.toString(response.getEntity());
    									Looper.prepare();
    									// 提示登录成功
    									Toast.makeText(HttpClientDemo.this,
    										msg, Toast.LENGTH_SHORT).show();
    									Looper.loop();
    								}
    							}
    							catch (Exception e)
    							{
    								e.printStackTrace();
    							}
    						}
    					}.start();
    				}
    			}).setNegativeButton("取消", null).show();
    	}
    }

             上面的程序中①、②号粗体字代码先创建了一个HttpGet对象,接下来程序调用HttpClient的execute()方法发送GET请求;程序中③、④号粗体字代码先创建了一个HttpPost对象,接下来程序调用了HttpClient的execute()方法发送POST请求。上面的GET请求用于获取server上的被保护页面,POST请求用于登录系统。

    执行该程序,单击“訪问页面”button将可看到例如以下图所看到的的页面。

    fail

            从上图能够看出,程序直接向指定Web应用的被保护页面secret.jsp发送请求,程序将无法訪问被保护页面,于是看到下图所看到的的页面。单击下图所看到的页面中的“登录”button,系统将会显演示样例如以下图所看到的的登录对话框。

           在上图所看到的对话框的两个输入框中分别输入“jph”、“123”,然后单击“登录”button,系统将会向Web网站的login.jsp页面发送POST请求,并将用户输入的username、password作为请求參数。假设username、password正确,就可以看到登录成功的提示。

    success

            登录成功后,HttpClient将会自己主动维护与server之间的连接,并维护与server之间的Session状态,再次单击程序中的“訪问页面”button,就可以看到例如以下图所看到的的输出。

    安全资源

            从上图能够看出,此时使用HttpClient发送GET请求就可以正常訪问被保护资源,这就是由于前面使用了HttpClient登录了系统,并且HttpClient能够维护与server之间的Session连接。

    从上面的编程过程不难看出,使用Apache的HttpClient更加简单,并且它比HttpURLConnection提供了很多其它的功能。

     

  • 相关阅读:
    pat甲级 1155 Heap Paths (30 分)
    pat甲级 1152 Google Recruitment (20 分)
    蓝桥杯 基础练习 特殊回文数
    蓝桥杯 基础练习 十进制转十六进制
    蓝桥杯 基础练习 十六进制转十进制
    蓝桥杯 基础练习 十六进制转八进制
    51nod 1347 旋转字符串
    蓝桥杯 入门训练 圆的面积
    蓝桥杯 入门训练 Fibonacci数列
    链表相关
  • 原文地址:https://www.cnblogs.com/gcczhongduan/p/4033225.html
Copyright © 2011-2022 走看看