哈哈,不知不觉时间过得真快,上篇博客是写viewpage的处理,其实有些地方是不足的:比如事件分发之类的,暂时先留着,后面再来做详细修改:
之所以来写这篇博客,主要是想把基础知识打扎实,框架用多了固然能给开发带来很多方便的地方,但只知道所以为,不知其然并不是长久之路,所以还是要把基础打好。来写正文了,先来个效果图
在写这些之前有必要把一些知识点来讲讲
首先是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中的“流”分为两种流:
- 字节流:数据流中的最小的数据单元是字节,一次读入读出8位二进制;
- 字符流:数据流中的最小的数据单元是字符,一次读入读出16位二进制,java中的字符是Unicode编码,一个字符占用两个字节。
“流”结构分类
“流”存在与java.io包中,主要包含四种基本的类,
字节流
InputStream、OutputStream
Reader 、 Writer
“流”常用方法
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
我是小帅,一起进步啊!