这一张我们来学习http和后台任务
一.对于由于有网络的存在,所以这边我们需要先建立一个网络连接专用类,这边需要访问的类是Flickr网站,所以这边就创建一个FlickFetchr的java类
public class FlickrFetchr {
public byte[] getUrlBytes(String urlSpec) throws IOException {
URL url = new URL(urlSpec);
HttpURLConnection connection = (HttpURLConnection)url.openConnection();//创建一个指向访问URL的对象
try{
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream in = connection.getInputStream();//之前虽然有提供一个连接,但是直到运用这个方法时,它才会真正连接到指定的URL地址
if(connection.getResponseCode() !=HttpURLConnection.HTTP_OK){
throw new IOException (connection.getResponseMessage() + ":" + urlSpec);
}
int bytesRead = 0;
byte[] buffer = new byte[1024];
while(bytesRead = in.read(buffer)>0){//循环读取网络数据
out.write(buffer,0,byteRead);
}
out.close();
return out.toByteArray();//数据全部读完后就关闭网络连接,然后将他们写入ByteArrayOutputStream字节数组中
}finally{
connection.disconnect();
}
}
public String getUrlString(String urlSpec) throws IOException{
return new String(getUrlBytes(urlSpec));
}
}
二.获取网络使用权限
要使用网络的使用权限,那么我们就需要在配置文件中添加网络配置权限
在manifest中
<uses-permission android:name = "android.permission.INTERNET"/>
对于这个非危险的权限,我们直接minifest文件里面做个声明就可以了,而对于危险权限,那么我们就需要声明也需要运行时动态申请
三.使用AsyncTask在后台线程上来运行代码
(1)不要在fragment类中去直接运行代码,而是创建一个后台线程,然后在该线程中运行代码,创建后台线程最简单的方法就是使用AsyncTask工具类,AsnycTask创建后台线程后我们就在该线程上调用doInBackground()方法来运行代码
(2)在该fragment类中创建一个FetchItemsTask的内部类,覆盖AsyncTask.doInBackground()方法
private class FetchItemsTask extends AsyncTask<Void,Void,Void> {
protected Void doInBackground(Void...params) {
try {
String result = new FlickrFetchr().getUrlString("http://www.")
}catch(IOException ioe) {
Log.e()
}
return null;
}
}
}
(3)实现AsyncTask工具类的方法
在onCreate()方法里面
new FetchItemsTask().execute();
调用execute()方法会启动AsyncTask,进而触发后台线程并调用doInBackground()方法
三.线程和主线程
(1)Android禁止任何主线网络连接行为,主线程不是像线程那样预定执行序列,相反的它处于一个无限循环的运行状态,等着用户或系统触发,有触发,主线程立刻就执行代码做出响应,主线程它运行着所有的更新UI的代码,包括响应activity的启动,按钮的点击等不同的UI相关事件的代码。事件循环让UI代码总是按顺序执行
(耗时的任务会导致应用无法响应,也就是ANR现象)
四.从Flickr中获取JSON数据,JSON数据是近些年来流行起来的一种数据格式,尤其适合于Web服务,Android提供了一些包来创建和解析JSON数据,
要去获取网站提供给我们的数据,那么我们就需要去了解该网站提供的方法,然后我们向该网站服务发起一个请求时我们需要把某些因素写入申请中,如下面的代码
http://api.fiicker.com/services/rest/?method=flickr.photo.getRecent&api_key = xxx&format=json&nojsoncallback=1
需要获取有效的JSON数据,就需要同时指定format和nojsoncallback参数
nojsoncallback=1这个的意思是告诉返回的数据不应该包括封闭方法名和括号
下面在FlickrFetchr类中添加一些常量
private static final String API_KEY = "我们得到的apikey"
使用刚才的常量编写一个方法,构建请求URL获取内容,
public void fetchItem(){
try{
String url = Uri.parse("http://api.flickr.com/").buildUpon().appendQueryParameter("method","flickr.photos.getRecent")
.appendQueryParameter("api_key",API_KEY);
.appendQueryParameter("format","json");
.appendQueryParameter("nojsoncallback","1");
.appendQueryParameter("extras","url_s");//意思是如果有小尺寸的图片也把它返其URL
.build().toString();
String jsonString = getUrlString(url);
}catch(IOException ioe) {
}
}
五.然后就在AsyncTask.doInBackground()方法中去调用上面的方法
private class FetchItemsTask extends AsyncTask<Void,Void,Void> {
protected Void doInBackground(Void...params) {
new FlickrFetchr().fetchItem();
return null;
}
}
}
六.处理数据
在得到网站返回的数据后,我们就需要像处理其他数据一样,将其存入一个或多个模型对象中
(1)所以我们需要根据网站返回来的数据去建立一个模型对象类,其他代码让Androidstudio去自动生成
public class GalleryItem {
private String mCaption;
privae String mId;
private String mUrl;
public String toString() {
return mCaption;
}
}
(2)接下来就是解析JSON数据,把数据放进上面的模型类中
JSON对象是一系列包含在{}中的名值对,JSON数组是包含在【】中用逗号隔开来的JSON对象列表,对象彼此之间相互形成层级关系
json.orgAPI提供了对应JSON数据的Java对象,如JSONObject 和 JSONArray.使用JSONObject(String) 构造函数可以很方便地把JSON数据解析到相对应得Java对象,现在更新fetchItem()方法
public void fetchItem(){
try{
String url = Uri.parse("http://api.flickr.com/").buildUpon().appendQueryParameter("method","flickr.photos.getRecent")
.appendQueryParameter("api_key",API_KEY);
.appendQueryParameter("format","json");
.appendQueryParameter("nojsoncallback","1");
.appendQueryParameter("extras","url_s");//意思是如果有小尺寸的图片也把它返其URL
.build().toString();
String jsonString = getUrlString(url);
JSONObject jsonBody = new JSONObject(jsonString);//新加的
}catch(IOException ioe) {
}catch(JSONException e) {
}
}
(3)这边就可以来解析Flickr对象,把返回来的JSON里面的数据返回解析除一个一个对象,然后放入我们的模型类、
在FlickrFetchr类中新写一个paraseItems()方法
private void parseItems (List<GalleryItem> items,JSONObject jsonBody) throws IOException ,JSONException {
JSONObject photosJsonObject = jsonBody.getJSONObject("photos");
JSONArray photoJsonArray = photosJsonObject.getJSONArray("photo");
for(int i =0;i<photoJsonObjectArray.length();i++) {
JSONObject photoJsonObject = photoJsonArray.getJSONObject(i);
Gallery item = new GalleryItem();
item.setId(phototJson);
item.setCaption(photoJsonObject.getString("title"));
//不是每一张图片都有uri_l,所以就需要来检查,没有得话就退出
if(!photoJsonObject.has("url_s")){
continue;//
}
item.setUrl(photoJsonObject.getString("url_s"));
items.add(item);
}
}
我们通过上面getJSONObject和getJSONArray方法就可以找出每一张图片对应得每一个对象,我们然后就用循环去把每一个对象放入模型类中就可以了
(4)更新fetchItems()方法让它返回一个包含GalleryItem的List
public List<GalleryItem> fetchItem(){
List<GalleryItem> items = new ArrayList<>;
try{
String url = Uri.parse("http://api.flickr.com/").buildUpon().appendQueryParameter("method","flickr.photos.getRecent")
.appendQueryParameter("api_key",API_KEY);
.appendQueryParameter("format","json");
.appendQueryParameter("nojsoncallback","1");
.appendQueryParameter("extras","url_s");//意思是如果有小尺寸的图片也把它返其URL
.build().toString();
String jsonString = getUrlString(url);
JSONObject jsonBody = new JSONObject(jsonString);//上面新加的
parseItems(items,jsonBody);//这边加的
}catch(IOException ioe) {
}catch(JSONException e) {
}
return items;
}
经过上面的代码后,我们就已经是把JSON中的数据解析到我们的模型列表中去了
七.从AsyncTask回到主线程
接下来我们就回到视图层部分,实现在fragment类RecyclerView中显示图片的标题
(1)定义一个内部类ViewHolder
private class PhotoHolder extends RecyclerView.ViewHolder {
private TextView mTitleTextView;
public PhotoHolder(View itemView) {
super(itemView);
mTitleTextView = (TextView)itemView;
}
public void bindGalleryItem(GalleryItem item) {
mTitleTextView.setText(item.toString());
}
(2)实现Adapter的实现
private class PhotoAdapter extends RecyclerView.Adapter<PhotoHolder> {
private List<GalleryItem> mGalleryItem;
public PhotoAdapter(List<GalleryItem> galleryItem) {
mGalleryItem = galleryItem;
}
public PhotoHolder onCreateViewHolder(ViewGroup viewGroup,int viewType) {
TextView textView = new TextView(getActivity());
return new PhotoHolder(textView);
}
public void onBindViewHolder(PhotoHolder photoHolder,int position){
GalleryItem galleryItem = mGalleryItems.get(position);
photoHolder.bindGalleryItem(galleryItem);
}
public int getItemCount(){
return mGalleryItem.size();
}
}
}
八.完成adapter的配置和关联
在PhotoGalleryFragment这个类中,我们编写下面的代码
private List<GalleryItem> mItems = new ArrayList<>();
private void setupAdapter(){
if(isAdded()){
mPhotoRecyclerView.setAdapter(new PhotoAdapter(mItems));
}
}
根据当前模型的数据状态,上面的方法会自动配置RecyclerView的Adapter,应该在onCreateView方法中去使用它,这样每次当设备旋转而重新生成RecyclerView时,可以为他们配置对应的adapter,另外,在模型层发生变化的时候也应该去及时调用该方法
配置adapter前应该检查isAdded()的返回值是不是为true,该检查确认fragment已经与目标activity相关联,从而getActivity()的返回值非空
在这边所有的方法调用都是由系统框架的回调方法驱动的,现在如果fragment接受到回调指令,那么它必然关联着一个activity
从Flickr成功获取到数据后,就需要调用setUpAdapter()方法,而在计算机里面,内存的对象互踩会让应用崩溃,所以安全起见,不能在后台线程更新UI
在AsyncTask还有另一个可以覆盖的onPostExecute()方法,onPostExecute()方法在doInBackground方法执行完毕后才会进行,它是在主线程上的,而不是在后台线程的
九.修改FetchItemsTask类以新的方式来更新mItem,并在成功获取图片后用setupAdapter的方法来更新RecyclerView的数据源
private class FetchItemsTask extends AsyncTask<Void,Void,List<GalleryItem>> {
protected List<GalleryItem> doInBackground(Void...params) {
return new FlickrFetchr().fetchItems();
}
//该方法接受doInBackground方法的返回数据,并放入mItem中
protected void onPostExecute(List<GalleryItem> items) {
mItem = items;
setupAdapter();
}
}
}
在上面的代码中,我们改变了FetchItemsTask类型的第三个泛型参数的类型,该参数是AsyncTask返回结果的数据类型,也就是doInBackground()方法返回结果的数据类型,以及onPostExecute()方法输入参数的数据类型
十.清理AsyncTask
在上面的代码中我们使用 setRetainInstance(true)这个方法,这样我们就保留了fragment,这样即使设备旋转,那么也不会重复创建新的AsynTask去获取JSON数据,然而有些情况下,我们必须好好使用它,有时需要去撤销或重新运行AsnycTask
针对复杂的应用场景,我们需要将AsyncTask赋值给实例变量,这样我们就可以使用AsyncTask.camcel(boolean)的方法
(1)如果设置为cancel(false),那么它只是简单地设置isCancelled()的状态为true,然后AsyncTask会检查isCancelled()状态,然后选择提前结束运行
(2)如果设置为cancel(true),那么它会立刻停止doInBackground()方法所在的线程,简单粗暴,如果可能尽量避免这个方法
(3)然而什么时候需要撤销AsyncTask就需要我们根据需求来决定了
十一.深入了解AsyncTask
对于这个类来说,它的第一个参数是可指定将要转给execute()方法的输入参数的类型,进而确定doInBackground()方法的输入参数的类型,如下面的代码
AsyncTask<String,Void,Void> task = new AsyncTask<String,,Void,Void>(){
public Void doInBackground(String...params){
for(String parameter:params) {
Log.i(TAG," DD" + parameter);
}
return null;
}
}
输入参数到execute方法中,这些变量会传给doInBackground
task.execute("1","2","3");
第二个参数是可指定发送进度给doInBackground()方法,进度更新发生在后台进程,但是后台进程无法完成必要的UI更新,因此AsyncTask提供了publicProgrogress()和onProgressUpdate()方法
其工作方式是:在后台线程中,doInBackground()方法里面调用publicProgress()方法,这样onProgressUpdate()方法就可以在UI线程上调用,因此在onProgressUpdate()方法中执行UI更新就可以,但必须在doInBackground()方法中使用publicProgress进行监控
final ProgressBar gesttationProgressBar;
gesttationProgressBar.setMax(42);
AsyncTask<Void,Integer,Void> haveBoy = new AsyncTask<Void,Integer,Void>(){
public Void doInBackground(Void...params) {
while(!babyIsBorn()){
Integer weeksPassed = getNumberOfWeeksPassed();
publishProgress(weeksPassed);
patientlyWaitForBaby();
}
}
public void onProgressUpdate(Integer... params) {
int progress = params[0];
gesttationProgrssBar.setProgress(progress);
}
}
十二.使用Gson,对于简化JSON数据到Java对象的互转,那么就可以使用Gson这个工具库,不用写任何解析代码,Gson就能自动把Json数据映射为Java对象