【Android進階】ListView使用“內存雙緩存+硬盤緩存”加載網絡圖片

ListView 加載網絡圖片是我們經常用到的方式,如果每次滾動ListView就去網絡下載圖片會非常影響性能(因爲網絡下載是比較慢的)而且非常耗費流量,所以這裏介紹一種使用“內存雙緩存+硬盤緩存”的方式來加載圖片。

實現的效果如下:

這裏使用了滾動時不去網絡下載圖片,停止時才加載,所以滾動時顯示默認的,注意觀察


設計思想

內存讀取速度 > 文件讀取速度 > 從網絡獲取的速度

基本代碼邏輯如下

// 從內存緩存中獲取圖片  
        Bitmap result = memoryCache.getBitmapFromCache(url);  
        if (result == null) {  
            // 從文件緩存中獲取  
            result = fileCache.getImage(url);  
            if (result == null) {  
                // 從網絡獲取  
                result = ImageGetFromHttp.downloadBitmap(url);  
                if (result != null) {  
                    fileCache.saveBitmap(result, url);  
                    memoryCache.addBitmapToCache(url, result);  
                }  
            } else {  
                // 添加到內存緩存  
                memoryCache.addBitmapToCache(url, result);  
            }  
      }

內存緩存中使用了LruCache,LRU算法請參考:http://blog.csdn.net/luoweifu/article/details/8297084/

我們在內存緩存中再將內存分爲兩層,強引用緩存和軟引用緩存。

對強引用和軟引用做簡單的介紹(具體內容請看:http://blog.csdn.net/u010583599/article/details/51970515

① 強引用:強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足的問題。

② 軟引用:如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。

程序介紹

主界面是一個ListView,該ListViewItem佈局爲

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:orientation="horizontal" >
    <ImageView
        android:id="@+id/iv_icon" 
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:src="@drawable/ic_launcher"/>
    <LinearLayout 
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:paddingLeft="5dp"
        android:orientation="vertical">
        <TextView 
            android:id="@+id/tv_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="15sp"
            android:text="新聞的標題"/>
         <TextView 
            android:id="@+id/tv_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:maxLines="3"
            android:text="新聞的內容"/>
        
    </LinearLayout>

</LinearLayout>

Activity完成的功能是,請求並解析慕課網的接口的JSon字符串,創建ListView的適配器。

MainActivity.java

/**
 * 主類,訪問網絡接口獲取JSon字符串,並解析字符串,生成對象集合,創建listView適配器
 *
 */
public class MainActivity extends Activity {
	private ListView listView;
	//網絡接口來自慕課網,獲取一個Json字符串並解析
	private static final 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);
        listView = (ListView)findViewById(R.id.listview);
        new NewsAsyncTask().execute(URL);
    }
    private List<NewsBean> getJsonData(String url){
    	List<NewsBean> newsBeansList = new ArrayList<NewsBean>();
    	try {
    		//獲得json數據並解析Json字符串
			String jsonString = readStream(new URL(url).openStream());
			JSONObject jsonObject;
			NewsBean newsBean;
			jsonObject = new JSONObject(jsonString);
			JSONArray jsonArray = jsonObject.getJSONArray("data");
			for(int i = 0;i< jsonArray.length();i++){
				jsonObject = jsonArray.getJSONObject(i);
				newsBean = new NewsBean();
				newsBean.newsIconUrl = jsonObject.getString("picSmall");
				newsBean.newsTitle = jsonObject.getString("name");
				newsBean.newsContent = jsonObject.getString("description");
				newsBeansList.add(newsBean);
			}
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (JSONException e) {
			e.printStackTrace();
		}
    	return newsBeansList;
    }
    //獲取網絡輸入流數據,返回一個Json字符串
    private String readStream(InputStream is){
    	InputStreamReader isr;
    	String result = "";
    	try{
    		String line = "";
	    	isr = new InputStreamReader(is,"utf-8");
	    	BufferedReader br = new BufferedReader(isr);
	    	while((line = br.readLine()) != null){
	    		result += line;
	    	}
    	}catch(IOException e){
    		e.printStackTrace();
    	}
    	return result;
    }
    /**
     * 使用AsyncTask來訪問網絡獲取JSon數據
     *
     */
    class NewsAsyncTask extends AsyncTask<String , Void, List<NewsBean>>{

		@Override
		protected List<NewsBean> doInBackground(String... params) {
			return getJsonData(params[0]);
		}
    	@Override
    	protected void onPostExecute(List<NewsBean> result) {
    		super.onPostExecute(result);
    		//創建並給listView設置適配器
    		NewsAdapter adapter = new NewsAdapter(getApplicationContext(), result,listView);
    		listView.setAdapter(adapter);
    	}
    }
}

爲了解析Json字符串,我們需要創建一個實體類

NewsBean.java

public class NewsBean {
	public String newsIconUrl;
	public String newsTitle;
	public String newsContent;
	public NewsBean(String newsIconUrl,String newsTitle,String newsContent){
		this.newsIconUrl = newsIconUrl;
		this.newsTitle = newsTitle;
		this.newsContent = newsContent;
	}
	public NewsBean(){
		
	}
}

ListView 適配器類中我們完成了圖片的下載,並且進行了控制,ListView滑動時不進行任何的下載,停止狀態才進行網絡下載,並且進行了優化,防止圖片加載時錯位、重複、閃爍

NewsAdapter.java
public class NewsAdapter extends BaseAdapter implements OnScrollListener{
	private List<NewsBean> mList;
	private LayoutInflater mInflater;
	//圖片加載類
	private ImageLoader imageLoader;
	//listView開始下載和結束下載的位置
	private int mStart,mEnd;
	//所有的URL的數組
	public static String[] URLS;
	private boolean mFirstIn;//第一次啓動
	public NewsAdapter(Context context,List<NewsBean> mList,ListView listView){
		mInflater = LayoutInflater.from(context);
		this.mList = mList;
		imageLoader = new ImageLoader(listView,context);
		//獲取所有的URL並初始化數組
		URLS = new String[mList.size()];
		for(int i = 0;i<mList.size();i++){
			URLS[i] = mList.get(i).newsIconUrl;
		}
		listView.setOnScrollListener(this);
		mFirstIn = true;
	}
	@Override
	public int getCount() {
		return mList.size();
	}

	@Override
	public Object getItem(int position) {
		return mList.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder viewHolder = null;
		if(convertView == null){
			viewHolder = new ViewHolder();
			convertView = mInflater.inflate(R.layout.item_layout, null);
			viewHolder.ivIcon = (ImageView)convertView.findViewById(R.id.iv_icon);
			viewHolder.tvTitle = (TextView)convertView.findViewById(R.id.tv_title);
			viewHolder.tvContent = (TextView)convertView.findViewById(R.id.tv_content);
			convertView.setTag(viewHolder);
		}else{
			viewHolder = (ViewHolder) convertView.getTag();
		}
		//設置默認的圖片
//		viewHolder.ivIcon.setImageResource(R.drawable.ic_launcher);
		
		String url = mList.get(position).newsIconUrl;
		/*因爲Itme是重複利用的,ListView滑動到第2行會異步加載某個圖片,但是加載很慢,加載過程中listView已經滑動到了第14行,
		 * 且滑動過程中該圖片加載結束,第2行已不在屏幕內,根據緩存原理,第2行的view可能被第14行復用,這樣我
		 * 們看到的就是第14行顯示了本該屬於第2行的圖片,造成顯示重複。如果14行的圖片也加載結束則會造成閃爍,先顯示前一張,再顯示後一張
		 * 爲了防止圖片加載時錯位,這裏加上tag,把imageView和url標識綁定,在異步顯示的位置,判斷當前任務的url和item設置的url是否
		 * 相同,只有相同纔去加載圖片
		 */
		viewHolder.ivIcon.setTag(url);
		//在滾動的時候加載圖片,如果緩存中都沒有,則使用默認的圖片
		imageLoader.showImagesFromCache(viewHolder.ivIcon, url);
		viewHolder.tvTitle.setText(mList.get(position).newsTitle);
		viewHolder.tvContent.setText(mList.get(position).newsContent);
		return convertView;
	}
	class ViewHolder{
		public TextView tvTitle,tvContent;
		public ImageView ivIcon;
	}
	
	@Override
	public void onScrollStateChanged(AbsListView view, int scrollState) {
		if(scrollState == SCROLL_STATE_IDLE){
			//當前狀態處於停止狀態,加載可見項
			imageLoader.loadImages(mStart, mEnd);
		}else{
			//停止任務
			imageLoader.cancelAllTasks();
		}
	}
	/**
	 * 由於我們使用的是滾動狀態改變時纔去下載圖片,但是第一次進入的時候要加載第一屏的圖片
	 * listview初始化後會調用onScroll方法,我們在這裏去加載第一屏的圖片並把第一次進入
	 * 狀態位置爲false
	 */
	@Override
	public void onScroll(AbsListView view, int firstVisibleItem,
			int visibleItemCount, int totalItemCount) {
		//start 爲第一個可見的item的位置
		mStart = firstVisibleItem;
		//end 爲第一個可見的位置加上可見的item的數量
		mEnd = firstVisibleItem + visibleItemCount;
		if(mFirstIn && visibleItemCount > 0){
			//第一次顯示的時候調用,加載圖片
			imageLoader.loadImages(mStart, mEnd);
			mFirstIn = false;
		}
	}
}
內存緩存類 ImageMemoryCache.java

/**
 * @author meng.li
 * 內存緩存圖片類,這裏使用了兩層內存緩存
 */
public class ImageMemoryCache {
	/** 
     * 從內存讀取數據速度是最快的,爲了更大限度使用內存,這裏使用了兩層緩存。 
     * 強引用緩存不會輕易被回收,用來保存常用數據
     * 不常用的數據轉入軟引用緩存,不會影響GC的回收。 
     */  
    private static final int SOFT_CACHE_SIZE = 15;  //軟引用緩存容量  
    private static LruCache<String, Bitmap> mLruCache;  //硬引用緩存  
    private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache;  //軟引用緩存  
                                                                                            
    public ImageMemoryCache(Context context) {  
    	//獲取最大可用內存
    	int maxMemory = (int) Runtime.getRuntime().maxMemory();
    	Log.i("mengli","maxMemory = "+ maxMemory);
    	//強引用緩存容量,爲系統可用內存的1/4 
    	int cacheSize = maxMemory/4;
        mLruCache = new LruCache<String, Bitmap>(cacheSize) {  
            @Override  
            protected int sizeOf(String key, Bitmap value) {  
                if (value != null)  
                	//每次加入緩存時會調用
    				return value.getByteCount();
                else  
                    return 0;  
            }
            @Override  
            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {  
                if (oldValue != null)
                	//LRU算法會把最近使用的元素壓入棧頂,所以棧底就是被移除的元素
                    // 強引用緩存容量滿的時候,會根據LRU算法把最近最久沒有被使用的圖片轉入此軟引用緩存  
                    mSoftCache.put(key, new SoftReference<Bitmap>(oldValue));  
            }  
        };  
        mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>>(SOFT_CACHE_SIZE, 0.75f, true) {  
//            private static final long serialVersionUID = 6040103833179403725L;  
            @Override  
            protected boolean removeEldestEntry(Entry<String, SoftReference<Bitmap>> eldest) {  
                if (size() > SOFT_CACHE_SIZE){      
                    return true;    
                }    
                return false;   
            }  
        };  
    }  
                                                                                    
    /** 
     * 從緩存中獲取圖片 
     */  
    public Bitmap getBitmapFromCache(String url) {  
        Bitmap bitmap;  
        //先從強引用緩存中獲取  
        synchronized (mLruCache) {  
            bitmap = mLruCache.get(url);  
            if (bitmap != null) {  
                //如果找到的話,把元素移到LinkedHashMap的最前面,從而保證在LRU算法中最後被刪除  
                mLruCache.remove(url);  
                mLruCache.put(url, bitmap);  
                return bitmap;  
            }  
        }  
        //如果強引用緩存中找不到,到軟引用緩存中找 
        synchronized (mSoftCache) {   
            SoftReference<Bitmap> bitmapReference = mSoftCache.get(url);  
            if (bitmapReference != null) {  
                bitmap = bitmapReference.get();  
                if (bitmap != null) {  
                    //將圖片移回硬緩存  
                    mLruCache.put(url, bitmap);  
                    mSoftCache.remove(url);  
                    return bitmap;  
                } else {
                	//沒找到,可能改bigmap已經被回收了,刪除url
                    mSoftCache.remove(url);  
                }  
            }  
        }  
        return null;  
    }   
                                                                                    
    /** 
     * 添加圖片到緩存 
     */  
    public void addBitmapToCache(String url, Bitmap bitmap) {  
        if (bitmap != null) {  
            synchronized (mLruCache) {  
                mLruCache.put(url, bitmap);  
            }  
        }  
    }  
                                                                                    
    public void clearCache() {  
        mSoftCache.clear();  
    }  
}
文件緩存類 ImageFileCache.java

/**
 * @author meng.li
 *  文件緩存類
 */
public class ImageFileCache {
	//緩存目錄名
    private static final String CACHDIR = "ImgeCache";  
    private static final String WHOLESALE_CONV = ".cach";  
                                                              
    private static final int MB = 1024*1024;
    //緩存大小
    private static final int CACHE_SIZE = 10; 
    //剩餘最小空間大小
    private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;  
                                                                  
    public ImageFileCache() {  
        //清理文件緩存  
        removeCache(getDirectory());  
    }  
                                                                  
    /** 從緩存中獲取圖片 **/  
    public Bitmap getImage(final String url) {
        final String path = getDirectory() + "/" + getFileNameFromUrl(url);  
        File file = new File(path);  
        if (file.exists()) {  
            Bitmap bmp = BitmapFactory.decodeFile(path);  
            if (bmp == null) {  
                file.delete();  
            } else {
            	//獲取時圖片時需要更新文件的最後修改時間
                updateFileTime(path);  
                return bmp;  
            }  
        }  
        return null;  
    }
                                                                  
    /** 將圖片存入文件緩存 **/  
    public void saveBitmap(Bitmap bm, String url) {  
        if (bm == null) {  
            return;  
        }  
        //判斷sdcard上的空間 ,如果不足10M返回
        if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {  
            //SD空間不足  
            return;  
        }  
        String filename = getFileNameFromUrl(url);  
        String dir = getDirectory();  
        File dirFile = new File(dir);  
        if (!dirFile.exists())  
            dirFile.mkdirs();
        //創建文件
        File file = new File(dir +"/" + filename);  
        try {  
            file.createNewFile();  
            OutputStream outStream = new FileOutputStream(file);
            //將圖片進行壓縮並寫入文件,100表示不壓縮
            bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);  
            outStream.flush();  
            outStream.close();  
        } catch (FileNotFoundException e) {  
            Log.w("ImageFileCache", "FileNotFoundException");  
        } catch (IOException e) {  
            Log.w("ImageFileCache", "IOException");  
        }  
    }   
                                                                  
    /** 
     * 計算存儲目錄下的文件大小, 
     * 當文件總大小大於規定的CACHE_SIZE或者sdcard剩餘空間小於FREE_SD_SPACE_NEEDED_TO_CACHE的規定 
     * 那麼刪除40%最近沒有被使用的文件 
     */  
    private boolean removeCache(String dirPath) {  
        File dir = new File(dirPath);  
        File[] files = dir.listFiles();  
        if (files == null) {  
            return true;  
        }
        //沒有掛載外部存儲設備
        if (!android.os.Environment.getExternalStorageState().equals(  
                android.os.Environment.MEDIA_MOUNTED)) {  
            return false;  
        }
                                                              
        int dirSize = 0;  
        for (int i = 0; i < files.length; i++) {
        	//遍歷目錄下的所有文件,如果包含.cach 則累加size
            if (files[i].getName().contains(WHOLESALE_CONV)) {  
                dirSize += files[i].length();  
            }  
        }
        //如果緩存目錄的文件大小 大於規定的緩存大小或者剩餘內存不足10M,則刪除40%最久未使用的文件
        if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {  
            int removeFactor = (int) ((0.4 * files.length) + 1);
            //對文件按時間排序
            Arrays.sort(files, new FileLastModifSort());
            for (int i = 0; i < removeFactor; i++) {  
                if (files[i].getName().contains(WHOLESALE_CONV)) {  
                    files[i].delete();  
                }  
            }  
        }  
                                                              
        if (freeSpaceOnSd() <= CACHE_SIZE) {  
            return false;  
        }  
                                                                      
        return true;  
    }  
                                                                  
    /** 修改文件的最後修改時間 **/  
    public void updateFileTime(String path) {  
        File file = new File(path);  
        long newModifiedTime = System.currentTimeMillis();  
        file.setLastModified(newModifiedTime);  
    }  
                                                                  
    /** 計算sdcard上的剩餘空間 **/  
    private int freeSpaceOnSd() {  
        StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());  
        double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;  
        return (int) sdFreeMB;  
    }   
                                                                  
    /**從url中獲取文件名 **/  
    private String getFileNameFromUrl(String url) {  
        return url.substring(url.lastIndexOf("/")+1)+WHOLESALE_CONV;
    }  
                                                                  
    /** 獲得緩存目錄 **/  
    private String getDirectory() {  
        String dir = getSDPath() + "/" + CACHDIR;  
        return dir;  
    }  
                                                                  
    /** 取SD卡路徑 **/  
    private String getSDPath() {  
        File sdDir = null;  
        boolean sdCardExist = Environment.getExternalStorageState().equals(  
                android.os.Environment.MEDIA_MOUNTED);  //判斷sd卡是否存在  
        if (sdCardExist) {  
            sdDir = Environment.getExternalStorageDirectory();  //獲取根目錄  
        }  
        if (sdDir != null) {  
            return sdDir.toString();  
        } else {  
            return "";  
        }  
    }   
    /** 
     * 根據文件的最後修改時間進行排序,Java中對對象進行排序要實現Comparator 接口,自己實現比較規則
     * 1 表示大於,0表示相等,-1表示小於 
     */  
    private class FileLastModifSort implements Comparator<File> {  
        public int compare(File arg0, File arg1) {  
            if (arg0.lastModified() > arg1.lastModified()) {  
                return 1;  
            } else if (arg0.lastModified() == arg1.lastModified()) {  
                return 0;  
            } else {  
                return -1;  
            }  
        }  
    }  
}  

通過圖片我們可以看到,圖片被寫入了文件


圖片下載類,使用多線程下載圖片用了AsyncTask封裝類

public class ImageLoader {
	/**
	 * 使用多線程的方式去加載圖片
	 */
	private ImageView imageView;
	private String mUrl;
	//內存緩存
	private ImageMemoryCache memoryCache;
	//文件緩存
	private ImageFileCache fileCache;
	private ListView mListView;
	//任務集合,用來處理多個下載線程
	private Set<NewsAsyncTask> mTasks;
	public ImageLoader(ListView listView,Context context){
		mListView = listView;
		mTasks = new HashSet<ImageLoader.NewsAsyncTask>();
		memoryCache = new ImageMemoryCache(context);
		fileCache = new ImageFileCache();
	}
	/**
	 * 用於從一個url獲取bitmap
	 */
	public Bitmap getBitmapFromURL(String urlString){
		Bitmap bitmap;
		InputStream is = null;
		try {
			URL url = new URL(urlString);
			HttpURLConnection connection = (HttpURLConnection)url.openConnection();
			is = new BufferedInputStream(connection.getInputStream());
			bitmap = BitmapFactory.decodeStream(is);
			connection.disconnect();
			return bitmap;
		} catch (Exception e) {
			
		}finally{
			try {
				is.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	//在滾動的時候顯示緩存的圖片,如果沒有緩存圖片則顯示默認的圖片
	public void showImagesFromCache(ImageView imageView,String url){
		//從緩存取出圖片
		Bitmap result = memoryCache.getBitmapFromCache(url);  
        if (result == null) {
            // 文件緩存中獲取  
            result = fileCache.getImage(url);
        }
		if(result == null){
			imageView.setImageResource(R.drawable.ic_launcher);
		}else{
			imageView.setImageBitmap(result);
		}
	}
	//取消加載圖片
	public void cancelAllTasks(){
		if(mTasks != null){
			for(NewsAsyncTask task: mTasks){
				task.cancel(false);
			}
		}
	}
	public void loadImages(int start,int end){
		//加載從start到end的圖片
		for(int i = start;i<end;i++){
			String url = NewsAdapter.URLS[i];
			//從內存緩存取出圖片
			Bitmap bitmap = memoryCache.getBitmapFromCache(url);
			//如果緩存沒有,則從文件中讀取
			if(bitmap == null){
				//從文件獲取圖片
				bitmap = fileCache.getImage(url);
				//文件中也爲空,則必須從網絡下載圖片
				if(bitmap == null){
					//使用AsyncTask下載圖片,這裏會耗費流量
					NewsAsyncTask task = new NewsAsyncTask(url);
					task.execute(url);
					//添加一個任務
					mTasks.add(task);
				}else{
					//文件中獲取到了圖片,則把圖片加入到內存中
					memoryCache.addBitmapToCache(url, bitmap);
				}
			}
			if(bitmap != null){
				//根據url去獲取 對應的imageView對象,防止顯示混亂
				ImageView imageView = (ImageView) mListView.findViewWithTag(url);
				imageView.setImageBitmap(bitmap);
			}
		}
	}
	private class NewsAsyncTask extends AsyncTask<String, Void, Bitmap>{
		private String mUrl;
		public NewsAsyncTask(String url){
			mUrl = url;
		}
		@Override
		protected Bitmap doInBackground(String... params) {
			//從網絡獲取圖片
			Bitmap bitmap = getBitmapFromURL(params[0]);
			if(bitmap != null){
				//把bitmap加入到緩存
				memoryCache.addBitmapToCache(params[0], bitmap);
				//把bitmap 加入到文件
				fileCache.saveBitmap(bitmap,params[0]);
			}
			return bitmap;
		}
		@Override
		protected void onPostExecute(Bitmap result) {
			super.onPostExecute(result);
			ImageView imageView = (ImageView) mListView.findViewWithTag(mUrl);
			if(imageView != null && result != null){
				imageView.setImageBitmap(result);
			}
			//下載任務完成則移除這個任務
			mTasks.remove(this);
		}
	}
}

AndroidManifest.xml  注意加上權限

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.newsdemo"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="22"
        android:targetSdkVersion="22" />
	<uses-permission android:name="android.permission.INTERNET"/>
	<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@android:style/Theme.Black.NoTitleBar" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

菜鳥一隻,剛踏上追求技術的不歸路,記錄所學,希望大家指點!

代碼下載 :http://download.csdn.net/detail/u010583599/9583583

本文參考:

http://blog.csdn.net/a79412906/article/details/10180583

http://www.imooc.com/learn/406









發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章