因为移动端软件开发思维模式或者说是开发的架构其实是不分平台和编程语言的,就拿安卓和IOS来说,他们都是移动前端app开发展示数据和用户交互数据的数据终端,移动架构的几个大模块:UI界面展示、本地数据可持续化存储、网络数据请求、性能优化等等,安卓和IOS开发都要考虑这些架构的模块。所以,熟悉IOS的开发的人,再去学习一下安卓的开发以及安卓的开发模式,你会发现很多技术和思想安卓和IOS是一样的,只是可能说法不一样,由于编程语言比如OC和Java略微的差异性,编码习惯和细节不一样之外,其他都是一样的。

本人开始对安卓略有兴趣,开始对安卓粗浅的学习,一方面也会拿IOS和安卓进行对比阐述,如果你会IOS,再学习安卓的,阅读本人的博客也许会对你有很大的帮助。

(但是对于安卓开发的语言基础Java、以及android studio的使用,本人不会详细阐述,作为有情怀有独立能力的程序员,这些基础应该不会难道你们的吧,更何况本人对android studio的使用一直通过google和百度来学习相关的setting和快捷键)

在Android中,异步加载最常用的两种方式:

  1、多线程\线程池

  2、AsyncTask

当然,AsyncTask底层是基于线程池实现的。所以以上两种方法是异曲同工。

一、首先,按照在IOS中用UITableView加载数据的思路一样,我们先要创建出自定义的Cell以及Cell的布局:

新建item_layout.xml文件:

源码:

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="4dp"
android:orientation="horizontal"
>
<ImageView
android:id="@+id/iv_icon"
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@mipmap/ic_launcher"/> <LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="4dp"
android:gravity="center"
android:orientation="vertical"> <TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="15sp"
android:maxLines="1"
android:text="标题标题标题"/> <TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="10sp"
android:maxLines="3"
android:text="内容内容内容"/> </LinearLayout> </LinearLayout>

就这样,一个自定义的item就创建好了,就好比我们IOS中xib或者storyboard上的UITableViewCell创建好了。

然后接着在activity_main.xml中创建一个ListView,这个ListView就好比我们IOS的UITableView。

源码:

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
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.heyang.myapplication.MainActivity"> <ListView
android:id="@+id/lv_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
/> </RelativeLayout>

二、因为每一个item或者类比IOS的每一个Cell都需要一个图片地址、标题Title、内容Content三个数据,所以我们就需要一个模型对象来一一映射对应到item,在安卓或者Java中的说法叫创建一个Bean对象,其实可以类比理解为IOS的model模型对象。

源码:

 package com.example.heyang.myapplication;

 /**
* Created by HeYang on 16/10/5.
*/ public class NewsBean {
// 包含三个属性:1、标题2、内容3、图片的网址
public String newsIconURL;
public String newsTitle;
public String newsContent;
}

三、接着就是要数据源了,http://www.imooc.com/api/teacher?type=4&num=30,点击这个URL打开网页会看到一大堆数据,然后你可以通过json格式转换工具就可以看到json的数据格式。

那么接下来就很容易明白了,模型Bean对象中的三个属性就可以来自这个URL下的json数据中的name、picSmall、discription。

那么接下来就是IOS中所谓的字典转模型的步骤。只不过在这之前,先要通过网络请求获取到这些数据才行,这里网络请求获取json数据的做法有点和IOS的不同了。

感觉IOS的网络请求,不管是苹果的NSURLSession还是第三方的AFN框架都已经是封装好的网络请求框架。而安卓的获取json数据的做法好像更接近底层,采用了输入输出流来获取URL的网页数据:

 package com.example.heyang.myapplication;

 import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.ListView; import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List; public class MainActivity extends AppCompatActivity { private ListView mListView; private static String URL = "http://www.imooc.com/api/teacher?type=4&num=30"; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // 获取xml上的ListView对象
mListView = (ListView) findViewById(R.id.lv_main); new NewsAsyncTask().execute(URL);
} // 通过输入输出流获取整个网页格式的字符串数据
private String readStream(InputStream is){
InputStreamReader isr;
String result = ""; try {
String line = "";
// 1、输入流对象 2、输入流读取对象 3、字节读取对象
isr = new InputStreamReader(is,"utf-8");
BufferedReader br = new BufferedReader(isr);
while ((line = br.readLine()) != null){
result += line;
}
} catch(UnsupportedEncodingException e){
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return result;
} private List<NewsBean> getJsonData(String url){
// 创建存储NewsBean的集合对象
List<NewsBean> newsBeanList = new ArrayList<>();
try {
// 取出网络的json字符串的格式之后
String jsonStr = readStream(new URL(url).openStream());
// 就要用JSONObject对象进行解析
JSONObject jsonObject;
// 然后需要NewsBean,其实相当于IOS的模型对象
NewsBean newsBean;
try {
// jsonObject的对象,创建该对象的同时传入json字符串格式的对象
jsonObject = new JSONObject(jsonStr);
// 拿到jsonObject对象之后,就需要通过key值来拿到数组
JSONArray jsonArray = jsonObject.getJSONArray("data");
// 然后开始遍历数组,获取模型数组
for (int i = 0;i<jsonArray.length();i++){
// 数组里每一个元素又是jsonObject
jsonObject = jsonArray.getJSONObject(i); // 开始创建模型对象
newsBean = new NewsBean();
newsBean.newsIconURL = jsonObject.getString("picSmall");
newsBean.newsTitle = jsonObject.getString("name");
newsBean.newsContent = jsonObject.getString("description"); // 创建的一个模型对象,就要添加到集合当中去
newsBeanList.add(newsBean);
} } catch (JSONException e) {
e.printStackTrace();
} Log.d("heyang",jsonStr);
} catch (IOException e) {
e.printStackTrace();
} return newsBeanList;
} // 创建一个内部类来实现 ,在实现下面内部类之前,需要自定义的Bean对象来封装处理Josn格式的数据
class NewsAsyncTask extends AsyncTask<String,Void,List<NewsBean>>{
@Override
protected List<NewsBean> doInBackground(String... strings) {
return getJsonData(strings[0]);
}
}
}

按照以上的源码,就能顺利的获取到了ListView的数据源数组。

但是在网络请求方面,别忘了要给项目增加Internet访问权限:

<!--增加 网络访问权限 -->
<uses-permission android:name="android.permission.INTERNET"/>

四、创建继承BaseAdapter自定义的Adapter,注意其中利用了匿名内部类来优化处理ListView的每一个view的循环利用:

这里要补充一下,安卓加载ListView用到了适配器模式,所以需要下面自定义的Adapter对象,这个和我们IOS开发的加载UITableView用到的一些列代理方法的代理模式还是有区别的。不过,如果读者学习了适配器模式,将会对安卓底层用到的适配器模式就会有所理解了。

 package com.example.heyang.myapplication;

 import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView; import java.util.List; /**
* Created by HeYang on 16/10/6.
*/ public class NewsAdapter extends BaseAdapter { // 适配器对象需要传入Bean数据集合对象,类似IOS的模型数组集合
private List<NewsBean> beanList;
// 然后要传入LayoutInflater对象,用来获取xml文件的视图控件
private LayoutInflater layoutInflater; // 创建构造方法
public NewsAdapter(MainActivity context, List<NewsBean> data){
beanList = data;
layoutInflater = LayoutInflater.from(context);// 这个context对象就是Activity对象
} @Override
public int getCount() {
return beanList.size();
} @Override
public Object getItem(int i) {
// 因为beanList是数组,通过get访问对应index的元素
return beanList.get(i);
} @Override
public long getItemId(int i) {
return i;
} @Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder = null;
if (view == null){
viewHolder = new ViewHolder();
// 每一个View都要和layout关联
view = layoutInflater.inflate(R.layout.item_layout,null);
// 在R.layout.item_layout中有三个控件对象
// 现在全部传递给view对象了
viewHolder.tvTitle = (TextView) view.findViewById(R.id.tv_title);
viewHolder.tvContent = (TextView) view.findViewById(R.id.tv_content);
viewHolder.ivIcon = (ImageView) view.findViewById(R.id.iv_icon);
view.setTag(viewHolder); }else { // 重复利用,但是由于里面的View展示的数据显然需要重新赋值
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.tvTitle.setText(beanList.get(i).newsTitle);
viewHolder.tvContent.setText(beanList.get(i).newsContent);
// 先默认加载系统图片
viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher); return view;
} // 最后需要一个匿名内部类来创建一个临时缓存View的对象
class ViewHolder{
public TextView tvContent,tvTitle;
public ImageView ivIcon;
}
}

五、最后优化和完善MainActivity对象

 package com.example.heyang.myapplication;

 import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.ListView; import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List; public class MainActivity extends AppCompatActivity { private ListView mListView; private static String URL = "http://www.imooc.com/api/teacher?type=4&num=30"; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // 获取xml上的ListView对象
mListView = (ListView) findViewById(R.id.lv_main); new NewsAsyncTask().execute(URL);
} // 通过输入输出流获取整个网页格式的字符串数据
private String readStream(InputStream is){
InputStreamReader isr;
String result = ""; try {
String line = "";
// 1、输入流对象 2、输入流读取对象 3、字节读取对象
isr = new InputStreamReader(is,"utf-8");
BufferedReader br = new BufferedReader(isr);
while ((line = br.readLine()) != null){
result += line;
}
} catch(UnsupportedEncodingException e){
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return result;
} private List<NewsBean> getJsonData(String url){
// 创建存储NewsBean的集合对象
List<NewsBean> newsBeanList = new ArrayList<>();
try {
// 取出网络的json字符串的格式之后
String jsonStr = readStream(new URL(url).openStream());
// 就要用JSONObject对象进行解析
JSONObject jsonObject;
// 然后需要NewsBean,其实相当于IOS的模型对象
NewsBean newsBean;
try {
// jsonObject的对象,创建该对象的同时传入json字符串格式的对象
jsonObject = new JSONObject(jsonStr);
// 拿到jsonObject对象之后,就需要通过key值来拿到数组
JSONArray jsonArray = jsonObject.getJSONArray("data");
// 然后开始遍历数组,获取模型数组
for (int i = 0;i<jsonArray.length();i++){
// 数组里每一个元素又是jsonObject
jsonObject = jsonArray.getJSONObject(i); // 开始创建模型对象
newsBean = new NewsBean();
newsBean.newsIconURL = jsonObject.getString("picSmall");
newsBean.newsTitle = jsonObject.getString("name");
newsBean.newsContent = jsonObject.getString("description"); // 创建的一个模型对象,就要添加到集合当中去
newsBeanList.add(newsBean);
} } catch (JSONException e) {
e.printStackTrace();
} Log.d("heyang",jsonStr);
} catch (IOException e) {
e.printStackTrace();
} return newsBeanList;
} // 创建一个内部类来实现 ,在实现下面内部类之前,需要自定义的Bean对象来封装处理Josn格式的数据
class NewsAsyncTask extends AsyncTask<String,Void,List<NewsBean>>{
@Override
protected List<NewsBean> doInBackground(String... strings) {
return getJsonData(strings[0]);
} @Override
protected void onPostExecute(List<NewsBean> newsBeen) {
super.onPostExecute(newsBeen);
NewsAdapter newsAdapter = new NewsAdapter(MainActivity.this,newsBeen);
mListView.setAdapter(newsAdapter); }
}
}

好,运行一下模拟器看看结果:

六、下面采用两种方法进行加载图片:①多线程加载图片 ②AsyncTask

①多线程加载图片:

创建一个普通的Class类:ImageLoader,在内部使用线程run运行执行ImageView加载网络图片的逻辑

 package com.example.heyang.myapplication;

 import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Message;
import android.widget.ImageView; import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL; /**
* Created by HeYang on 16/10/6.
*/ public class ImageLoader { private ImageView mImageView;
private String mURLStr; private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg); if (mImageView.getTag().equals(mURLStr)){
mImageView.setImageBitmap((Bitmap) msg.obj);
} }
}; public void showImageByThread(ImageView imageView, final String urlString){ mImageView = imageView;
mURLStr = urlString; new Thread(){
@Override
public void run() {
super.run();
Bitmap bitmap = getBitmapFromURL(urlString);
// 当前线程是子线程,并不是UI主线程
// 不是Message message = new Message();
Message message = Message.obtain();
message.obj = bitmap;
handler.sendMessage(message); }
}.start();
} public Bitmap getBitmapFromURL(String urlString){
Bitmap bitmap = null;
InputStream is = null; try {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
is = new BufferedInputStream(connection.getInputStream());
bitmap = BitmapFactory.decodeStream(is);
// 最后要关闭http连接
connection.disconnect();
Thread.sleep(1000);// 睡眠1秒
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally { try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
} return bitmap;
}
}

然后修改一下之前的NewsAdapter的代码:

 package com.example.heyang.myapplication;

 import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView; import java.util.List; /**
* Created by HeYang on 16/10/6.
*/ public class NewsAdapter extends BaseAdapter { // 适配器对象需要传入Bean数据集合对象,类似IOS的模型数组集合
private List<NewsBean> beanList;
// 然后要传入LayoutInflater对象,用来获取xml文件的视图控件
private LayoutInflater layoutInflater; // 创建构造方法
public NewsAdapter(MainActivity context, List<NewsBean> data){
beanList = data;
layoutInflater = LayoutInflater.from(context);// 这个context对象就是Activity对象
} @Override
public int getCount() {
return beanList.size();
} @Override
public Object getItem(int i) {
// 因为beanList是数组,通过get访问对应index的元素
return beanList.get(i);
} @Override
public long getItemId(int i) {
return i;
} @Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder = null;
if (view == null){
viewHolder = new ViewHolder();
// 每一个View都要和layout关联
view = layoutInflater.inflate(R.layout.item_layout,null);
// 在R.layout.item_layout中有三个控件对象
// 现在全部传递给view对象了
viewHolder.tvTitle = (TextView) view.findViewById(R.id.tv_title);
viewHolder.tvContent = (TextView) view.findViewById(R.id.tv_content);
viewHolder.ivIcon = (ImageView) view.findViewById(R.id.iv_icon); view.setTag(viewHolder); }else { // 重复利用,但是由于里面的View展示的数据显然需要重新赋值
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.tvTitle.setText(beanList.get(i).newsTitle);
viewHolder.tvContent.setText(beanList.get(i).newsContent);
// 先默认加载系统图片
viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher); // 类似加载占位图片
viewHolder.ivIcon.setTag(beanList.get(i).newsIconURL);
// 将ImageView对象和URLSting对象传入进去
new ImageLoader().showImageByThread(viewHolder.ivIcon,beanList.get(i).newsIconURL); return view;
} // 最后需要一个匿名内部类来创建一个临时缓存View的对象
class ViewHolder{
public TextView tvContent,tvTitle;
public ImageView ivIcon;
}
}

注意,其中使用了viewHolder.ivIcon.setTag(beanList.get(i).newsIconURL);作为唯一标示传递给ImageLoader内部做判断,这样加载ListView不会出现图片加载和缓存冲突了现象。(源码中保留了Thread.sleep(1000)可以查看到效果)。

②AsyncTask

接着,使用AsyncTask异步加载,在imageLoader对象的基础上继续:

完整源码:

 package com.example.heyang.myapplication;

 import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.widget.ImageView; import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL; /**
* Created by HeYang on 16/10/6.
*/ public class ImageLoader { private ImageView mImageView;
private String mURLStr; private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg); if (mImageView.getTag().equals(mURLStr)){
mImageView.setImageBitmap((Bitmap) msg.obj);
} }
}; public void showImageByThread(ImageView imageView, final String urlString){ mImageView = imageView;
mURLStr = urlString; new Thread(){
@Override
public void run() {
super.run();
Bitmap bitmap = getBitmapFromURL(urlString);
// 当前线程是子线程,并不是UI主线程
// 不是Message message = new Message();
Message message = Message.obtain();
message.obj = bitmap;
handler.sendMessage(message); }
}.start();
} public Bitmap getBitmapFromURL(String urlString){
Bitmap bitmap = null;
InputStream is = null; try {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
is = new BufferedInputStream(connection.getInputStream());
bitmap = BitmapFactory.decodeStream(is);
// 最后要关闭http连接
connection.disconnect();
Thread.sleep(1000);// 睡眠1秒
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally { try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
} return bitmap;
} // ==================使用AsyncTask====================
public void showImageByAsyncTask(ImageView imageView, final String urlString){
new NewsAsyncTask(imageView, (String) imageView.getTag()).execute(urlString);// 这两个参数分别传递的目的地可以理解一下
} private class NewsAsyncTask extends AsyncTask<String,Void,Bitmap>{ // 需要私有的ImageView对象和构造方法来传递ImageView对象
private ImageView mImageView;
private String urlString; public NewsAsyncTask(ImageView imageView,String urlString){
mImageView = imageView;
urlString = urlString;
} @Override
protected Bitmap doInBackground(String... strings) {
// 在这个方法中,完成异步下载的任务
getBitmapFromURL(strings[0]);
return null;
} @Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
// 然后在这个方法中设置imageViewd
if (mImageView.getTag().equals(urlString)){
mImageView.setImageBitmap(bitmap);
} }
} }

然后在NewsAdapter里修改一下:

然后运行效果和上面多线程的效果一样,这里就不再重复展示了。

七、LruCache缓存处理

为了提高用户体验,所以需要使用缓存机制。这里安卓提供了Lru算法处理缓存。

Lru:Least Recently Used 近期最少使用算法。

所以,我们需要在ImageLoader中创建LruCache对象:

完整源码:

 package com.example.heyang.myapplication;

 import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.RequiresApi;
import android.util.Log;
import android.util.LruCache;
import android.widget.ImageView; import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL; /**
* Created by HeYang on 16/10/6.
*/ public class ImageLoader { private ImageView mImageView;
private String mURLStr;
// 创建缓存对象
// 第一个参数是需要缓存对象的名字或者ID,这里我们传输url作为唯一名字即可
// 第二个参数是Bitmap对象
private LruCache<String,Bitmap> lruCache; // 然后我们需要在构造方法中初始化这个缓存对象
// 另外,我们不可能把所有的缓存空间拿来用
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB_MR1)
public ImageLoader(){ // 获取最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cachaSize = maxMemory / 4;
// 创建LruCache对象,同时用匿名内部类的方式重写方法
lruCache = new LruCache<String,Bitmap>(cachaSize){
@Override
protected int sizeOf(String key, Bitmap value) {
// 我们需要直接返回Bitmap value的实际大小
//return super.sizeOf(key, value);
// 在每次存入缓存的时候调用
return value.getByteCount();
}
};
} // 然后我们要写俩个方法:1、将bitmap存入缓存中 2、从缓存中取出bitmap @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public void addBitmapToCache(String url, Bitmap bitmap){
if (getBitMapFromCache(url) == null){
lruCache.put(url,bitmap);
}
} @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public Bitmap getBitMapFromCache(String url){
return lruCache.get(url);
} private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg); if (mImageView.getTag().equals(mURLStr)){
mImageView.setImageBitmap((Bitmap) msg.obj);
} }
}; public void showImageByThread(ImageView imageView, final String urlString){ mImageView = imageView;
mURLStr = urlString; new Thread(){
@Override
public void run() {
super.run();
Bitmap bitmap = getBitmapFromURL(urlString);
// 当前线程是子线程,并不是UI主线程
// 不是Message message = new Message();
Message message = Message.obtain();
message.obj = bitmap;
handler.sendMessage(message); }
}.start();
} public Bitmap getBitmapFromURL(String urlString){
Bitmap bitmap = null;
InputStream is = null; try {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
is = new BufferedInputStream(connection.getInputStream());
bitmap = BitmapFactory.decodeStream(is);
// 最后要关闭http连接
connection.disconnect();
// Thread.sleep(1000);// 睡眠1秒
} catch (IOException e) {
e.printStackTrace();
}
// catch (InterruptedException e) {
// e.printStackTrace();
// }
finally { try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
} return bitmap;
} // ==================使用AsyncTask====================
public void showImageByAsyncTask(ImageView imageView, final String urlString){
// 在异步请求之前,先判断缓存中是否有,有的话就取出直接加载
Bitmap bitmap = getBitMapFromCache(urlString);
if (bitmap == null){
// 如果没有,就异步任务加重
new NewsAsyncTask(imageView, (String) imageView.getTag()).execute(urlString);// 这两个参数分别传递的目的地可以理解一下
}else{
imageView.setImageBitmap(bitmap);
} } private class NewsAsyncTask extends AsyncTask<String,Void,Bitmap>{ // 需要私有的ImageView对象和构造方法来传递ImageView对象
private ImageView mImageView;
private String mURlString; public NewsAsyncTask(ImageView imageView,String urlString){
mImageView = imageView;
mURlString = urlString;
} @Override
protected Bitmap doInBackground(String... strings) {
// 在这个方法中,完成异步下载的任务
String url = strings[0];
// 从网络中获取图片
Bitmap bitmap = getBitmapFromURL(strings[0]);
if (bitmap != null){
// 将网络加载出来的图片存储缓存
addBitmapToCache(url,bitmap);
}
return bitmap;
} @Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
// 然后在这个方法中设置imageViewd if (mImageView.getTag().equals(mURlString)){
mImageView.setImageBitmap(bitmap);
} }
}
}

还需要在NewsAdapter类里做一个小小的优化,因为适配器的getView是个不断被调用的方法,就好比UITableView创建Cell的代理方法,会被不断的被调用。而在getView方法中,ImageLoader的构造方法会被不断的被调用,这样的话,会造成ImageLoader构造方法中的LruCache对象不断的被创建,这样显然是不好的。所以我们需要用一个引用,指向一开始就创建好的ImageLoader对象,然后多次使用。

然后

完整源码:

 package com.example.heyang.myapplication;

 import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView; import java.util.List; /**
* Created by HeYang on 16/10/6.
*/ public class NewsAdapter extends BaseAdapter { // 适配器对象需要传入Bean数据集合对象,类似IOS的模型数组集合
private List<NewsBean> beanList;
// 然后要传入LayoutInflater对象,用来获取xml文件的视图控件
private LayoutInflater layoutInflater; private ImageLoader imageLoader; // 创建构造方法 @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public NewsAdapter(MainActivity context, List<NewsBean> data){
beanList = data;
layoutInflater = LayoutInflater.from(context);// 这个context对象就是Activity对象
imageLoader = new ImageLoader();
} @Override
public int getCount() {
return beanList.size();
} @Override
public Object getItem(int i) {
// 因为beanList是数组,通过get访问对应index的元素
return beanList.get(i);
} @Override
public long getItemId(int i) {
return i;
} @Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder = null;
if (view == null){
viewHolder = new ViewHolder();
// 每一个View都要和layout关联
view = layoutInflater.inflate(R.layout.item_layout,null);
// 在R.layout.item_layout中有三个控件对象
// 现在全部传递给view对象了
viewHolder.tvTitle = (TextView) view.findViewById(R.id.tv_title);
viewHolder.tvContent = (TextView) view.findViewById(R.id.tv_content);
viewHolder.ivIcon = (ImageView) view.findViewById(R.id.iv_icon); view.setTag(viewHolder); }else { // 重复利用,但是由于里面的View展示的数据显然需要重新赋值
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.tvTitle.setText(beanList.get(i).newsTitle);
viewHolder.tvContent.setText(beanList.get(i).newsContent);
// 先默认加载系统图片
viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher); // 类似加载占位图片
viewHolder.ivIcon.setTag(beanList.get(i).newsIconURL);
// 将ImageView对象和URLSting对象传入进去
// new ImageLoader().showImageByThread(viewHolder.ivIcon,beanList.get(i).newsIconURL);
imageLoader.showImageByAsyncTask(viewHolder.ivIcon,beanList.get(i).newsIconURL);
return view;
} // 最后需要一个匿名内部类来创建一个临时缓存View的对象
class ViewHolder{
public TextView tvContent,tvTitle;
public ImageView ivIcon;
}
}

八、滚动时的高效优化

前面的代码,虽然顺利的实现了异步加载的过程,但是在实际项目开发中,ListView的每一个item项可能是很复杂的,如果按照前面的代码实现,用户在滚动的过程就可能会出现卡顿的现象。

虽然从网络加载的数据是在子线程中完成,但是更新UI的过程只能在主线程中更新,如果item项很复杂的话,滚动ListView出现卡顿现象也是可以理解的。

但是我们可以优化这个卡顿的问题:

  ① ListView滑动停止后才加载可见项

  ② ListView滑动时,取消所有加载项

优化的原因是:用户在滑动的时候,因为ListView在滑动,所以没必要做更新主线程UI的操作,而更新主线程UI的操作可以放在滑动结束的时候执行,这样也是符合用户交互习惯的,同时也优化了卡顿的问题。

既然我们需要监听滚动的事件,可以直接使用监听滚动的接口:

并实现接口的方法:

因为我们要实现在结束滚动的时候加载可见项,说的再明白点就是比如有100项需要加载,手机屏幕最多显示8项,用户滚动到第20项范围内结束,这时候就要加载这地20项附近的8项即可。

所以要实现这样的逻辑,就需要滚动结束之前,就要获取可见项的起止位置,还要获取这起止位置之间对应的所有数据。

接下来在前面代码进行修改的细节就比较多了,这里就直接上修改的源码,总体思路就是之前的一边滚动一边执行适配器对象的getView对象去加载每一个item,而接下来就是改成

在滚动结束的时候,才加载可见项的图片,当然之前的getView方法里加载String字符串的逻辑仍旧可以保留,因为这个和通过网络加载图片的业务逻辑本质是不同的,读者自己领会,不难。

在NewsAdapter类中:

 package com.example.heyang.myapplication;

 import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView; import java.util.List; /**
* Created by HeYang on 16/10/6.
*/ public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener{ // 适配器对象需要传入Bean数据集合对象,类似IOS的模型数组集合
private List<NewsBean> beanList;
// 然后要传入LayoutInflater对象,用来获取xml文件的视图控件
private LayoutInflater layoutInflater; private ImageLoader imageLoader; // 可见项的起止index
private int mStart,mEnd;
// 因为我们需要存储起止项所有的url地址
public static String[] URLS; // 创建构造方法
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public NewsAdapter(MainActivity context, List<NewsBean> data, ListView listView){
beanList = data;
layoutInflater = LayoutInflater.from(context);// 这个context对象就是Activity对象
imageLoader = new ImageLoader(listView); // 将模型数组中的url字符串单独存储在静态数组中
int dataSize = data.size();
URLS = new String[dataSize];
for (int i = 0; i < dataSize; i++) {
URLS[i] = data.get(i).newsIconURL;
} // 已经要记得注册
listView.setOnScrollListener(this);
} @Override
public int getCount() {
return beanList.size();
} @Override
public Object getItem(int i) {
// 因为beanList是数组,通过get访问对应index的元素
return beanList.get(i);
} @Override
public long getItemId(int i) {
return i;
} @Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder = null;
if (view == null){
viewHolder = new ViewHolder();
// 每一个View都要和layout关联
view = layoutInflater.inflate(R.layout.item_layout,null);
// 在R.layout.item_layout中有三个控件对象
// 现在全部传递给view对象了
viewHolder.tvTitle = (TextView) view.findViewById(R.id.tv_title);
viewHolder.tvContent = (TextView) view.findViewById(R.id.tv_content);
viewHolder.ivIcon = (ImageView) view.findViewById(R.id.iv_icon); view.setTag(viewHolder); }else { // 重复利用,但是由于里面的View展示的数据显然需要重新赋值
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.tvTitle.setText(beanList.get(i).newsTitle);
viewHolder.tvContent.setText(beanList.get(i).newsContent);
// 先默认加载系统图片
viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher); // 类似加载占位图片
viewHolder.ivIcon.setTag(beanList.get(i).newsIconURL);
// 将ImageView对象和URLSting对象传入进去
// new ImageLoader().showImageByThread(viewHolder.ivIcon,beanList.get(i).newsIconURL);
imageLoader.showImageByAsyncTask(viewHolder.ivIcon,beanList.get(i).newsIconURL);
return view;
} @Override
public void onScrollStateChanged(AbsListView absListView, int i) {
// SCROLL_STATE_IDLE : 滚动结束
if (i == SCROLL_STATE_IDLE){
// 加载可见项
imageLoader.loadImages(mStart,mEnd);
}else{
// 停止所有任务
imageLoader.cancelAllTask();
}
} @Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
// i是第一个可见元素 i1是当前可见元素的长度
mStart = i;
mEnd = i + i1;
} // 最后需要一个匿名内部类来创建一个临时缓存View的对象
class ViewHolder{
public TextView tvContent,tvTitle;
public ImageView ivIcon;
}
}

在ImageLoader类中:

 package com.example.heyang.myapplication;

 import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.RequiresApi;
import android.util.Log;
import android.util.LruCache;
import android.widget.ImageView;
import android.widget.ListView; import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashSet;
import java.util.Set; /**
* Created by HeYang on 16/10/6.
*/ public class ImageLoader { private ImageView mImageView;
private String mURLStr;
// 创建缓存对象
// 第一个参数是需要缓存对象的名字或者ID,这里我们传输url作为唯一名字即可
// 第二个参数是Bitmap对象
private LruCache<String,Bitmap> lruCache; private ListView mListView;
private Set<NewsAsyncTask> mTasks; // 然后我们需要在构造方法中初始化这个缓存对象
// 另外,我们不可能把所有的缓存空间拿来用
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB_MR1)
public ImageLoader(ListView listView){ mListView = listView;
mTasks = new HashSet<>(); // 获取最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cachaSize = maxMemory / 4;
// 创建LruCache对象,同时用匿名内部类的方式重写方法
lruCache = new LruCache<String,Bitmap>(cachaSize){
@Override
protected int sizeOf(String key, Bitmap value) {
// 我们需要直接返回Bitmap value的实际大小
//return super.sizeOf(key, value);
// 在每次存入缓存的时候调用
return value.getByteCount();
}
};
} // 然后我们要写俩个方法:1、将bitmap存入缓存中 2、从缓存中取出bitmap @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public void addBitmapToCache(String url, Bitmap bitmap){
if (getBitMapFromCache(url) == null){
lruCache.put(url,bitmap);
}
} @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public Bitmap getBitMapFromCache(String url){
return lruCache.get(url);
} private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg); if (mImageView.getTag().equals(mURLStr)){
mImageView.setImageBitmap((Bitmap) msg.obj);
} }
}; // 根据可见项起止位置加载可见项
public void loadImages(int startIndex,int endIndex){
for (int i = startIndex; i < endIndex; i++) {
String url = NewsAdapter.URLS[i];
// 在异步请求之前,先判断缓存中是否有,有的话就取出直接加载
Bitmap bitmap = getBitMapFromCache(url);
if (bitmap == null){
// 如果没有,就异步任务加重
// new NewsAsyncTask(imageView, (String) imageView.getTag()).execute(urlString);// 这两个参数分别传递的目的地可以理解一下
NewsAsyncTask task = new NewsAsyncTask(url);
task.execute(url);
mTasks.add(task); }else{
ImageView loadImageView = (ImageView) mListView.findViewWithTag(url);
loadImageView.setImageBitmap(bitmap);
}
}
} public void cancelAllTask(){
if (mTasks != null){
for (NewsAsyncTask newsTask: mTasks) {
newsTask.cancel(false);
}
}
} public Bitmap getBitmapFromURL(String urlString){
Bitmap bitmap = null;
InputStream is = null; try {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
is = new BufferedInputStream(connection.getInputStream());
bitmap = BitmapFactory.decodeStream(is);
// 最后要关闭http连接
connection.disconnect();
// Thread.sleep(1000);// 睡眠1秒
} catch (IOException e) {
e.printStackTrace();
}
// catch (InterruptedException e) {
// e.printStackTrace();
// }
finally { try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
} return bitmap;
} // ==================使用AsyncTask====================
public void showImageByAsyncTask(ImageView imageView, final String urlString){
// 在异步请求之前,先判断缓存中是否有,有的话就取出直接加载
Bitmap bitmap = getBitMapFromCache(urlString);
if (bitmap == null){
// 如果没有,就异步任务加重
// new NewsAsyncTask( urlString).execute(urlString);// 这两个参数分别传递的目的地可以理解一下
imageView.setImageResource(R.mipmap.ic_launcher);
}else{
imageView.setImageBitmap(bitmap);
} } private class NewsAsyncTask extends AsyncTask<String,Void,Bitmap>{ // 需要私有的ImageView对象和构造方法来传递ImageView对象
// private ImageView mImageView;
private String mURlString; // public NewsAsyncTask(ImageView imageView,String urlString){
// mImageView = imageView;
// mURlString = urlString;
// } public NewsAsyncTask(String urlString){
mURlString = urlString;
} @Override
protected Bitmap doInBackground(String... strings) {
// 在这个方法中,完成异步下载的任务
String url = strings[0];
// 从网络中获取图片
Bitmap bitmap = getBitmapFromURL(strings[0]);
if (bitmap != null){
// 将网络加载出来的图片存储缓存
addBitmapToCache(url,bitmap);
}
return bitmap;
} @Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
// 然后在这个方法中设置imageViewd ImageView executeImageView = (ImageView) mListView.findViewWithTag(mURlString);
if (bitmap != null && executeImageView != null){
executeImageView.setImageBitmap(bitmap);
}
// 执行完当前任务,自然要把当前Task任务remove
mTasks.remove(this); // if (mImageView.getTag().equals(mURlString)){
// mImageView.setImageBitmap(bitmap);
// } }
} // ==================使用多线程====================
public void showImageByThread(ImageView imageView, final String urlString){ mImageView = imageView;
mURLStr = urlString; new Thread(){
@Override
public void run() {
super.run();
Bitmap bitmap = getBitmapFromURL(urlString);
// 当前线程是子线程,并不是UI主线程
// 不是Message message = new Message();
Message message = Message.obtain();
message.obj = bitmap;
handler.sendMessage(message); }
}.start();
}
}

但是,以上的代码还有一个不足之处,就是最开始启动的时候,因为没有滚动ScorllView,所以默认是没有加载图片的,但是我们可以做一个预加载:

完整源码:

 package com.example.heyang.myapplication;

 import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView; import java.util.List; /**
* Created by HeYang on 16/10/6.
*/ public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener{ // 适配器对象需要传入Bean数据集合对象,类似IOS的模型数组集合
private List<NewsBean> beanList;
// 然后要传入LayoutInflater对象,用来获取xml文件的视图控件
private LayoutInflater layoutInflater; private ImageLoader imageLoader; private boolean isFirstLoadImage; // 可见项的起止index
private int mStart,mEnd;
// 因为我们需要存储起止项所有的url地址
public static String[] URLS; // 创建构造方法
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public NewsAdapter(MainActivity context, List<NewsBean> data, ListView listView){ isFirstLoadImage = true; beanList = data;
layoutInflater = LayoutInflater.from(context);// 这个context对象就是Activity对象
imageLoader = new ImageLoader(listView); // 将模型数组中的url字符串单独存储在静态数组中
int dataSize = data.size();
URLS = new String[dataSize];
for (int i = 0; i < dataSize; i++) {
URLS[i] = data.get(i).newsIconURL;
} // 已经要记得注册
listView.setOnScrollListener(this);
} @Override
public int getCount() {
return beanList.size();
} @Override
public Object getItem(int i) {
// 因为beanList是数组,通过get访问对应index的元素
return beanList.get(i);
} @Override
public long getItemId(int i) {
return i;
} @Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder = null;
if (view == null){
viewHolder = new ViewHolder();
// 每一个View都要和layout关联
view = layoutInflater.inflate(R.layout.item_layout,null);
// 在R.layout.item_layout中有三个控件对象
// 现在全部传递给view对象了
viewHolder.tvTitle = (TextView) view.findViewById(R.id.tv_title);
viewHolder.tvContent = (TextView) view.findViewById(R.id.tv_content);
viewHolder.ivIcon = (ImageView) view.findViewById(R.id.iv_icon); view.setTag(viewHolder); }else { // 重复利用,但是由于里面的View展示的数据显然需要重新赋值
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.tvTitle.setText(beanList.get(i).newsTitle);
viewHolder.tvContent.setText(beanList.get(i).newsContent);
// 先默认加载系统图片
viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher); // 类似加载占位图片
viewHolder.ivIcon.setTag(beanList.get(i).newsIconURL);
// 将ImageView对象和URLSting对象传入进去
// new ImageLoader().showImageByThread(viewHolder.ivIcon,beanList.get(i).newsIconURL);
imageLoader.showImageByAsyncTask(viewHolder.ivIcon,beanList.get(i).newsIconURL);
return view;
} @Override
public void onScrollStateChanged(AbsListView absListView, int i) {
// SCROLL_STATE_IDLE : 滚动结束
if (i == SCROLL_STATE_IDLE){
// 加载可见项
imageLoader.loadImages(mStart,mEnd);
}else{
// 停止所有任务
imageLoader.cancelAllTask();
}
} @Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
// i是第一个可见元素 i1是当前可见元素的长度
mStart = i;
mEnd = i + i1;
// 第一次预加载可见项
if (isFirstLoadImage && i1 > 0){
imageLoader.loadImages(mStart,mEnd);
isFirstLoadImage = false;
}
} // 最后需要一个匿名内部类来创建一个临时缓存View的对象
class ViewHolder{
public TextView tvContent,tvTitle;
public ImageView ivIcon;
}
}

就这样完成了异步加载的完整最优化的功能:

完整源码下载地址:

https://github.com/HeYang123456789/Android-ListView-AsyncLoader-Demo

Android开发--异步加载的更多相关文章

  1. [Android] Android 用于异步加载 ContentProvider 中的内容的机制 -- Loader 机制 (LoaderManager + CursorLoader + LoaderManager.LoaderCallbacks)

    Android 用于异步加载 ContentProvider 中的内容的机制 -- Loader 机制 (LoaderManager + CursorLoader + LoaderManager.Lo ...

  2. Android 图片异步加载的体会,SoftReference已经不再适用

      在网络上搜索Android图片异步加载的相关文章,目前大部分提到的解决方案,都是采用Map<String, SoftReference<Drawable>>  这样软引用的 ...

  3. Android图片异步加载之Android-Universal-Image-Loader

    将近一个月没有更新博客了,由于这段时间以来准备毕业论文等各种事务缠身,一直没有时间和精力沉下来继续学习和整理一些东西.最近刚刚恢复到正轨,正好这两天看了下Android上关于图片异步加载的开源项目,就 ...

  4. Android图片异步加载之Android-Universal-Image-Loader(转)

    今天要介绍的是Github上一个使用非常广泛的图片异步加载库Android-Universal-Image-Loader,该项目的功能十分强大,可以说是我见过的目前功能最全.性能最优的图片异步加载解决 ...

  5. android 网络异步加载数据进度条

    ProgressDialog progressDialog = null; public static final int MESSAGETYPE = 0; private void execute( ...

  6. Android图片异步加载

    原:http://www.cnblogs.com/angeldevil/archive/2012/09/16/2687174.html 相关:https://github.com/nostra13/A ...

  7. Android图片异步加载框架Android-Universal-Image-Loader

    版权声明:本文为博主原创文章,未经博主允许不得转载. Android-Universal-Image-Loader是一个图片异步加载,缓存和显示的框架.这个框架已经被很多开发者所使用,是最常用的几个 ...

  8. Android 多线程 异步加载

    Android 应用中需要显示网络图片时,图片的加载过程较为耗时,因此加载过程使用线程池进行管理, 同时使用本地缓存保存图片(当来回滚动ListView时,调用缓存的图片),这样加载和显示图片较为友好 ...

  9. android ListView异步加载图片(双缓存)

    首先声明,参考博客地址:http://www.iteye.com/topic/685986 对于ListView,相信很多人都很熟悉,因为确实太常见了,所以,做的用户体验更好,就成了我们的追求... ...

随机推荐

  1. scikit-learn决策树算法类库使用小结

    之前对决策树的算法原理做了总结,包括决策树算法原理(上)和决策树算法原理(下).今天就从实践的角度来介绍决策树算法,主要是讲解使用scikit-learn来跑决策树算法,结果的可视化以及一些参数调参的 ...

  2. 高级javascript---原型和原型继承

    高级javascript---原型和原型继承 在 JavaScript 中,prototype 是函数的一个属性,同时也是由构造函数创建的对象的一个属性. 函数的原型为对象. 它主要在函数用作构造函数 ...

  3. IDDD 实现领域驱动设计-一个简单业务用例的回顾和理解

    上一篇:<IDDD 实现领域驱动设计-由贫血导致的失忆症> 这篇博文是对<实现领域驱动设计>第一章后半部分内容的理解. Domain Experts-领域专家 这节点内容是昨天 ...

  4. 关于一道数据库例题的解析。为什么σ age>22 (πS_ID,SCORE (SC) ) 选项是错的?

    本人大二学子.近段时间在做数据库复习题的时候遇到一道题,如下. 有关系SC(S_ID,C_ID,AGE,SCORE),查找年龄大于22岁的学生的学号和分数,正确的关系代数表达式是( ) . ⅰ. πS ...

  5. 对来自于Azure的远程连接文件(.rdp)的另一种更便捷的自定义方法

    在上一篇日志中(很抱歉那张比较黑的截图)介绍了如何获得Azure中的Windows虚拟机的远程连接文件,以及一种基于文本编辑方式进行自定义的方法. 实际上对于在Windows下的用户来说,我们可以使用 ...

  6. 软件开发常用设计模式—单例模式总结(c++版)

    单例模式:就是只有一个实例. singleton pattern单例模式:确保某一个类在程序运行中只能生成一个实例,并提供一个访问它的全局访问点.这个类称为单例类.如一个工程中,数据库访问对象只有一个 ...

  7. objective-c 语法快速过(2)

    oc类的声明和定义的常见错误 1.只有类的声明,没有类的实现 2.漏了@end 3.@interface和@implementation嵌套,也就是@interface或者@implementatio ...

  8. Oracle手边常用命令及操作语句

    Oracle手边常用命令及操作语句 作者:白宁超 时间:2016年3月4日11:24:08 摘要:日常使用oracle数据库过程中,常用脚本命令莫不是用户和密码.表空间.多表联合.执行语句等常规操作. ...

  9. C#常用的IO操作方法

    public class IoHelper { /// <summary> /// 判断文件是否存在 /// </summary> /// <param name=&qu ...

  10. Sql Server函数全解(三)数据类型转换函数和文本图像函数

    一:数据类型转换函数 在同时处理不同数据类型的值时,SQL Server一般会自动进行隐士类型转换.对于数据类型相近的值是有效的,比如int和float,但是对于其它数据类型,例如整型和字符类型,隐士 ...