细解android之图片下载(无任何框架)

哈哈,不知不觉时间过得真快,上篇博客是写viewpage的处理,其实有些地方是不足的:比如事件分发之类的,暂时先留着,后面再来做详细修改:
之所以来写这篇博客,主要是想把基础知识打扎实,框架用多了固然能给开发带来很多方便的地方,但只知道所以为,不知其然并不是长久之路,所以还是要把基础打好。来写正文了,先来个效果图

细解android之图片下载(无任何框架)_第1张图片
GIF.gif

在写这些之前有必要把一些知识点来讲讲
首先是Bitmap;
然后java的io流操作
还有就是android中SD卡的操作;
关于Bitmap有很多文章系统性的介绍,我只把我常用的写进来
Bitmap
Bitmap是Android系统中的图像处理的最重要类之一。用它可以获取图像文件信息,进行图像剪切、旋转、缩放等操作,并可以指定格式保存图像文件。

 Bitmap重要函数

 public void recycle() // 回收位图占用的内存空间,把位图标记为Dead

 public final boolean isRecycled() //判断位图内存是否已释放  

 public final int getWidth()//获取位图的宽度 

 public final int getHeight()//获取位图的高度

 public final boolean isMutable()//图片是否可修改 

 public int getScaledWidth(Canvas canvas)//获取指定密度转换后的图像的宽度 

 public int getScaledHeight(Canvas canvas)//获取指定密度转换后的图像的高度 

public boolean compress(CompressFormat format, int quality, OutputStream stream)//按指定的图片格式以及画质,将图片转换为输出流。 

format:Bitmap.CompressFormat.PNG或Bitmap.CompressFormat.JPEG 

quality:画质,0-100.0表示最低画质压缩,100以最高画质压缩。对于PNG等无损格式的图片,会忽略此项设置。

public static Bitmap createBitmap(Bitmap src) //以src为原图生成不可变得新图像 

public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)//以src为原图,创建新的图像,指定新图像的高宽以及是否可变。 

public static Bitmap createBitmap(int width, int height, Config config)——创建指定格式、大小的位图 

public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height)以source为原图,创建新的图片,指定起始坐标以及新图像的高宽。

BitmapFactory工厂类:

**  Option 参数类:**
public boolean inJustDecodeBounds//如果设置为true,不获取图片,不分配内存,但会返回图片的高度宽度信息。

public int inSampleSize//图片缩放的倍数

public int outWidth//获取图片的宽度值

public int outHeight//获取图片的高度值 

public int inDensity//用于位图的像素压缩比 

public int inTargetDensity//用于目标位图的像素压缩比(要生成的位图) 

public byte[] inTempStorage //创建临时文件,将图片存储

public boolean inScaled//设置为true时进行图片压缩,从inDensity到inTargetDensity

public boolean inDither //如果为true,解码器尝试抖动解码

public Bitmap.Config inPreferredConfig //设置解码器

public String outMimeType //设置解码图像

public boolean inPurgeable//当存储Pixel的内存空间在系统内存不足时是否可以被回收

public boolean inInputShareable //inPurgeable为true情况下才生效,是否可以共享一个InputStream

public boolean inPreferQualityOverSpeed  //为true则优先保证Bitmap质量其次是解码速度

public boolean inMutable //配置Bitmap是否可以更改,比如:在Bitmap上隔几个像素加一条线段

public int inScreenDensity //当前屏幕的像素密度

  工厂方法:
public static Bitmap decodeFile(String pathName, Options opts) //从文件读取图片 

public static Bitmap decodeFile(String pathName)

public static Bitmap decodeStream(InputStream is) //从输入流读取图片

public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)

public static Bitmap decodeResource(Resources res, int id) //从资源文件读取图片

public static Bitmap decodeResource(Resources res, int id, Options opts) 

public static Bitmap decodeByteArray(byte[] data, int offset, int length) //从数组读取图片

public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts)

public static Bitmap decodeFileDescriptor(FileDescriptor fd)//从文件读取文件 与decodeFile不同的是这个直接调用JNI函数进行读取 效率比较高

public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts)

Bitmap.Config inPreferredConfig :

  枚举变量 (位图位数越高代表其可以存储的颜色信息越多,图像越逼真,占用内存越大)
public static final Bitmap.Config ALPHA_8 //代表8位Alpha位图        每个像素占用1byte内存
public static final Bitmap.Config ARGB_4444 //代表16位ARGB位图  每个像素占用2byte内存
public static final Bitmap.Config ARGB_8888 //代表32位ARGB位图  每个像素占用4byte内存
public static final Bitmap.Config RGB_565 //代表8位RGB位图          每个像素占用2byte内存

Android中一张图片(BitMap)占用的内存主要和以下几个因数有关:图片长度,图片宽度,单位像素占用的字节数。
一张图片(BitMap)占用的内存=图片长度图片宽度/单位像素占用的字节数

IO流
“流”的概念
“流”是一个抽象的概念,它是对输入输出设备的一种抽象理解,在java中,对数据的输入输出操作都是以“流”的方式进行的。“流”具有方向性,输入流、输出流是相对的。当程序需要从数据源中读入数据的时候就会开启一个输入流,相反,写出数据到某个数据源目的地的时候也会开启一个输出流。数据源可以是文件、内存或者网络等。
其实你只要记住
你的程序如果要获得数据就是输入流,比如从网络下载图片
你的程序要写出数据就是输出流,比如将图片保存在sd卡
流的分类
“流”序列中的数据可以是未经加工的原始二进制数据,也可以是经过一定编码处理后符合某种格式的特定数据,因此java中的“流”分为两种流:

  1. 字节流:数据流中的最小的数据单元是字节,一次读入读出8位二进制;
  2. 字符流:数据流中的最小的数据单元是字符,一次读入读出16位二进制,java中的字符是Unicode编码,一个字符占用两个字节。

“流”结构分类
“流”存在与java.io包中,主要包含四种基本的类,
字节流
InputStream、OutputStream

细解android之图片下载(无任何框架)_第2张图片
InputStream.png
细解android之图片下载(无任何框架)_第3张图片
OutputStream.png

Reader 、 Writer

细解android之图片下载(无任何框架)_第4张图片
Reader.png
细解android之图片下载(无任何框架)_第5张图片
Writer.png

“流”常用方法

InputStream为字节输入流,本身是个抽象类,必须依靠其子类实现各种功能,数据单位位字节(8bit)。常用方法有

        (1) public abstract int read( ):读取一个byte的数据,返回值是高位补0的int类型值。若返回值=-1说明没有读取到任何字节读取工作结束。
   (2) public int read(byte b[ ]):读取b.length个字节的数据放到b数组中。返回值是读取的字节数。该方法实际上是调用下一个方法实现的 
   (3) public int read(byte b[ ], int off, int len):从输入流中最多读取len个字节的数据,存放到偏移量为off的b数组中。 
   (4) public int available( ):返回输入流中可以读取的字节数。注意:若输入阻塞,当前线程将被挂起,如果InputStream对象调用这个方法的话,它只会返回0,这个方法必须由继承InputStream类的子类对象调用才有用, 
   (5) public long skip(long n):忽略输入流中的n个字节,返回值是实际忽略的字节数, 跳过一些字节来读取 
   (6) public int close( ) :我们在使用完后,必须对我们打开的流进行关闭. 



         OutputStream为字节输出流,常用方法有:

  (1) public void write(byte b[ ]):将参数b中的字节写到输出流。 
  (2) public void write(byte b[ ], int off, int len) :将参数b的从偏移量off开始的len个字节写到输出流。 
  (3) public abstract void write(int b) :先将int转换为byte类型,把低字节写入到输出流中。 
  (4) public void flush( ) : 将数据缓冲区中数据全部输出,并清空缓冲区。 
  (5) public void close( ) : 关闭输出流并释放与流相关的系统资源。



          Reader为字符输入流,常用方法有:

        (1)  public int read() throws IOException: 读取一个字符,返回值为读取的字符 

   (2)  public int read(char cbuf[]) throws IOException:读取一系列字符到数组cbuf[]中,返回值为实际读取的字符的数量
   (3)  public abstract int read(char cbuf[],int off,int len) throws IOException:读取len个字符,从数组cbuf[]的下标off处开始存放,返回值为实际读取的字符数量,该方法必须由子类实现。



          Writer为字符输出流,常用法有:

        (1) public void write(int c) throws IOException:将整型值c的低16位写入输出流 
   (2) public void write(char cbuf[]) throws IOException:将字符数组cbuf[]写入输出流 
   (3) public abstract void write(char cbuf[],int off,int len) throws IOException:将字符数组cbuf[]中的从索引为off的位置处开始的len个字符写入输出流 
   (4) public void write(String str) throws IOException:将字符串str中的字符写入输出流 
   (5) public void write(String str,int off,int len) throws IOException:将字符串str 中从索引off开始处的len个字符写入输出流 
   (6) flush( ) :刷空输出流,并输出所有被缓存的字节。 
   (7) close()  :关闭流 

SD卡的相关知识点

/system 存放的是rom的信息;
/system/app 存放rom本身附带的软件即系统软件;
/system/data 存放/system/app 中核心系统软件的数据文件信息。
/data 存放的是用户的软件信息(非自带rom安装的软件);
/data/app 存放用户安装的软件;
/data/data 存放所有软件(包括/system/app 和 /data/app 和 /mnt/asec中装的软件)的一些lib和xml文件等数据信息;
/data/dalvik-cache 存放程序的缓存文件,这里的文件都是可以删除的。
/mnt 目录,熟悉linux的人都清楚,linux默认挂载外部设备都会挂到这个目录下面去,如将sd卡挂载上去后,会生成一个/mnt/sdcard 目录。
/sdcard 目录,这是一个软链接(相当于windows的文件夹的快捷方式),链接到/mnt/sdcard 目录,即这个目录的内容就是sdcard的内容。
getExternalFilesDir()
获取应用程序下的存储目录,/data/data/your_package/,随着应用的卸载存储的文件被删除

getExternalStorageDirectory()
获取sd卡根目录,跟应用的是否卸载无关。

getFilesDir()/data/data/[packagename]/files 
文件缓存目录,一般存小的文件缓存,如果是图片,不建议放这里,一般放到外置卡

getDownloadCacheDirectory() 
获得下载缓存目录。(/cache)

getCacheDir()/data/data/[packagename]/cache
目录 存放一些其他缓存

getRootDirectory() 获得系统主目录(/system)

如果你上面这些知识点都很熟悉的话,恭喜你基础知识很扎实。
接下来来看具体代码

先来看下布局吧




    

java代码

package com.cs.test_image3;

import android.app.ProgressDialog;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button mBtnLoading;
    private Button mBtnSaveimage;
    private Button mBtnDelete;
    private ImageView mImage;
    private String URLPATH = "http://ww4.sinaimg.cn/large/610dc034gw1fafmi73pomj20u00u0abr.jpg";
    //外部存储    /mnt/sdcard路径下
    private final static String FILEPATH = Environment.getExternalStorageDirectory() + "/BBB/";
 /*   private final static String FILEPATH1= Environment.getDataDirectory()+"/BBB/";
    private final static String FILEPATH2= Environment.getExternalStorageDirectory()+"/BBB/";*/

    private Bitmap mBitmap;
    private String mFileName = "test.jpg";
    private ProgressDialog mProgressDialog;
    private File file;// mnt/sd/BBB/
    private File dirfile;// mnt/sd/BBB/
    private Button mRxjava;
    private ImageView mImage2;
    private Button mBtnRead;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        mBtnLoading = (Button) findViewById(R.id.btn_loading);
        mBtnSaveimage = (Button) findViewById(R.id.btn_saveimage);
        mBtnDelete = (Button) findViewById(R.id.btn_delete);

        mBtnLoading.setOnClickListener(this);
        mBtnSaveimage.setOnClickListener(this);
        mBtnDelete.setOnClickListener(this);
        mImage = (ImageView) findViewById(R.id.image);

        mRxjava = (Button) findViewById(R.id.rxjava);
        mRxjava.setOnClickListener(this);
        mImage2 = (ImageView) findViewById(R.id.image2);

        mBtnRead = (Button) findViewById(R.id.btn_read);
        mBtnRead.setOnClickListener(this);
        mBtnRead.setClickable(false);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_loading:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        // LoadImage1();
                        LoadImage2();

                    }
                }).start();
                mBtnRead.setClickable(true);

                break;
            case R.id.btn_saveimage:
              /*  mProgressDialog = ProgressDialog.show(MainActivity.this, "保存图片", "图片正在保存中,请稍等...", true);
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        SaveImage();

                    }
                }).start();
               sdHandler.sendEmptyMessage(2);*/
                mProgressDialog = ProgressDialog.show(MainActivity.this, "保存图片", "图片正在保存中,请稍等...", true);
                SaveImage();
                connectHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.what = 2;
                        connectHandler.sendMessage(message);
                    }
                }, 1000);
                mBtnRead.setClickable(true);
                break;
            case R.id.btn_delete:
                DeleteFile(dirfile);
                mBitmap = null;
                Message message = new Message();
                message.what = 3;
                connectHandler.sendMessage(message);
                mBtnRead.setClickable(true);
                break;
            case R.id.rxjava:
                startActivity(new Intent(MainActivity.this, RxActivity.class));
                mBtnRead.setClickable(true);
                break;
            case R.id.btn_read:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        if (file.exists()) {
                            ReadFromSDCard(file);
                            Message message = new Message();
                            message.what = 4;
                            connectHandler.sendMessage(message);
                        }else {
                            //Toast.makeText(MainActivity.this, "文件不存在", Toast.LENGTH_SHORT).show();
                            Message message = new Message();
                            message.what = 5;
                            connectHandler.sendMessage(message);
                        }

                    }
                }).start();


                break;
        }
    }



    /**
     * 主线程更新UI
     */
    private Handler connectHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    mImage.setImageBitmap(mBitmap);
                    break;
                case 2:
                    mProgressDialog.dismiss();
                    Toast.makeText(MainActivity.this, "图片保存完成", Toast.LENGTH_SHORT).show();
                    break;
                case 3:
                    mBitmap = null;
                    mImage.setImageBitmap(mBitmap);
                    mImage2.setImageBitmap(mBitmap);
                    break;
                case 4:
                    mImage2.setImageBitmap(mBitmap);
                    break;
                case 5:
                    Toast.makeText(MainActivity.this, "文件不存在", Toast.LENGTH_SHORT).show();
                    break;

            }

        }
    };

    /**
     * 下载图片从字节数组里面
     */
    private void LoadImage1() {
        //将图片从网络获取,并用hanler更行ui
        try {
            //传入需要的网址
            URL url = new URL(URLPATH);
            //打开网络连接
            final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            //设置网络延时
            connection.setConnectTimeout(5 * 1000);
            //设置获取方式
            connection.setRequestMethod("GET");
            //转变为输入流
            InputStream inputStream = connection.getInputStream();
            if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                //将输入流转变为字节流
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                //每次读取以一字节读取
                byte[] buffer = new byte[1024];
                //初始化读取字节的长度
                int len;
                //设置len
                while ((len = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, len);
                }
                outputStream.close();
                inputStream.close();
                //将字节流转变为字节数组由bitmapfacoty来写入
                byte[] byteArray = outputStream.toByteArray();
                mBitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
                //用hanler在主线程去更新UI,这里给个延时操作
                connectHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.what = 1;
                        connectHandler.sendMessage(message);
                    }
                }, 2000);
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 下载图片从输入里面
     */
    private void LoadImage2() {
        try {
            URL url = new URL(URLPATH);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5 * 1000);
            conn.setRequestMethod("GET");
            InputStream inputStream = conn.getInputStream();
            mBitmap = BitmapFactory.decodeStream(inputStream);
            Message message = new Message();
            message.what = 1;
            connectHandler.sendMessage(message);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }

    /**
     * 保存图片到sd卡指定目录
     */
    private void SaveImage() {
        //创建文件 在是的sd卡外部存储
        dirfile = new File(FILEPATH);
        if (!dirfile.exists()) {
            dirfile.mkdir();
        }

        String fileName;
        //指定保存文件路径
        file = new File(FILEPATH + mFileName);
        //从系统保存需要用到输出流
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            BufferedOutputStream bos = new BufferedOutputStream(fileOutputStream);
            //bitmap进行解码
            if (mBitmap == null) {
                Toast.makeText(this, "Kong", Toast.LENGTH_SHORT).show();
                mProgressDialog.cancel();
            } else {
                mBitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
                bos.flush();
                bos.hashCode();
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 删除sd卡下的图片
     */
    private void DeleteFile(File file) {
        if (file.isFile()) {
            file.delete();
            return;
        }
        if (file.isDirectory()) {
            File[] childFile = file.listFiles();
            if (childFile == null || childFile.length == 0) {
                file.delete();
                return;
            }
           /* for (File f : childFile) {
                DeleteFile(f);
            }
            file.delete();*/
            for (int i = 0; i < childFile.length; i++) {
                //file下面的子文件删除
                DeleteFile(childFile[i]);
            }
            //删除父文件夹
            file.delete();
        }
    }

    /**
     * 从sd卡里面读取文件
     * @param file
     */
    private void ReadFromSDCard(File file) {
            try {
                FileInputStream fileInputStream = new FileInputStream(file);
               // mBitmap.recycle();
                mBitmap=BitmapFactory.decodeStream(fileInputStream);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
    }
}

因为是对网络和sd卡的操作
所以需要加权限

    
    
    

在解释之前希望新手朋友们有一个概念
为了防止主线程被阻塞,通常需要启动子线程来处理耗时任务,子线程的任务完成时通过handler通知主线程,让主线程刷新ui等

来讲下简单的逻辑,老手可以忽略
首先是下载图片,下载图片的最终目的是能得到 输入流
由bitmap的方法
BitmapFactory.decodeStream(inputStream)
BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length)
可以知道有两种写法
用惯了okhttp,HttpURLConnection不熟悉了吧
貌似源代码里面少了一句
connection.connect();
因为是请求网络数据是耗时操作
通过HttpURLConnection获取流,获取bitmap,更新的话就要调用handler;
不了解hadler的话也没关系,去学习如下rxjava吧;

保存图片则是输出流的操作了
但因为是图片,所以要创建文件,在哪里创建文件根据需求,对应的sd卡目录也已经在本文中详细的解释了
将输出流转码保存图片就调用
mBitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);

删除文件的操作是io操作,思路是是否为文件,文件夹,遍历,其实可以封装为工具类,这里为了更好的理解直接写在代码里面;

最后一个就是从sd卡里面读取文件,这个其实本质将就是输入流的读取,如果对网络下载理解透了这个就真的很容易理解了

本文最后是rxjava的下载,还没有详细的写,主要是还在了解rxjava的操作符,真的很多,估计等下篇博客吧;
图片上传没写,找不到服务器啊,有推荐留言下
源码地址 https://github.com/Chenshuai770/test_image3
我是小帅,一起进步啊!

你可能感兴趣的:(细解android之图片下载(无任何框架))