Android 性能优化(十)图片加载和大图片缓存机制OOM完美解决方案LruCache&DiskLruCache

https://blog.csdn.net/WHB20081815/article/details/68493509https://blog.csdn.net/WHB20081815/article/details/68493509

Android List,Set,Map集合安全 集合区别 并发集合类性能分析

https://blog.csdn.net/WHB20081815/article/details/70291412https://blog.csdn.net/WHB20081815/article/details/70291412

android 图片功能大全

重点:图片压缩,图片计算内存,图片的缓存lru和diskcache原理

1.图片的基本组成(搞定)

2.图片裁剪(搞定)

3.图片选择(搞定)

4.图片压缩(搞定)

5.gif图片加载(搞定)

6.ImageVIew和ImageButton的区别,相关的一些图片对象(搞定)

7.圆角图片(搞定)

8.图片加密(搞定)

9.图片上传(搞定)

10.图片OOM(搞定)

11.相识照片算法和原理(搞定)

13.图像识别(人脸识别搞不懂)

14.图片的适配

15.行业A平台的图片处理组件(搞定。荣耀10处理)

16.相册拍照之后旋转角度有问题(搞定)

17.自定义相机(搞定)

18.著名的图片框架分析和比较(搞定)

19.著名的图片博客(-----传信推荐的)爱歌

20.图片滤镜

21.Android图片优化--使用webp

22.图片缓存原理分析(重点)

23.微信大图片分享解决方案

一整套流程:拍照,选择,裁剪,美化,压缩,加密,上传,显示

Bitmap

  • BMP:高质量绘图 保证原图质量,用于相机等
    BMP格式图片是有一个一个的像素点组成,每一个像素都是一个颜色.而每一个像素显示的颜色用的二进制位也不相同,这个像素位称之为位深,位深越大,表示每一个像素点所用的二进制位越多,显示的图像也就越清晰。
    png:较高质量绘图 体积小,适用于网络传输
    png图片是将bmp图片进行压缩,其压缩格式类似于rar压缩——将相同的byte信息合并表示。png图片可以还原,是无损的压缩方式。
    jpg:良好的绘图质量 体积小,便于传输
    jpg格式图片也是对bmp图片进行压缩,因为眼睛的精度是有限的,jpg利用这一点将很多颜色相近的用同一颜色标识,而对于一大块相同的颜色,则用一个值表示。jpg格式图片不能被还原。

颜色矩阵值(ColorMatrix)

ALPHA_8就是Alpha由8位组成

ARGB_4444就是由4个4位组成即16位

ARGB_8888就是由4个8位组成即32位

RGB_565就是R为5位,G为6位,B为5位共16位

由此可见

  • ALPHA_8——代表8位Alpha位图
  • ARGB_4444——代表16位ARGB位图
  • ARGB_8888——代表32位ARGB位图
  • RGB_565——代表8位RGB位图

位图位数越高代表其可以存储的颜色信息越多,当然图像也就越逼真

如果图片要显示下Android设备上,ImageView最终是要加载Bitmap对象的,就要考虑单个Bitmap对象的内存占用了,如何计算一张图片的加载到内存的占用呢?其实就是所有像素的内存占用总和:
bitmap内存大小 = 图片长度 x 图片宽度 x 单位像素占用的字节数
起决定因素就是最后那个参数了,Bitmap'常见有2种编码方式:ARGB_8888和RGB_565,ARGB_8888每个像素点4个byte,RGB_565是2个byte,一般都采用ARGB_8888这种。那么常见的1080*1920的图片内存占用就是:
1920 x 1080 x 4 = 7.9M

A:透明度

R:红色

G:绿

B:蓝

Bitmap.Config ARGB_4444:每个像素占四位,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位

Bitmap.Config ARGB_8888:每个像素占四位,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位

Bitmap.Config RGB_565:每个像素占四位,即R=5,G=6,B=5,没有透明度,那么一个像素点占5+6+5=16位

Bitmap.Config ALPHA_8:每个像素占四位,只有透明度,没有颜色。

一般情况下我们都是使用的ARGB_8888,由此可知它是最占内存的,因为一个像素占32位,8位=1字节,所以一个像素占4字节的内存。假设有一张480x800的图片,如果格式为ARGB_8888,那么将会占用1500KB的内存。

来源: https://blog.csdn.net/wulongtiantang/article/details/8481077

获取图片的r、g、b三个图像分量的值。

  获取图像r、g、b三个通道的值一般采用移位操作来获得:方法如下

 View Code

四、利用a、r、g、b三个分量合成一个像素值(a为alpha值表示图像透明度)。

首先你得看图片的格式,是几个byte的,如android 中额度bitmap有 ALPHA_8(1个字节)、RGB_565(2个字节)、ARGB_4444(Deprecated 2个字节)、ARGB_8888(4个字节)

通过getPixel(x, y)方法通过坐标拿到我们需要的色值即可,比较简单。

iv_image.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int x = (int) event.getX();
                int y = (int) event.getY();
                if (event.getAction() == MotionEvent.ACTION_UP) {
                    int color = bitmap.getPixel(x, y);
                    // 如果你想做的更细致的话 可以把颜色值的R G B 拿到做响应的处理
                    int r = Color.red(color);
                    int g = Color.green(color);
                    int b = Color.blue(color);
                    int a = Color.alpha(color);
                    Log.i(TAG, "r=" + r + ",g=" + g + ",b=" + b);
                    tv_rgb.setText("a=" + a + ",r=" + r + ",g=" + g + ",b="
                            + b);
                    btnColor.setTextColor(Color.rgb(r, g, b));

Bitmap图片在加载到内存的时候是按照:宽*高*像素点位数来计算的。你可以把图片看成是由width行、height列的矩阵组成,每一个矩阵元素代表一个像素点,每一个像素点都是1byte整数倍的数据,这个数据越大,表示的色彩就越丰富,图片的显示质量就越高。Bitmap中有一个枚举类Config用来配置图片的压缩格式,代表每个像素是用多大的数据来存储的,数值越大能够存储的颜色信息就越多,也就越丰富,显示效果也就越好。Config.ALPHA_8是1 byte,Config.RGB_565和Config.ARGB_4444都是2 bytes,Config.RGB_565没有Alpha值所以多用来配置没有透明度的图片,Config.ARGB_8888是4 bytes,一般图片都是按照这个来配置的。下面是获取配置的代码:

1

2

3

4

5

6

7

8

9

10

11

12

static int getBytesPerPixel(Config config) {

    if (config == Config.ARGB_8888) {

        return 4;

    else if (config == Config.RGB_565) {

        return 2;

    else if (config == Config.ARGB_4444) {

        return 2;

    else if (config == Config.ALPHA_8) {

        return 1;

    }

    return 1;

}

来源: http://www.cnblogs.com/donghuizaixian/p/4424929.html

1、Bitmap使用需要注意哪些问题 ?

  • 参考回答:

    • 要选择合适的图片规格(bitmap类型):通常我们优化Bitmap时,当需要做性能优化或者防止OOM,我们通常会使用RGB_565,因为ALPHA_8只有透明度,显示一般图片没有意义,Bitmap.Config.ARGB_4444显示图片不清楚,Bitmap.Config.ARGB_8888占用内存最多。:

      • ALPHA_8 每个像素占用1byte内存
      • ARGB_4444 每个像素占用2byte内存
      • ARGB_8888 每个像素占用4byte内存(默认)
      • RGB_565 每个像素占用2byte内存
    • 降低采样率:BitmapFactory.Options 参数inSampleSize的使用,先把options.inJustDecodeBounds设为true,只是去读取图片的大小,在拿到图片的大小之后和要显示的大小做比较通过calculateInSampleSize()函数计算inSampleSize的具体值,得到值之后。options.inJustDecodeBounds设为false读图片资源。
    • 复用内存:即通过软引用(内存不够的时候才会回收掉),复用内存块,不需要再重新给这个bitmap申请一块新的内存,避免了一次内存的分配和回收,从而改善了运行效率。
    • 使用recycle()方法及时回收内存
    • 压缩图片

2、一张Bitmap所占内存以及内存占用的计算

  • 参考回答:

    • Bitamp 所占内存大小 = 宽度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一个像素所占的内存字节大小

      • 注:这里inDensity表示目标图片的dpi(放在哪个资源文件夹下),inTargetDensity表示目标屏幕的dpi,所以你可以发现inDensity和inTargetDensity会对Bitmap的宽高进行拉伸,进而改变Bitmap占用内存的大小。
    • 在Bitmap里有两个获取内存占用大小的方法。
      • getByteCount():API12 加入,代表存储 Bitmap 的像素需要的最少内存。
      • getAllocationByteCount():API19 加入,代表在内存中为 Bitmap 分配的内存大小,代替了 getByteCount() 方法。
      • 不复用 Bitmap 时,getByteCount() 和 getAllocationByteCount 返回的结果是一样的。在通过复用 Bitmap 来解码图片时,那么 getByteCount() 表示新解码图片占用内存的大 小,getAllocationByteCount() 表示被复用 Bitmap 真实占用的内存大小

图片裁剪:

自定义图片裁剪

https://blog.csdn.net/chunqiuwei/article/details/78858192/

1.根据图片的大小生成矩形九宫格框

2.判定手指在哪个位置和区域

3.事件处理,移动和缩放,重新绘制矩形框

4.得到的区域进行绘图

//图片裁剪的核心功能

Bitmap.createBitmap(originalBitmap,//原图

cropX,//图片裁剪横坐标开始位置

cropY,//图片裁剪纵坐标开始位置

cropWidth,//要裁剪的宽度

cropHeight);//要裁剪的高度

当裁剪过后怎么拿到裁剪的图片

//获取原图片final Bitmap originalBitmap = ((BitmapDrawable) drawable).getBitmap(); //获取裁剪框x,y的坐标位置final float cropX = (bitmapLeft + Edge.LEFT.getCoordinate()) / scaleX; final float cropY = (bitmapTop + Edge.TOP.getCoordinate()) / scaleY; //计算裁剪框的宽高final float cropWidth = Math.min(Edge.getWidth() / scaleX, originalBitmap.getWidth() - cropX); final float cropHeight = Math.min(Edge.getHeight() / scaleY, originalBitmap.getHeight() - cropY); //生成裁剪框的bitmapreturn Bitmap.createBitmap(originalBitmap, (int) cropX, (int) cropY, (int) cropWidth, (int) cropHeight);

开源库:Cropper

系统裁剪:

1.不过这也带来了一个问题,从Android 4.4开始,在onActivityResult()方法的Intent中所包含的uri不再是file://类型,而是变成了content://类型,这也是为什么在Android 4.4以后调用data.getData.getPath()获取到的结果是无效的。因此,我们必须2.对Android 4.4以上的版本进行特殊的处理:

Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);intent.setType("image/*");intent.putExtra("crop", "true");intent.putExtra("aspectX", 2);intent.putExtra("aspectY", 1);intent.putExtra("outputX", 600);intent.putExtra("outputY", 300);intent.putExtra("scale", true);intent.putExtra("return-data", false);intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());intent.putExtra("noFaceDetection", true); // no face detectionstartActivityForResult(intent, CHOOSE_BIG_PICTURE);

2.Android 7.0之FileProvider(图片裁剪的图片保存权限)

图片选择器:

系统的相册和自定义图片选择器2种

//在这里跳转到手机系统相册里面

Intent intent = new Intent(

Intent.ACTION_PICK,

android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);

startActivityForResult(intent, IMAGE_REQUEST_CODE);

下面说两种方法分别是直接选择相册返回,另外一种为选择相册之后进行裁剪

当做一款APP,需要选择本地图片时,首先考虑的无疑是系统相册,但是Android手机五花八门,再者手机像素的提升,大图无法返回等异常因数,

导致适配机型比较困难,微信、QQ都相继的在自己的APP里集成了图片选择功能,放弃了系统提供的图片选择器,这里仿造QQ做了一个本地图片选择器

https://github.com/LuckSiege/PictureSelector

图片压缩

如果图片要显示下Android设备上,ImageView最终是要加载Bitmap对象的,就要考虑单个Bitmap对象的内存占用了,如何计算一张图片的加载到内存的占用呢?其实就是所有像素的内存占用总和:
bitmap内存大小 = 图片长度 x 图片宽度 x 单位像素占用的字节数
起决定因素就是最后那个参数了,Bitmap'常见有2种编码方式:ARGB_8888和RGB_565,ARGB_8888每个像素点4个byte,RGB_565是2个byte,一般都采用ARGB_8888这种。那么常见的1080*1920的图片内存占用就是:
1920 x 1080 x 4 = 7.9M

压缩原理

从上面可以总结出,图片压缩应该从两个方面入手同时进行:先是降低分辨率,然后降低每个像素的质量也就是内存占用。

Android应用开发中三种常见的图片压缩方法,分别是:质量压缩法、比例压缩法(根据路径获取图片并压缩)和比例压缩法(根据Bitmap图片压缩)。

options.inSampleSize这个属性,他只能是2的N次方,如果算出来是7,Android会取近似值8,以此类推导致这个值不能压缩到目标值。

用Compressor开源库压缩(用矩阵在画布上按照图片的目标尺寸进行绘制)

Matrix scaleMatrix = new Matrix(); scaleMatrix.setScale(ratioX, ratioY, 0, 0); Canvas canvas = new Canvas(scaledBitmap); canvas.setMatrix(scaleMatrix); canvas.drawBitmap(bmp, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG));

Bitmap有个方法 compress(CompressFormat format, int quality, OutputStream stream),quality就是压缩质量传入0-100,数值越小压缩的越厉害。

options和justdecode的用处:不加载到内存得到图片的大小

质量压缩:image.compress();

比例压缩:Bitmap bitmap = BitmapFactory.decodeFile(srcPath,newOpts)

质量压缩,这个只是降低了图片的质量,但是像素是不会减小的(内存不回少)

一、质量压缩法private Bitmap compressImage(Bitmap image) {ByteArrayOutputStream baos = new ByteArrayOutputStream();image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中int options = 100;while ( baos.toByteArray().length / 1024>100) { //循环判断如果压缩后图片是否大于100kb,大于继续压缩baos.reset();//重置baos即清空baosimage.compress(Bitmap.CompressFormat.JPEG, options, baos);//这里压缩options%,把压缩后的数据存放到baos中options -= 10;//每次都减少10}ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把压缩后的数据baos存放到ByteArrayInputStream中Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream数据生成图片return bitmap;}二、图片按比例大小压缩方法(根据路径获取图片并压缩)private Bitmap getimage(String srcPath) {BitmapFactory.Options newOpts = new BitmapFactory.Options();//开始读入图片,此时把options.inJustDecodeBounds 设回true了newOpts.inJustDecodeBounds = true;Bitmap bitmap = BitmapFactory.decodeFile(srcPath,newOpts);//此时返回bm为空newOpts.inJustDecodeBounds = false;int w = newOpts.outWidth;int h = newOpts.outHeight;//现在主流手机比较多是800*480分辨率,所以高和宽我们设置为float hh = 800f;//这里设置高度为800ffloat ww = 480f;//这里设置宽度为480f//缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可int be = 1;//be=1表示不缩放if (w > h && w > ww) {//如果宽度大的话根据宽度固定大小缩放be = (int) (newOpts.outWidth / ww);} else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放be = (int) (newOpts.outHeight / hh);}if (be <= 0)be = 1;newOpts.inSampleSize = be;//设置缩放比例//重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了bitmap = BitmapFactory.decodeFile(srcPath, newOpts);return compressImage(bitmap);//压缩好比例大小后再进行质量压缩}三、图片按比例大小压缩方法(根据Bitmap图片压缩)private Bitmap comp(Bitmap image) {ByteArrayOutputStream baos = new ByteArrayOutputStream();image.compress(Bitmap.CompressFormat.JPEG, 100, baos);if( baos.toByteArray().length / 1024>1024) {//判断如果图片大于1M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出baos.reset();//重置baos即清空baosimage.compress(Bitmap.CompressFormat.JPEG, 50, baos);//这里压缩50%,把压缩后的数据存放到baos中}ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());BitmapFactory.Options newOpts = new BitmapFactory.Options();//开始读入图片,此时把options.inJustDecodeBounds 设回true了newOpts.inJustDecodeBounds = true;Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);newOpts.inJustDecodeBounds = false;int w = newOpts.outWidth;int h = newOpts.outHeight;//现在主流手机比较多是800*480分辨率,所以高和宽我们设置为float hh = 800f;//这里设置高度为800ffloat ww = 480f;//这里设置宽度为480f//缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可int be = 1;//be=1表示不缩放if (w > h && w > ww) {//如果宽度大的话根据宽度固定大小缩放be = (int) (newOpts.outWidth / ww);} else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放be = (int) (newOpts.outHeight / hh);}if (be <= 0)be = 1;newOpts.inSampleSize = be;//设置缩放比例//重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了isBm = new ByteArrayInputStream(baos.toByteArray());bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);return compressImage(bitmap);//压缩好比例大小后再进行质量压缩
}

JNI终极压缩(主要是编码和解码。数据结构层面上)

仿微信终级压缩

1.下载JPEG引擎使用的库---libjpeg库

1.8*8的矩阵

2.每一个小块都有RGB的数据模型。

3.离散余弦变换----傅里叶变换找出低频变量和高频变量

4.量子化---把无用的数据清楚

5.编码=======哈弗曼编码

真正的流程:

图像分成 8*8 小块 –> DCT 变换 –>用量化表对其量化 –>编码生成压缩数据

二、 具体压缩过程

  • 1、将原始图像分为 8 * 8 的小块, 每个 block 里有 64 个像素。

  • 2、 将图像中每个 8 * 8 的 block 进行 DCT 变换。8 * 8 的图象经过 DCT 变换后,其低频分量都集中在左上角,高频分量分布在右下角。由于该低频分量包含了图象的主要信息(如亮度),而高频与之相比,就不那么重要了,所以我们可以忽略高频分量。

  • 3、 利用量化表抑制高频变量。量化操作,就是将某一个值除以量化表中对应的值。由 于量化表左上角的值较小,右上角的值较大,这样就起到了保持低频分量,抑制高 频分量的目的。压缩时候将彩色图像的颜色空间由 RGB 转化为 YUV 格式。其中 Y 分量代表了亮度信息,UV 分量代表了色差信息。相比而言,Y 分量更重要一些。 我们可以对 Y 采用细量化,对 UV 采用粗量化,可进一步提高压缩比。所以量化表 通常有两张,一张是针对 Y 的标准亮度量化表;一张是针对 UV 的标准色彩量化表。

  • 4、 经过量化之后右下角大部分数据变成了 0,左上角为非零数据。这时使用 Z 字型(如 图所示)的顺序来重新排列数据生成一个整数数组,这样 0 就位于数组都后端。找到数组最后一个非零元素,将其后的数据都舍弃,并加上结束标志。

压缩流程

JPEG整个压缩过程基本上也是遵循这个步骤:
        1. 把数据分为“重要部分”和“不重要部分”
        2. 滤掉不重要的部分
        3. 保存

首先创建BitmapFacotry.Options参数,这个参数很重要,因为它的参数配置能够极大的减少你对内存的消耗。

设置参数options.inJustDecodeBounds =true,inJustDecodeBounds参数,置于这个参数什么意思,贴出来google的原文:

/**
         * If set to true, the decoder will return null (no bitmap), but
         * the out... fields will still be set, allowing the caller to query
         * the bitmap without having to allocate the memory for its pixels.
         */

用我的话来说就是在decode的时候不给这个bitmap的像素区分配内存,除了这个区别Bitmap的其他信息你都能获取到。这样就有很大的意义,你既没有消耗内存又拿到了图片的信息,为你下一步图片处理提供帮助。

混合终极方法

(1)原理:三种方式结合使用实现指定图片内存大小,清晰度达到最优。

参考博客:

https://blog.csdn.net/mensheng110/article/details/78953164

最牛逼的压缩方案:

https://github.com/Curzibn/Luban/tree/turbo

https://wetest.qq.com/lab/view/155.html?from=adsout_qqtips_past2&sessionUserType=BFT.PARAMS.198435.TASKID&ADUIN=190144292&ADSESSION=1473040494&ADTAG=CLIENT.QQ.5467_.0&ADPUBNO=26558

gif图片加载:

1.

方式一:使用第三开源框架直接在布局文件中加载gif(效果最好)

底层是c代码写的

  • 基本原理:内部转化为gifdrawable,类似于使用动画一样进行播放,内部使用c++的方法逐帧播放。内部原理还需探究。

2.

方式二:使用Glide加载gif

  • 基本原理:glide内部会识别你是用的是什么类型的图片,如果是gif图片,内部会转变为GifDrawable用于播放。

3.自定义Gif框架

这个自定义的gifview组件核心代码就是读取Gif数据,创建Movie实例,绘制每一帧图片来达到Gif动态效果。这种方式比较直观方便,代码量也少,不过经测试部分Gif图片不能自适应大小,播放速度比实际播放速度快,如果要显示的gif过大,还会出现BOOM的问题。

  • 基本原理:使用Movie类,将图片加载到Movie内,通过不断的绘制,不断的切换时间,达到播放Gif的目的。
public class GifView extends View {
    private Movie mMovie;
    private long mMovieStart = 0;
    public GifView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public void startGif(String path)  {
    //此方法用于加载drawable内的gif图片
//        mMovie = Movie.decodeStream(getResources().openRawResource(R.drawable.test));
        byte[] array = new byte[0];
        try {
        //用于加载本地文件gif图片
            array = streamToBytes(new FileInputStream(path));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        mMovie = Movie.decodeByteArray(array, 0, array.length);
        invalidate();
    }
    //关键代码,原理已经说过
    @Override
    public void onDraw(Canvas canvas) {
        long now = android.os.SystemClock.uptimeMillis();
        if (mMovieStart == 0) {
            mMovieStart = now;
        }
        if (mMovie != null) {
            int dur = mMovie.duration();
            if (dur == 0) {
                dur = 1000;
            }
            int relTime = (int) ((now - mMovieStart) % dur);
            mMovie.setTime(relTime);
            mMovie.draw(canvas, 0, 0);
            invalidate();
        }
    }
    private static byte[] streamToBytes(InputStream is) {
        ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
        byte[] buffer = new byte[1024];
        int len;
        try {
            while ((len = is.read(buffer)) >= 0) {
                os.write(buffer, 0, len);
            }
        } catch (java.io.IOException e) {
        }
        return os.toByteArray();
    }
}  
  • 结论:此方法我测试过,速度很慢,此方法也可以控制gif的播放,可以通过是否重绘进行控制,或者可以使用ValueAnimator ,根据对应的Gif时长生成对应长度的value,实时根据value设置time,用于控制播放暂停等操作,但根本原因加载太慢不推荐使用

图片控件的区别对比: ImageView和ImageButton
1, ImageView和ImageButton在用src指定图片的时候,不设置具体宽高,显示效果一样; 
设置了具体宽高,若宽高不是图片的原始大小,ImageView会根据宽高放大或者缩小,ImageButton会显示原始图片大小

2, ImageView和ImageButton在使用background的时候,效果是一样的

3, ImageView和ImageButton在用选择器的时候,无论是src还是backgroud,都会生效,前提是ImageView需要获取到焦点

3,ImageView和ImageButton在使用点9图的时候,使用src指定点9图,都不会生效; 
使用background指定点9图,都才会生效

补充: 
对于网上的一些资料说ImageButton支持点9图,ImageView不支持点9图,对此感觉这种说法并不严谨

bitmap和Drawable间的区别:

Bitmap - 称作位图,一般位图的文件格式后缀为bmp,当然编码器也有很多如RGB565、RGB888。作为一种逐像素的显示对象执行效率高,但是缺点也很明显存储效率低。我们理解为一种存储对象比较好。
 Drawable - 作为Android平下通用的图形对象,它可以装载常用格式的图像,比如GIF、PNG、JPG,当然也支持BMP,当然还提供一些高级的可视化对象,比如渐变、图形等。
两者间的简单对比:

setImageResource和setImageDrawable区别

ImageView设置图片的方式有很多钟,可以在xml里面写android:src=”@drawable/xxx”,也可以在java代码里面设置。

在java里面的设置方式也有多种,方法包括:setImageResource,setImageDrawable,setImageBitmap

在xml里面设置实际上和在java里面调用setImageResource是一样的,当然xml多了一个解析的过程,放到java代码里调用会稍微好些(实际没什么区别)。

3种设置图片方式的区别:

(1)setImageResource的参数是resId,必须是drawable目录下的资源.另外,在setImageResource方法中有写明了注释

* <pclass="note">This does Bitmap reading and decoding on the UI

* thread, which can cause a latency hiccup.If that's a concern,

* consider using {@link #setImageDrawable(android.graphics.drawable.Drawable)}or

* {@link #setImageBitmap(android.graphics.Bitmap)} and

* {@link android.graphics.BitmapFactory} instead.</p>

这个方法是在UI线程中对图片读取和解析的,所以有可能对一个Activity的启动造成延迟。所以如果顾虑到这个官方建议用setImageDrawable和setImageBitmap来代替。

(2)setImageBitmap参数是Bitmap,可以解析不同来源的图片再进行设置。不过我们看看setImageBitmap的源码:

@android.view.RemotableViewMethod

public void setImageBitmap(Bitmap bm) {

// if this is used frequently, mayhandle bitmaps explicitly

// to reduce the intermediate drawable object

setImageDrawable(newBitmapDrawable(mContext.getResources(), bm));

}

实际上setImageBitmap做的事情就是把Bitmap对象封装成Drawable对象,然后调用setImageDrawable来设置图片。因此代码里面才写上了建议,如果需要频繁调用这个方法的话最好自己封装个固定的Drawable对象,直接调用setImageDrawable,这样可以减少Drawable对象。因为每次调用setImageBitmap方法都会对Bitmap对象new出一个Drawable。

(3)setImageDrawable参数是Drawable,也是可以接受不同来源的图片,方法中所做的事情就是更新ImageView的图片。上面两个方法实际上最后调用的都是setImageDrawable(setImageResource没有直接调用,不过更新的方法与setImageDrawable一样)。

所以综合来看setImageDrawable是最省内存高效的,如果担心图片过大或者图片过多影响内存和加载效率,可以自己解析图片然后通过调用setImageDrawable方法进行设置。

图片的基本转化准备:概念说明

1、Drawable就是一个可画的对象,其可能是一张位图(BitmapDrawable),也可能是一个图形(ShapeDrawable),还有可能是一个图层(LayerDrawable),我们根据画图的需求,创建相应的可画对象

2、Canvas画布,绘图的目的区域,用于绘图

3、Bitmap位图,用于图的处理

4、Matrix矩阵 ,图像矩阵

1.从resources中获取Bitmap

Resources res = getResources();

Bitmap bmp = BitmapFactory.decodeResource(res, R.drawable.icon);

2.Bitmap → byte[]

public byte[] Bitmap2Bytes(Bitmap bm) {

ByteArrayOutputStream baos = new ByteArrayOutputStream();

bm.compress(Bitmap.CompressFormat.PNG, 100, baos);

return baos.toByteArray();

}

3.byte[] → Bitmap

public Bitmap Bytes2Bimap(byte[] b) {

if (b.length != 0) {

return BitmapFactory.decodeByteArray(b, 0, b.length);

} else {

return null;

}

}

4.Bitmap缩放

public static Bitmap zoomBitmap(Bitmap bitmap, int width, int height) {

int w = bitmap.getWidth();

int h = bitmap.getHeight();

Matrix matrix = new Matrix();

float scaleWidth = ((float) width / w);

float scaleHeight = ((float) height / h);

matrix.postScale(scaleWidth, scaleHeight);

Bitmap newbmp = Bitmap.createBitmap(bitmap, 0, 0, w, h, matrix, true);

return newbmp;

}

5.将Drawable转化为Bitmap

public static Bitmap drawableToBitmap(Drawable drawable) {

// 取 drawable 的长宽

int w = drawable.getIntrinsicWidth();

int h = drawable.getIntrinsicHeight();

// 取 drawable 的颜色格式

Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888

: Bitmap.Config.RGB_565;

// 建立对应 bitmap

Bitmap bitmap = Bitmap.createBitmap(w, h, config);

// 建立对应 bitmap 的画布

Canvas canvas = new Canvas(bitmap);

drawable.setBounds(0, 0, w, h);

// 把 drawable 内容画到画布中

drawable.draw(canvas);

return bitmap;

}

6.获得圆角图片

public static Bitmap getRoundedCornerBitmap(Bitmap bitmap, float roundPx) {

int w = bitmap.getWidth();

int h = bitmap.getHeight();

Bitmap output = Bitmap.createBitmap(w, h, Config.ARGB_8888);

Canvas canvas = new Canvas(output);

final int color = 0xff424242;

final Paint paint = new Paint();

final Rect rect = new Rect(0, 0, w, h);

final RectF rectF = new RectF(rect);

paint.setAntiAlias(true);

canvas.drawARGB(0, 0, 0, 0);

paint.setColor(color);

canvas.drawRoundRect(rectF, roundPx, roundPx, paint);

paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));

canvas.drawBitmap(bitmap, rect, rect, paint);

return output;

}

7.Bitmap转换成Drawable

Bitmap bm=xxx; //xxx根据你的情况获取

BitmapDrawable bd= new BitmapDrawable(getResource(), bm);

//因为BtimapDrawable是Drawable的子类,最终直接使用bd对象即可。

8.Drawable缩放

public static Drawable zoomDrawable(Drawable drawable, int w, int h) {

int width = drawable.getIntrinsicWidth();

int height = drawable.getIntrinsicHeight();

// drawable转换成bitmap

Bitmap oldbmp = drawableToBitmap(drawable);

// 创建操作图片用的Matrix对象

Matrix matrix = new Matrix();

// 计算缩放比例

float sx = ((float) w / width);

float sy = ((float) h / height);

// 设置缩放比例

matrix.postScale(sx, sy);

// 建立新的bitmap,其内容是对原bitmap的缩放后的图

Bitmap newbmp = Bitmap.createBitmap(oldbmp, 0, 0, width, height,matrix, true);

return new BitmapDrawable(newbmp);

}

图片的缩放类型:

android:scaleType属性

android:scaleType是控制图片如何resized/moved来匹对ImageView的size。

ImageView.ScaleType / android:scaleType值的意义区别:

CENTER /center  按图片的原来size居中显示,当图片长/宽超过View的长/宽,则截取图片的居中部分显示

CENTER_CROP / centerCrop  按比例扩大图片的size居中显示,使得图片长(宽)等于或大于View的长(宽)

CENTER_INSIDE / centerInside  将图片的内容完整居中显示,通过按比例缩小或原来的size使得图片长/宽等于或小于View的长/宽

FIT_CENTER / fitCenter  把图片按比例扩大/缩小到View的宽度,居中显示

FIT_END / fitEnd   把图片按比例扩大/缩小到View的宽度,显示在View的下部分位置

FIT_START / fitStart  把图片按比例扩大/缩小到View的宽度,显示在View的上部分位置

FIT_XY / fitXY  把图片不按比例扩大/缩小到View的大小显示

MATRIX / matrix 用矩阵来绘制,动态缩小放大图片来显示。

** 要注意一点,Drawable文件夹里面的图片命名是不能大写的

圆角图片

其实实现圆角图片的方法应该很多,常见的就是利用

1.Xfermode

2.Shader

Shader

1.现在大概明白了,BitmapShader通过设置给mPaint,然后用这个mPaint绘图时,就会根据你设置的TileMode,对绘制区域进行着色。

2.拿到drawable转化为bitmap,然后直接初始化BitmapShader,画笔设置Shader,最后在onDraw里面进行画圆就行了。

3.关于scale的计算:

4.状态的存储与恢复

onRestoreInstanceState

原理二:

然后就是设置Xfermode,getBitmap会根据type返回图形,直接绘制到内存中的bitmap上即可。最后把bitmap缓存起来,避免每次onDraw都分配内存和重启绘图。

mPaint.setXfermode(mXfermode)

代码:

  1. //绘制形状

  2. drawCanvas.drawBitmap(mMaskBitmap, 0, 0, mPaint);

  3. mPaint.setXfermode(null);

  4. //将准备好的bitmap绘制出来

  5. canvas.drawBitmap(bitmap, 0, 0, null);

两个tu ceng图层取交集

图片加密

1.百度云盘上传图片是没有加密的,抓包可以看到,现在慢慢改成https

2.android 项目中开发为了节省周期又或者不重复造轮子都会选择一些开源框架来设计我们的app,而imageloader是加载网络本地图片的一大利器,但是其本身并未提供加密和解密的方法,需要我们从源码中自己改写。

原理:

图像加密方案,该方案创新了灰度变换和图像置乱的结合方法,分割成几个小图片,依次解密显示,时间没有变快,用户感受上好很多

加密的方法:4种

1.图片流转到TBytes数组中
对TBytes数组的前面N个字节的后面N个字节进行加密处理
再转回图片流,只加密文件前1K或2K字节,就地写入

2.此加密算法选定了AES加密算法,在网上搜索AES加密实现的时候发现几乎都是对byte[]的加密解密操作,考虑到Android机上对图片做解密操作可能对内存消耗大,尝试找有没有基于Stream的加密解密方式,经过了一番资料查找找到了支持AES加密解密的CipherInputStreamCipherOutputStream借助这两个Stream可以实现将加密的图片文件读取成解密后的Bitmap

2.3国际标准的AES加密,通过加密流存储文件,查看的时候需要解密,这种方法适合用于非要重要的图片加密。

是先由服务器创建RSA密钥对,RSA公钥保存在安卓的so文件里面,服务器保存RSA私钥。而安卓创建AES密钥(这个密钥也是在so文件里面),并用该AES密钥加密待传送的明文数据,同时用接受的RSA公钥加密AES密钥,最后把用RSA公钥加密后的AES密钥同密文一起通过Internet传输发送到服务器。当服务器收到这个被加密的AES密钥和密文后,首先调用服务器保存的RSA私钥,并用该私钥解密加密的AES密钥,得到AES密钥。最后用该AES密钥解密密文得到明文。

(用Retrofit的网络请求框架的,要加密参数和解密服务器传输过来的数据需自定义Converter,推荐http://blog.csdn.net/zr940326/article/details/51549310)
so文件:ndk开发的so,可以存放一些重要的数据,如:密钥、私钥、API_KEy等,不过这里我建议大家使用分段存放,C层(so文件)+String文件(string.xml)+gradle文件,用的时候再拼接合并,还有如上图所示,AES的加密算法是放在C层进行实现的,这样也是最大程度保护我们数据的安全

Q:公钥传输是否有安全问题?(RSA key)
R:固定key,也就是保存在so文件中,理论上不会不安全,当然也可以动态从服务器获取,但传输不安全(前提不是https)

Q:AES key存储在哪里比较好?
R:分段存放,C层(so文件)+String文件(string.xml)+gradle文件;也可以从服务获取

对图片进行处理,在存储文件的时候混入字节,让它查看不了,当我们要查看的时候,我们在读文件的时候在去掉混入的字节就可以了,这种方法适合不是很重要的图片,但是又不希望用户直接在文件管理里能看到。

3.使用setPixel()和getPixel()对每个像素点进行加密,然后在使用的时候在还原

分析:通过Bitmap.getPixel(x, y)得到color值,对color的rgb值加密操作,然后setPixel(x,y,color)

Step1:懒得写了,直接贴代码:

注意:bitmap一定要copy一份,然后第二个值为true才能对其setPixel,不然会报错的;代码中的encrypt和decrypt就是你加密解密过程;

严重问题:对bitmap setPixel然后在getPixel,color值竟然不是set的值,有偏差,不知道为什么。

4.对图片的字节倒叙的方式,或者做简单的与运算

首先,使用java把图片加密;

  1. // 加密后,会在原图片的路径下生成加密后的图片

  2. public static void encrypt(String filePath) {

  3. byte[] tempbytes = new byte[5000];

  4. try {

  5. InputStream in = new FileInputStream(filePath);

  6. OutputStream out = new FileOutputStream(filePath.subSequence(0, filePath.lastIndexOf(".")) + "2.png");

  7. while (in.read(tempbytes) != -1) {// 简单的交换

  8. byte a = tempbytes[0];

  9. tempbytes[0] = tempbytes[1];

  10. tempbytes[1] = a;

  11. out.write(tempbytes);// 写文件

  12. }

  13. } catch (IOException e) {

  14. e.printStackTrace();

  15. }

  16. }

  1. //调用加密方法

  2. KMD.encrypt("D:/connectus.png");

然后把生成的图片放在asets文件夹下;

代码调用;

  1. public static Bitmap getImageFromAssets(Context context, String fileName) {

  2. Bitmap image = null;

  3. AssetManager am = context.getResources().getAssets();

  4. try {

  5. InputStream is = am.open(fileName);

  6. byte[] buffer = new byte[1500000];//足够大

  7. is.read(buffer);

  8. for(int i=0; i<buffer.length; i+= 5000){//与加密相同

  9. byte temp = buffer[i];

  10. buffer[i] = buffer[i+1];

  11. buffer[i+1] = temp;

  12. }

  13. image = BitmapFactory.decodeByteArray(buffer, 0, buffer.length);

  14. if (is!=null){

  15. is.close();

  16. }

  17. } catch (IOException e) {

  18. e.printStackTrace();

  19. }

  20. return image;

  21. }

Bitmap bitmap = getImageFromAssets(this,"to.png")

来源: https://blog.csdn.net/try_zp_catch/article/details/82628229

存在的问题:

1.解密的速度问题

图片上传 多线程的断点续传图片上传    多线程的断点续传 总结:

流程步骤总结:

1.根据线程分片,chunks和chunks 的总量

2.通过线程池完成创建任务

3.上传  Range

4.用数据哭纪录每条现场的下载量

5.文件续写:RomAcceiable

6.取消功能:取消读写流

7.通过md5值判定是否为一个(完整性校验和合法性校验)

多线程下载文件(支持暂停、取消、断点续传)

多线程同时下载文件即:在同一时间内通过多个线程对同一个请求地址发起多个请求,将需要下载的数据分割成多个部分,同时下载,每个线程只负责下载其中的一部分,最后将每一个线程下载的部分组装起来即可。

涉及的知识及问题

  • 请求的数据如何分段
  • 分段完成后如何下载和下载完成后如何组装到一起
  • 暂停下载和继续下载的实现(wait()、notifyAll()、synchronized的使用)
  • 取消下载和断点续传的实现

一、请求的数据如何分段

首先通过HttpURLConnection请求总文件大小,而后根据线程数计算每一个线程的下载量,在分配给每一个线程去下载

fileLength = conn.getContentLength(); //根据文件大小,先创建一个空文件 //“r“——以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。 //“rw“——打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。 //“rws“—— 打开以便读取和写入,对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。 //“rwd“——打开以便读取和写入,对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备。 RandomAccessFile raf = new RandomAccessFile(filePath, "rwd"); raf.setLength(fileLength); raf.close(); //计算各个线程下载的数据段 int blockLength = fileLength / threadCount;

二、分段完成后如何下载和下载完成后如何组装到一起

分段完成后给每一个线程的请求头设置Range参数,他允许客户端只请求文件的一部分数据,每一个线程只请求下载相应范围内的数据,使用RandomAccessFile(可随机读写的文件)写入到同一个文件里即可组装成目标文件Range,是在 HTTP/1.1里新增的一个 header field,它允许客户端实际上只请求文档的一部分(范围可以相互重叠)

Range的使用形式:

属性 解释
bytes=0-499 表示头500个字节
bytes=500-999 表示第二个500字节
bytes=-500 表示最后500个字节
bytes=500- 表示500字节以后的范围
bytes=0-0,-1 第一个和最后一个字节

HttpUrlConnection中设置请求头

URL url = new URL(loadUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition); conn.setConnectTimeout(5000); //若请求头加上Range这个参数,则返回状态码为206,而不是200 if (conn.getResponseCode() == 206) {    InputStream is = conn.getInputStream();    RandomAccessFile raf = new RandomAccessFile(filePath, "rwd");    raf.seek(startPosition);//跳到指定位置开始写数据 }

http协议和ftp协议区别:上传文件

断点续传框架:okhttp和xutils

https://blog.csdn.net/tianzhaoai/article/details/56673071

https://www.cnblogs.com/android-blogs/p/5752431.html

若请求头加上Range这个参数,则返回状态码为206,而不是200

(4)用一个map表来记录每个线程下载的信息,保存到sqlite数据库里面,用于断点续传的实现,当下载中断,可以从map表的位置开始下载

四、取消下载和断点续传的实现

取消下载即取消每个线程的执行,不建议直接使用Thread.stop()方法,安全的取消线程即run方法执行结束。只要控制住循环,就可以让run方法结束,也就是线程结束

while ((len = is.read(buffer)) != -1) {        //是否继续下载        if (!isGoOn)            break;   }

如何取消下载:

不读取输入流!

URL url = new URL(loadUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setRequestMethod("GET");conn.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition);conn.setConnectTimeout(5000);
//若请求头加上Range这个参数,则返回状态码为206,而不是200
            if (conn.getResponseCode() == 206) {InputStream is = conn.getInputStream();RandomAccessFile raf = new RandomAccessFile(filePath, "rwd");raf.seek(startPosition);//跳到指定位置开始写数据
    int len;byte[] buffer = new byte[1024];while ((len = is.read(buffer)) != -1) {//是否继续下载
        if (!isGoOn)

这是java.io.RandomAccessFile类,这个类的实例支持对随机访问文件的读取和写入,这个类里有个很重要的seek(long pos)方法,

这个方法可以在你设置的长度的下一个位置进行写入,例如seek(599),那么系统下一次写入的位置就是该文件的600位置。

断点续传是要从我们之前已经下载好的文件位置继续上一次的传输,顾名思义我们需要告诉服务器我们上一次已经下载到哪里了,所以在我们的HTTP请求头里要额外的包含一条信息,而这也就是断点续传的奥秘所在了。

这条信息为:(RANGE为请求头属性,X为从什么地方开始,Y为到什么地方结束)

RANGE: bytes=X-Y 

例如:

Range : 用于客户端到服务器端的请求,可通过该字段指定下载文件的某一段大小,及其单位。典型的格式如:
Range: bytes=0-499 下载第0-499字节范围的内容
Range: bytes=500-999 下载第500-999字节范围的内容
Range: bytes=-500 下载最后500字节的内容
Range: bytes=500- 下载从第500字节开始到文件结束部分的内容

一、HTTP multipart/form-data——上传报文格式

我们来了解下Android客户端如何通过HTTP协议来将图片上传到服务器。我们先来了解下HTTP multipart/form-data,上传报文格式。

假设接收文件的网页程序是http://172.31.8.6:8080/fileUpload/file_upload。假设我们要发送一个图片文件,文件名为“iamge.jpg”。

首先,客户端成功链接172.31.8.6后,会发送如下的HTTP请求:

POST/http://172.31.8.6:8080/fileUpload/file_upload HTTP/1.1   Accept: text/plain, */*   Accept-Language: zh-cn   Host: 172.31.8.6   Content-Type:multipart/form-data;boundary=-----------------------------7db372eb000e2   User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)   Content-Length: 3693   Connection: Keep-Alive   -------------------------------7db372eb000e2   Content-Disposition: form-data; name="img"; filename="image.jpg"   Content-Type: image/jpeg   (此处省略jpeg文件二进制数据...)   -------------------------------7db372eb000e2--

此内容必须一字不差,包括最后的回车,红色字体部分就是协议的头。给服务器上传数据时,并非协议头每个字段都得说明,其中,content-type是必须的,它包括一个类似标志性质的名为boundary的标志,它可以是随便输入的字符串。对后面的具体内容也是必须的。它用来分辨一段内容的开始。Content-Length: 3693 ,这里的3693是要上传文件的总长度。绿色字体部分就是需要上传的数据,可以是文本,也可以是图片等。数据内容前面需要有Content-Disposition, Content-Type以及Content-Transfer-Encoding等说明字段。最后的紫色部分就是协议的结尾了。

注意这一行:

  Content-Type: multipart/form-data; boundary=---------------------------7db372eb000e2  

  根据 rfc1867, multipart/form-data是必须的.

  ---------------------------7db372eb000e2 是分隔符,分隔多个文件、表单项。其中b372eb000e2 是即时生成的一个数字,用以确保整个分隔符不会在文件或表单项的内容中出现。Form每个部分用分隔符分割,分隔符之前必须加上"--"着两个字符(即--{boundary})才能被http协议认为是Form的分隔符,表示结束的话用在正确的分隔符后面添加"--"表示结束。

  前面的 ---------------------------7d 是 IE 特有的标志,Mozila 为---------------------------71.

  每个分隔的数据的都可以用Content-Type来表示下面数据的类型,可以参考rfc1341(http://www.ietf.org/rfc/rfc1341.txt)

  例如 :Contect-Type:image/jpeg 表示下面的数据是jpeg文件数据。

 URL url = new URL(RequestURL);HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(readTimeOut);
conn.setConnectTimeout(connectTimeout);
conn.setDoInput(true); // 允许输入流
conn.setDoOutput(true); // 允许输出流
conn.setUseCaches(false); // 不允许使用缓存
conn.setRequestMethod("POST"); // 请求方式
conn.setRequestProperty("Charset", CHARSET); // 设置编码
conn.setRequestProperty("connection", "keep-alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
conn.setRequestProperty("Content-Type", CONTENT_TYPE + ";boundary=" + BOUNDARY);/**
  * 当文件不为空,把文件包装并且上传
  */
 DataOutputStream dos = new DataOutputStream(conn.getOutputStream());StringBuffer sb = null;String params = "";/***
  * 以下是用于上传参数
  */
if (param != null && param.size() > 0) {Iterator<String> it = param.keySet().iterator();while (it.hasNext()) {sb = null;sb = new StringBuffer();String key = it.next();String value = param.get(key);sb.append(PREFIX).append(BOUNDARY).append(LINE_END);sb.append("Content-Disposition: form-data; name=\"").append(key).append("\"").append(LINE_END).append(LINE_END);sb.append(value).append(LINE_END);params = sb.toString();Log.i(TAG, key+"="+params+"##");dos.write(params.getBytes());}}sb = null;params = null;sb = new StringBuffer();/**
  * 这里重点注意: name里面的值为服务器端需要key 只有这个key 才可以得到对应的文件
  * filename是文件的名字,包含后缀名的 比如:abc.png
  */
sb.append(PREFIX).append(BOUNDARY).append(LINE_END);
sb.append("Content-Disposition:form-data; name=\"" + fileKey+ "\"; filename=\"" + file.getName() + "\"" + LINE_END);
sb.append("Content-Type:image/pjpeg" + LINE_END); // 这里配置的Content-type很重要的 ,用于服务器端辨别文件的类型的

分片上传的原理:

分片上传是把文件分成若干份,然后向你定义的文件接收端post数据,如果上传的文件大于分片的尺寸,就会进行分片,然后会在post的数据中添加两个form元素chunk和chunks,前者标示当前分片在上传分片中的顺序(从0开始),后者代表总分片数。

为什么要采用分块上传? 
试想一下,如果上传的文件是一个大文件,本来上传时间就相对较久,再加上网络不稳定各种因素影响,容易导致传输中断,用户除了重传整个文件外没有更好的选择。采用分片上传可以很好地解决这个问题。

什么是分片上传? 
分块上传,就是把一个大的文件分成若干块,一块一块的传输。如上面的case, 如果传输中断,仅需重传出错的分片,而不需要重传整个文件,大大减少了重传开销。

2.可以模拟暂停与继续

对于一个http请求,其实并没有暂停功能,要不就是已完成,要不就是已中断。如果不分块,是没法做暂停功能。但是如果采用分块上传,在某个分块上传完了后不自动开始下个分块上传,是不是就实现了暂停功能?

缺点:

分块也会有一定的副作用,本来是一个请求,分块后变成了多个请求,自然会带来网络开销

3.断点续传

有了分块上传,其实我们可以实现更多的功能。试想,如果服务端能够把每个已成功上传的分块都记住,客户端可以快速验证,条件选择跳过某个分块,是不是就实现了断点续传?

那么,断点续传能带来哪些好处?

节省流量,避免上传重复的分块。 
减少用户等待时间。 
可恢复的上传。出现中断,就算浏览器刷新或者是换了台电脑也能恢复到上次中断的位置。 
那么现在最关键的问题是如何标识一个分块了。怎样标识让服务端好入库,同时客户端可以快速的计算出来与服务端验证,换句话说就是,如何去找出分块的唯一ID。

之前尝试过文件名+分块编号,或者再加文件大小,文件最后修改时间什么的,都无法保证分块的唯一性。于是还是直接采用 MD5 的方式来序列化文件内容吧,这样就算是文件不同名,只要内容是一致的也会正确地识别出是同一个分块。

那么现在的逻辑就是,每次分块上传前,根据内容 MD5 序列化,得到一个唯一ID,与服务端验证,如果此分块已经存在于服务端,则直接跳过此分块上传,否则上传此分块,成功后,服务端记下此分块ID。

如是,分块上传是不是具有了秒传的功能?既然分块能秒传,那么整个文件是否也可以秒传?

MD5 的时间花费根本就不算什么。对于类似于百度云,文库这类的产品,在上传一个文件的时候很可能服务端已经存在了此文件,如果多等个几秒钟,能跳过整个文件上传,我觉得是非常划算的

  1. //文件上传的connection的一些必须设置

  2. private void initConnection() throws Exception {

  3. conn = (HttpURLConnection) this.url.openConnection();

  4. conn.setDoOutput(true);

  5. conn.setUseCaches(false);

  6. conn.setConnectTimeout(10000); //连接超时为10秒

  7. conn.setRequestMethod("POST");

  8. conn.setRequestProperty("Content-Type",

  9. "multipart/form-data; boundary=" + boundary);

  10. }

  11. //普通字符串数据

  12. private void writeStringParams() throws Exception {

  13. Set<String> keySet = textParams.keySet();

  14. for (Iterator<String> it = keySet.iterator(); it.hasNext();) {

  15. String name = it.next();

  16. String value = textParams.get(name);

  17. ds.writeBytes("--" + boundary + "\r\n");

  18. ds.writeBytes("Content-Disposition: form-data; name=\"" + name

  19. + "\"\r\n");

  20. ds.writeBytes("\r\n");

  21. ds.writeBytes(encode(value) + "\r\n");

  22. }

  23. }

  24. //文件数据

  25. private void writeFileParams() throws Exception {

  26. Set<String> keySet = fileparams.keySet();

  27. for (Iterator<String> it = keySet.iterator(); it.hasNext();) {

  28. String name = it.next();

  29. File value = fileparams.get(name);

  30. ds.writeBytes("--" + boundary + "\r\n");

  31. ds.writeBytes("Content-Disposition: form-data; name=\"" + name

  32. + "\"; filename=\"" + encode(value.getName()) + "\"\r\n");

  33. ds.writeBytes("Content-Type: " + getContentType(value) + "\r\n");

  34. ds.writeBytes("\r\n");

  35. ds.write(getBytes(value));

  36. ds.writeBytes("\r\n");

  37. }

一、HTTP multipart/form-data——上传报文格式

我们来了解下Android客户端如何通过HTTP协议来将图片上传到服务器。我们先来了解下HTTP multipart/form-data,上传报文格式。

假设接收文件的网页程序是http://172.31.8.6:8080/fileUpload/file_upload。假设我们要发送一个图片文件,文件名为“iamge.jpg”。

首先,客户端成功链接172.31.8.6后,会发送如下的HTTP请求:

POST/http://172.31.8.6:8080/fileUpload/file_upload HTTP/1.1   Accept: text/plain, */*   Accept-Language: zh-cn   Host: 172.31.8.6   Content-Type:multipart/form-data;boundary=-----------------------------7db372eb000e2   User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)   Content-Length: 3693   Connection: Keep-Alive   -------------------------------7db372eb000e2   Content-Disposition: form-data; name="img"; filename="image.jpg"   Content-Type: image/jpeg   (此处省略jpeg文件二进制数据...)   -------------------------------7db372eb000e2--

此内容必须一字不差,包括最后的回车,红色字体部分就是协议的头。给服务器上传数据时,并非协议头每个字段都得说明,其中,content-type是必须的,它包括一个类似标志性质的名为boundary的标志,它可以是随便输入的字符串。对后面的具体内容也是必须的。它用来分辨一段内容的开始。Content-Length: 3693 ,这里的3693是要上传文件的总长度。绿色字体部分就是需要上传的数据,可以是文本,也可以是图片等。数据内容前面需要有Content-Disposition, Content-Type以及Content-Transfer-Encoding等说明字段。最后的紫色部分就是协议的结尾了。

注意这一行:

  Content-Type: multipart/form-data; boundary=---------------------------7db372eb000e2  

  根据 rfc1867, multipart/form-data是必须的.

  ---------------------------7db372eb000e2 是分隔符,分隔多个文件、表单项。其中b372eb000e2 是即时生成的一个数字,用以确保整个分隔符不会在文件或表单项的内容中出现。Form每个部分用分隔符分割,分隔符之前必须加上"--"着两个字符(即--{boundary})才能被http协议认为是Form的分隔符,表示结束的话用在正确的分隔符后面添加"--"表示结束。

  前面的 ---------------------------7d 是 IE 特有的标志,Mozila 为---------------------------71.

  每个分隔的数据的都可以用Content-Type来表示下面数据的类型,可以参考rfc1341(http://www.ietf.org/rfc/rfc1341.txt)

  例如 :Contect-Type:image/jpeg 表示下面的数据是jpeg文件数据。

 URL url = new URL(RequestURL);HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(readTimeOut);
conn.setConnectTimeout(connectTimeout);
conn.setDoInput(true); // 允许输入流
conn.setDoOutput(true); // 允许输出流
conn.setUseCaches(false); // 不允许使用缓存
conn.setRequestMethod("POST"); // 请求方式
conn.setRequestProperty("Charset", CHARSET); // 设置编码
conn.setRequestProperty("connection", "keep-alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
conn.setRequestProperty("Content-Type", CONTENT_TYPE + ";boundary=" + BOUNDARY);/**
  * 当文件不为空,把文件包装并且上传
  */
 DataOutputStream dos = new DataOutputStream(conn.getOutputStream());StringBuffer sb = null;String params = "";/***
  * 以下是用于上传参数
  */
if (param != null && param.size() > 0) {Iterator<String> it = param.keySet().iterator();while (it.hasNext()) {sb = null;sb = new StringBuffer();String key = it.next();String value = param.get(key);sb.append(PREFIX).append(BOUNDARY).append(LINE_END);sb.append("Content-Disposition: form-data; name=\"").append(key).append("\"").append(LINE_END).append(LINE_END);sb.append(value).append(LINE_END);params = sb.toString();Log.i(TAG, key+"="+params+"##");dos.write(params.getBytes());}}sb = null;params = null;sb = new StringBuffer();/**
  * 这里重点注意: name里面的值为服务器端需要key 只有这个key 才可以得到对应的文件
  * filename是文件的名字,包含后缀名的 比如:abc.png
  */
sb.append(PREFIX).append(BOUNDARY).append(LINE_END);
sb.append("Content-Disposition:form-data; name=\"" + fileKey+ "\"; filename=\"" + file.getName() + "\"" + LINE_END);
sb.append("Content-Type:image/pjpeg" + LINE_END); // 这里配置的Content-type很重要的 ,用于服务器端辨别文件的类型的
一、HTTP multipart/form-data——上传报文格式

我们来了解下Android客户端如何通过HTTP协议来将图片上传到服务器。我们先来了解下HTTP multipart/form-data,上传报文格式。

假设接收文件的网页程序是http://172.31.8.6:8080/fileUpload/file_upload。假设我们要发送一个图片文件,文件名为“iamge.jpg”。

首先,客户端成功链接172.31.8.6后,会发送如下的HTTP请求:

POST/http://172.31.8.6:8080/fileUpload/file_upload HTTP/1.1   Accept: text/plain, */*   Accept-Language: zh-cn   Host: 172.31.8.6   Content-Type:multipart/form-data;boundary=-----------------------------7db372eb000e2   User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)   Content-Length: 3693   Connection: Keep-Alive   -------------------------------7db372eb000e2   Content-Disposition: form-data; name="img"; filename="image.jpg"   Content-Type: image/jpeg   (此处省略jpeg文件二进制数据...)   -------------------------------7db372eb000e2--

此内容必须一字不差,包括最后的回车,红色字体部分就是协议的头。给服务器上传数据时,并非协议头每个字段都得说明,其中,content-type是必须的,它包括一个类似标志性质的名为boundary的标志,它可以是随便输入的字符串。对后面的具体内容也是必须的。它用来分辨一段内容的开始。Content-Length: 3693 ,这里的3693是要上传文件的总长度。绿色字体部分就是需要上传的数据,可以是文本,也可以是图片等。数据内容前面需要有Content-Disposition, Content-Type以及Content-Transfer-Encoding等说明字段。最后的紫色部分就是协议的结尾了。

注意这一行:

  Content-Type: multipart/form-data; boundary=---------------------------7db372eb000e2  

  根据 rfc1867, multipart/form-data是必须的.

  ---------------------------7db372eb000e2 是分隔符,分隔多个文件、表单项。其中b372eb000e2 是即时生成的一个数字,用以确保整个分隔符不会在文件或表单项的内容中出现。Form每个部分用分隔符分割,分隔符之前必须加上"--"着两个字符(即--{boundary})才能被http协议认为是Form的分隔符,表示结束的话用在正确的分隔符后面添加"--"表示结束。

  前面的 ---------------------------7d 是 IE 特有的标志,Mozila 为---------------------------71.

  每个分隔的数据的都可以用Content-Type来表示下面数据的类型,可以参考rfc1341(http://www.ietf.org/rfc/rfc1341.txt)

  例如 :Contect-Type:image/jpeg 表示下面的数据是jpeg文件数据。

 URL url = new URL(RequestURL);HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(readTimeOut);
conn.setConnectTimeout(connectTimeout);
conn.setDoInput(true); // 允许输入流
conn.setDoOutput(true); // 允许输出流
conn.setUseCaches(false); // 不允许使用缓存
conn.setRequestMethod("POST"); // 请求方式
conn.setRequestProperty("Charset", CHARSET); // 设置编码
conn.setRequestProperty("connection", "keep-alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
conn.setRequestProperty("Content-Type", CONTENT_TYPE + ";boundary=" + BOUNDARY);/**
  * 当文件不为空,把文件包装并且上传
  */
 DataOutputStream dos = new DataOutputStream(conn.getOutputStream());StringBuffer sb = null;String params = "";/***
  * 以下是用于上传参数
  */
if (param != null && param.size() > 0) {Iterator<String> it = param.keySet().iterator();while (it.hasNext()) {sb = null;sb = new StringBuffer();String key = it.next();String value = param.get(key);sb.append(PREFIX).append(BOUNDARY).append(LINE_END);sb.append("Content-Disposition: form-data; name=\"").append(key).append("\"").append(LINE_END).append(LINE_END);sb.append(value).append(LINE_END);params = sb.toString();Log.i(TAG, key+"="+params+"##");dos.write(params.getBytes());}}sb = null;params = null;sb = new StringBuffer();/**
  * 这里重点注意: name里面的值为服务器端需要key 只有这个key 才可以得到对应的文件
  * filename是文件的名字,包含后缀名的 比如:abc.png
  */
sb.append(PREFIX).append(BOUNDARY).append(LINE_END);
sb.append("Content-Disposition:form-data; name=\"" + fileKey+ "\"; filename=\"" + file.getName() + "\"" + LINE_END);
sb.append("Content-Type:image/pjpeg" + LINE_END); // 这里配置的Content-type很重要的 ,用于服务器端辨别文件的类型的

一、HTTP multipart/form-data——上传报文格式

我们来了解下Android客户端如何通过HTTP协议来将图片上传到服务器。我们先来了解下HTTP multipart/form-data,上传报文格式。

假设接收文件的网页程序是http://172.31.8.6:8080/fileUpload/file_upload。假设我们要发送一个图片文件,文件名为“iamge.jpg”。

首先,客户端成功链接172.31.8.6后,会发送如下的HTTP请求:

此内容必须一字不差,包括最后的回车,红色字体部分就是协议的头。给服务器上传数据时,并非协议头每个字段都得说明,其中,content-type是必须的,它包括一个类似标志性质的名为boundary的标志,它可以是随便输入的字符串。对后面的具体内容也是必须的。它用来分辨一段内容的开始。Content-Length: 3693 ,这里的3693是要上传文件的总长度。绿色字体部分就是需要上传的数据,可以是文本,也可以是图片等。数据内容前面需要有Content-Disposition, Content-Type以及Content-Transfer-Encoding等说明字段。最后的紫色部分就是协议的结尾了。

注意这一行:

  Content-Type: multipart/form-data; boundary=---------------------------7db372eb000e2  

  根据 rfc1867, multipart/form-data是必须的.

  ---------------------------7db372eb000e2 是分隔符,分隔多个文件、表单项。其中b372eb000e2 是即时生成的一个数字,用以确保整个分隔符不会在文件或表单项的内容中出现。Form每个部分用分隔符分割,分隔符之前必须加上"--"着两个字符(即--{boundary})才能被http协议认为是Form的分隔符,表示结束的话用在正确的分隔符后面添加"--"表示结束。

  前面的 ---------------------------7d 是 IE 特有的标志,Mozila 为---------------------------71.

  每个分隔的数据的都可以用Content-Type来表示下面数据的类型,可以参考rfc1341(http://www.ietf.org/rfc/rfc1341.txt)

  例如 :Contect-Type:image/jpeg 表示下面的数据是jpeg文件数据。

 URL url = new URL(RequestURL);HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(readTimeOut);
conn.setConnectTimeout(connectTimeout);
conn.setDoInput(true); // 允许输入流
conn.setDoOutput(true); // 允许输出流
conn.setUseCaches(false); // 不允许使用缓存
conn.setRequestMethod("POST"); // 请求方式
conn.setRequestProperty("Charset", CHARSET); // 设置编码
conn.setRequestProperty("connection", "keep-alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
conn.setRequestProperty("Content-Type", CONTENT_TYPE + ";boundary=" + BOUNDARY);/**
  * 当文件不为空,把文件包装并且上传
  */
 DataOutputStream dos = new DataOutputStream(conn.getOutputStream());StringBuffer sb = null;String params = "";/***
  * 以下是用于上传参数
  */
if (param != null && param.size() > 0) {Iterator<String> it = param.keySet().iterator();while (it.hasNext()) {sb = null;sb = new StringBuffer();String key = it.next();String value = param.get(key);sb.append(PREFIX).append(BOUNDARY).append(LINE_END);sb.append("Content-Disposition: form-data; name=\"").append(key).append("\"").append(LINE_END).append(LINE_END);sb.append(value).append(LINE_END);params = sb.toString();Log.i(TAG, key+"="+params+"##");dos.write(params.getBytes());}}sb = null;params = null;sb = new StringBuffer();/**
  * 这里重点注意: name里面的值为服务器端需要key 只有这个key 才可以得到对应的文件
  * filename是文件的名字,包含后缀名的 比如:abc.png
  */
sb.append(PREFIX).append(BOUNDARY).append(LINE_END);
sb.append("Content-Disposition:form-data; name=\"" + fileKey+ "\"; filename=\"" + file.getName() + "\"" + LINE_END);
sb.append("Content-Type:image/pjpeg" + LINE_END); // 这里配置的Content-type很重要的 ,用于服务器端辨别文件的类型的

图片oom(批量的图片和超大图片)图片的内存优化

1.

方法1:读取图片时注意方法的调用,适当压缩 。尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decode

options

2.默认为ARGB_8888: 每个像素4字节. 共32位。

Alpha_8: 只保存透明度,共8位,1字节。

ARGB_4444: 共16位,2字节。

RGB_565:共16位,2字节。

如果不需要透明度,可把默认值ARGB_8888改为RGB_565,节约一半内存。

3.在适当的时候及时回收图片占用的内存  通常Activity或者Fragment在onStop/onDestroy时候就可以释放图片资源:

oldBitmap.recycle();

4.图片压缩

5.缓存机制

Android 高清加载巨图方案 拒绝压缩图片

BitmapRegionDecoder

那么就看一下,怎么用这个类去加载超规格的图片。
主要有四步:
① 使用BitmapRegionDecoder的newInstance(...)获得一个解码对象decoder;
② 生成一个Rect类的对象rect,这个矩形对象就是设置好要解码的区域(当然长宽不能大于4096,实际上就算设置了大于4096的数值,最后也是会不能加载的);
③ decoder调用decodeRegion(...)将rect对象转化为bitmap对象;
④ 控件加载bitmap。

相识照片

参考博客:

https://blog.csdn.net/u010652002/article/details/72722198

https://blog.csdn.net/gundumw100/article/details/70001422

算法

1.缩小尺寸。

将图片缩小到8x8的尺寸,总共64个像素。这一步的作用是去除图片的细节,只保留结构、明暗等基本信息,摒弃不同尺寸、比例带来的图片差异。

2.简化色彩。

将缩小后的图片,转为64级灰度。也就是说,所有像素点总共只有64种颜色。

3.计算平均值。

计算所有64个像素的灰度平均值。

getPixels()函数把一张图片,从指定的偏移位置(offset),指定的位置(x,y)截取指定的宽高(width,height ),把所得图像的每个像素颜色转为int值,存入pixels

4.比较像素的灰度。

将每个像素的灰度,与平均值进行比较。大于或等于平均值,记为1;小于平均值,记为0。

5.计算哈希值。 (得到图片的指纹)

将上一步的比较结果,组合在一起,就构成了一个64位的整数,这就是这张图片的指纹。组合的次序并不重要,只要保证所有图片都采用同样次序就行了。

6.对比指纹 (把图片的指纹进行比较,有一个阀值 )

static void diff(String s1, String s2) {

char[] s1s = s1.toCharArray();

char[] s2s = s2.toCharArray();

int diffNum = 0;

for (int i = 0; i<s1s.length; i++) {

if (s1s[i] != s2s[i]) {

diffNum++;

}

}

System.out.println("diffNum="+diffNum);

}

看看64位中有多少位是不一样的。在理论上,这等同于计算”汉明距离”(Hamming distance)。如果不相同的数据位不超过5,就说明两张图片很相似;如果大于10,就说明这是两张不同的图片。

实现关键点

计算灰阶

  1. 实现关键点 
  2. 计算灰阶 

    汉明距离

    最终指纹其实是 0101 的二进制数字,举例

    111000 
    111111

    那么这两个数字的汉明距离,其实就是 ^ 运算后 1 的个数。 

    1. 问题:旋转的图片可以识别出来吗?

Android OpenCV图像识别和图像追踪 ocr 识别 和人脸识别(待开发)

图片适配:

一张图片放到hdip里面和放到xhdip里面,手机是xhip的?查找方式是怎么样的?

https://www.cnblogs.com/Sharley/p/5852133.htmlhttps://www.cnblogs.com/Sharley/p/5852133.html

网上的答案都不一样的?

会首先检查与设备相同的文件夹,如果没有会向下寻找,当找到时系统会认为这个图片是小的,而所需的图片会比这个大,就会自动放大这个图片,从而使用更多的内存,那么有的同学会问,小的不行大的行不行呢?这样的同学呢,只要再想一下就会发现系统会自动放大那就一定会自动缩小喽,按这个道理当系统在xxxhdpi中发现后,会 缩小图片从而内存会大雨27.76M,那事实呢,我们来验证一下吧:

-drawable-xxxhdpi 对用的内存 34.17 M

首先解释一下图片为什么会被放大,当我们使用资源id来去引用一张图片时,Android会使用一些规则来去帮我们匹配最适合的图片。什么叫最适合的图片?比如我的手机屏幕密度是xxhdpi,那么drawable-xxhdpi文件夹下的图片就是最适合的图片。因此,当我引用android_logo这张图时,如果drawable-xxhdpi文件夹下有这张图就会优先被使用,在这种情况下,图片是不会被缩放的。但是,如果drawable-xxhdpi文件夹下没有这张图时, 系统就会自动去其它文件夹下找这张图了,优先会去更高密度的文件夹下找这张图片,我们当前的场景就是drawable-xxxhdpi文件夹,然后发现这里也没有android_logo这张图,接下来会尝试再找更高密度的文件夹,发现没有更高密度的了,这个时候会去drawable-nodpi文件夹找这张图,发现也没有,那么就会去更低密度的文件夹下面找,依次是drawable-xhdpi -> drawable-hdpi -> drawable-mdpi -> drawable-ldpi。 
总体匹配规则就是这样,那么比如说现在终于在drawable-mdpi文件夹下面找到android_logo这张图了,但是系统会认为你这张图是专门为低密度的设备所设计的,如果直接将这张图在当前的高密度设备上使用就有可能会出现像素过低的情况,于是系统自动帮我们做了这样一个放大操作。

android 图片旋转

部分三星手机拍完照片后保存的图片是旋转90度后的图片

在部分Android手机(如MT788、Note2)上,使用Camera拍照以后,得到的照片会被自动旋转(90°、180°、270°),这个情况很不符合预期。

原理:ExifInterface类读取图片中相机方向信息。

1.这个显然是android手机的兼容性问题,网上也有很大类似的网友提出此类问题,但给出的解决方法大都是先通过ExifInterface类来读取bitmap的旋转角度,然后根据旋转角度旋转bitmap实现,我也用这个方法检验过,但实际上此方法并不靠谱,原因是读取bitmap的角度始终为0,即使我图片在拍照后出现的旋转了90度,也是读取不了的。后来找一篇文章:http://www.xuebuyuan.com/1223069.html 该文章介绍了2.ExifInterface类在某些机型的有兼容性问题,并指出了问题产生的原因,我采用,拍照完成后,强制调用setPictureDegreeZero方法(具体实现参考下面源码)来重新将图片的旋转角度设置为0,终于以次方解决了问题。终于松了口气,啊哈哈。

图片的操作:复制,旋转,放大,移动,透明度

复制图像


ImageView iv = (ImageView) findViewById(R.id.iv);
/***  复制图片:作用*  在Android中,直接从资源文件加载到的图片是不能进行操作的,只能进行显示*  想要进行操作,可以复制一张图片到内存,然后操作复制到的图片* */
//加载原图
Bitmap bitmap = BitmapFactory.decodeFile("/mnt/sdcard/1.jpg");
//搞一个一样大小一样样式的复制图
Bitmap copybm = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
//获取复制图的画布
Canvas canvas = new Canvas(copybm);
//获取一个画笔,设置颜色
Paint paint = new Paint();
paint.setColor(Color.RED);
//向画布绘制,绘制原图内容
canvas.drawBitmap(bitmap, new Matrix(), paint);
//canvas.drawPoint(10, 10, paint); 向指定位置画一个点
iv.setImageBitmap(copybm);

图片旋转


ImageView iv = (ImageView) findViewById(R.id.iv);
/***     图片旋转:*Android中原图是不能进行操作的,必须要先复制一张图到内存,然后再操作*旋转是在绘制过程中进行的* */
//加载原图
Bitmap bitmap = BitmapFactory.decodeFile("/mnt/sdcard/1.jpg");
//搞一个一样大小一样样式的复制图
Bitmap copybm = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
//获取复制图的画布
Canvas canvas = new Canvas(copybm);
//获取一个画笔,设置颜色
Paint paint = new Paint();
paint.setColor(Color.RED);
//设置图片绘制角度——设置矩阵
Matrix matrix = new Matrix();/**
matrix.setValues(new float[]{//这是矩阵的默认值
1.5f,0,0,
0,1,0,
0,0,1
});
而旋转其实是将每个点坐标和sinx  cosx进行计算...*/
//安卓提供了便捷方法
matrix.setRotate(30,bitmap.getWidth()/2,bitmap.getHeight()/2);
//向画布绘制,绘制原图内容
canvas.drawBitmap(bitmap, matrix, paint);
//canvas.drawPoint(10, 10, paint); 向指定位置画一个点
iv.setImageBitmap(copybm);

改变图片大小和位置


ImageView iv = (ImageView) findViewById(R.id.iv);
//加载原图
Bitmap bitmap = BitmapFactory.decodeFile("/mnt/sdcard/1.jpg");
//搞一个一样大小一样样式的复制图
Bitmap copybm = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
//获取复制图的画布
Canvas canvas = new Canvas(copybm);
//获取一个画笔,设置颜色
Paint paint = new Paint();
paint.setColor(Color.RED);
//设置图片绘制角度——设置矩阵
Matrix matrix = new Matrix();
matrix.setValues(new float[]{//这是矩阵的默认值
1,0,0,
0,1,0,
0,0,1
});
/**
位置矩阵计算公式(以默认值为例,计算x、y、z轴的值):
x = 1x+0y+0z;
y = 0x+1y+0z;
z = 0x+0y+1z;
通过改变矩阵值可以修改图片
//图像的缩放也可以使用Android中自带的方法进行设置
matrix.setScale(0.5f, 0.5f);*/
//向画布绘制,绘制原图内容
canvas.drawBitmap(bitmap, matrix, paint);
iv.setImageBitmap(copybm);

镜像


Matrix matrix = new Matrix();
matrix.setValues(new float[]{//这是矩阵的默认值
-1,0,0,
0,1,0,
0,0,1
});
//镜像完还要平移回来
matrix.postTranslate(bitmap.getWidth(), 0);
//向画布绘制,绘制原图内容
canvas.drawBitmap(bitmap, matrix, paint);

倒影


Matrix matrix = new Matrix();
matrix.setValues(new float[]{//这是矩阵的默认值
1,0,0,
0,-1,0,
0,0,1
});
//镜像完还要平移回来
matrix.postTranslate(0, bitmap.getHeight());
//向画布绘制,绘制原图内容
canvas.drawBitmap(bitmap, matrix, paint);
iv.setImageBitmap(copybm);

颜色处理


//加载原图
Bitmap bitmap = BitmapFactory.decodeFile("/mnt/sdcard/1.jpg");
//搞一个一样大小一样样式的复制图
Bitmap copybm = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
//获取复制图的画布
Canvas canvas = new Canvas(copybm);
//获取一个画笔,设置颜色
Paint paint = new Paint();
paint.setColor(Color.RED);
ColorMatrix cm = new ColorMatrix();
cm.set(//默认颜色矩阵,通过修改rgba来对图片颜色进行处理
new float[]{
1,0,0,0,0,
0,1,0,0,0,
0,0,1,0,0,
0,0,0,1,0,
}
);
/*
颜色矩阵计算公式:
red   = 1*128 + 0*128 + 0*128 + 0*0 +0
blue  = 0*128 + 1*128 + 0*128 + 0*0 +0
green = 0*128 + 0*128 + 1*128 + 0*0 +0
alpha = 0*128 + 0*128 + 0*128 + 1*0 +0  透明度
*/
paint.setColorFilter(new ColorMatrixColorFilter(cm));
canvas.drawBitmap(bitmap,new Matrix(), paint);
iv.setImageBitmap(copybm);

图片放大等等处理

http://www.cnblogs.com/plokmju/p/3212558.html

android 自定义相机 身份证拍照(相机的适配)

https://www.cnblogs.com/dongweiq/p/6273670.html

8.0相机批照问题

https://blog.csdn.net/wufeng55/article/details/80918749

里面有个权限:

https://blog.csdn.net/zz110753/article/details/60877594

1.Camera类的时候发现居然被弃用了,API 21中出现了camera2这个类来代替camera类

2.预览:

了解了View的作用,我想有人会问了:为什么不使用View,而用SurfaceView,首先在这里我想说用View这是可以的,但是用SurfaceView会更好,SurfaceView中Google为摄像等方法重写了很多方法,而且SurfaceView类是在一个新起的单独线程中重新绘制画面,而View是在UI线程上绘制画面

3.拍照

takePicture (Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg)

4.前后摄像头切换

//打开当前选中的摄像头
mCamera = Camera.open(facing);

5.自动对焦(

)autoFocus

在实现无力的情况下,打开了其他已经实现自定义相机而且能够完美对焦的app,一番操作后,发现很多app都是在我移动手机或者有轻微晃动才进行了第二次对焦,等等,这不就是基于传感器实现的吗?

我们完全可以判断手机的运动状态啊,比如静止和移动。在移动一定时间后去对焦。

具体原理是根据传感器来判断手机的运动状态,如果手机从静止状态变成运行状态后再次进入静止状态,此时就是手机的对焦时机。 通过传感器方式来触发对焦,可以兼容几乎所有拥有传感器的手机的对焦问题。

6.传感器

sensorManager = (SensorManager) getContext().getSystemService( 
Context.SENSOR_SERVICE); 
//获得传感器控制器[SensorManager] 
//getContext()获得环境 
//getSystemService(Context.SENSOR_SERVICE);获得系统服务(传感器)

sensorManager.registerListener(this, 
sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION), 
SensorManager.SENSOR_DELAY_NORMAL); 
//注册传感器监听器 
//(监听器、监听内容、监听精度) 
//监听器 既 监听内容发生变化的回调函数 
//监听内容 
Android提供很很多种传感器 传入监听的具体哪一种传感器 
//监听精度 多久监听一次

传感器监听器 
实现SensorEventListener接口 
复写下面方法 
@Override 
public void onAccuracyChanged(Sensor sensor, int accuracy) { 
// TODO Auto-generated method stub

}

@Override 
public void onSensorChanged(SensorEvent event) { 
// TODO Auto-generated method stub

7.录像:录音和图片绑定

mMediaRecorder.setCamera(mCamera);

mMediaRecorder = new MediaRecorder();

问题:

相机拍照:调用系统的相机不支持传多少分辨率拍照大小

1.拍身份证

2.仿微信图片

3.美颜相机

view和surfaceView

了解了View的作用,我想有人会问了:为什么不使用View,而用SurfaceView,首先在这里我想说用View这是可以的,但是用SurfaceView会更好,SurfaceView中Google为摄像等方法重写了很多方法,而且SurfaceView类是在一个新起的单独线程中重新绘制画面,而View是在UI线程上绘制画面,可以想象,如果你用View来预览图像(当然你必须要重写View中的大量方法来实现预览,这是我们不愿意看到的),那么在摄像的时候你就什么都别想做了,因为如果你打算更新UI的话,线程就可能会阻塞,你的APP就可能未响应了,Android系统就自动提示关闭了,这是用户极其不好的体验,而我们希望在摄像的也能更新UI,所以我们用SurfaceView类来预览图像。

  1. 图片分享异常的办法

  2. 微信的返回,第三方没有回掉

  3. 判断是否超过300k,超过300k主动给它压缩

微信不能分享图片的问题是安卓系统的限制,不是微信限制的

传输大突破会报 TransactionTooLargeException,

JavaBinder: !!! FAILED BINDER TRANSACTION !!!

微信图片分享的原理大概是这样的:

我们的App和微信进行进程间的通信,采用binder机制

传输图片封装成bundle给了微信。数据太大就会报系统异常

结论:

经过测试官方的微信demo

300-500k的图片不能直接分享,必须压缩或者缩小

android 美颜相机和openGL 还有setXfermode

https://github.com/wuhaoyu1990/MagicCamera

java弱引用(WeakReference)和SoftReference的区别以及在android内存处理的作用

weakReference一般用来防止内存泄漏,要保证内存被VM回收

softReference的话,好像多用作来实现cache机制.

只有当内存不够的时候才回收这类内存,因此在内存足够的时候,他们通常不被回收。另外,这些引用对象还能保证在Java  抛出OutOfMemory异常之前,被设置为null。

Java从JDK1.2之后就将对象的引用分为4个级别:强引用、软引用、弱引用、虚引用。具体的概念不详述了。

软引用:当内存空间足够的时候,GC就不会回收它,内存空间不足了,就会回收。

弱引用:GC工作过程中,一旦发现了弱引用对象,不管内存足够与否,都会回收它的内存。

=>只具有弱引用的对象拥有更短暂的生命周期,可能随时被回收。

两层缓存,内存缓存+磁盘缓存;也就是Lrucache + DiskCache.

Bitmap在decode的时候申请的内存如何复用,释放时机

https://blog.csdn.net/sted_zxz/article/details/78262671

2、Bitmap.recycle()会立即回收么?什么时候会回收?如果没有地方使用这个Bitmap,为什么垃圾回收不会直接回收?

  • 参考回答:

    • 通过源码可以了解到,加载Bitmap到内存里以后,是包含两部分内存区域的。简单的说,一部分是Java部分的,一部分是C部分的。这个Bitmap对象是由Java部分分配的,不用的时候系统就会自动回收了
    • 但是那个对应的C可用的内存区域,虚拟机是不能直接回收的,这个只能调用底层的功能释放。所以需要调用recycle()方法来释放C部分的内存
    • bitmap.recycle()方法用于回收该Bitmap所占用的内存,接着将bitmap置空,最后使用System.gc()调用一下系统的垃圾回收器进行回收,调用System.gc()并不能保证立即开始进行回收过程,而只是为了加快回收的到来。

4、Android中缓存更新策略 ?

  • 参考回答:

    • Android的缓存更新策略没有统一的标准,一般来说,缓存策略主要包含缓存的添加、获取和删除这三类操作,但不管是内存缓存还是存储设备缓存,它们的缓存容量是有限制的,因此删除一些旧缓存并添加新缓存,如何定义缓存的新旧这就是一种策略,不同的策略就对应着不同的缓存算法
    • 比如可以简单地根据文件的最后修改时间来定义缓存的新旧,当缓存满时就将最后修改时间较早的缓存移除,这就是一种缓存算法,但不算很完美

5、LRU的原理 ?

  • 参考回答:

    • 为减少流量消耗,可采用缓存策略。常用的缓存算法是LRU(Least Recently Used):当缓存满时, 会优先淘汰那些近期最少使用的缓存对象。主要是两种方式:
    • LruCache(内存缓存):LruCache类是一个线程安全的泛型类:内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象,并提供get和put方法来完成缓存的获取和添加操作,当缓存满时会移除较早使用的缓存对象,再添加新的缓存对象。
    • DiskLruCache(磁盘缓存): 通过将缓存对象写入文件系统从而实现缓存效果

性能优化

1、图片的三级缓存中,图片加载到内存中,如果内存快爆了,会发生什么?怎么处理?

  • 参考回答:

    • 首先我们要清楚图片的三级缓存是如何的如果内存足够时不回收。内存不够时就回收软引用对象

2、内存中如果加载一张500*500的png高清图片.应该是占用多少的内存?

  • 参考回答:

    • 不考虑屏幕比的话:占用内存=500 * 500 * 4 = 1000000B ≈ 0.95MB
    • 考虑屏幕比的的话:占用内存= 宽度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一个像素所占的内存字节大小
    • inDensity表示目标图片的dpi(放在哪个资源文件夹下),inTargetDensity表示目标屏幕的dpi

作者:Android的后花园
链接:https://juejin.im/post/5c85cead5188257c6703af47
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Android vivo美颜相机相册 照亮你的美 图片压缩 图片滤镜,裁剪(附带完整项目源码)相关推荐

  1. android课程表控件、悬浮窗、Todo应用、MVP框架、Kotlin完整项目源码

    Android精选源码 Android游戏2048 MVP Kotlin项目(RxJava+Rerotfit+OkHttp+Glide) Android基于自定义Span的富文本编辑器 android ...

  2. 为什么Flutter是跨平台开发的终极之选,android完整项目源码

    它完全免费,彻底开源 可以用来更快地创建应用 出色的用户界面(UI) 节省代码量 可接入平台原生功能 最适合 MVP 开发(最小化可行产品) 较老的设备也使用相同 UI 运行应用 减少测试工作量 更丰 ...

  3. 白皮书 | 国内首份《Android 全埋点技术白皮书》开源所有项目源码!

    随着大数据行业的快速发展,越来越多的人们意识到--数据基础夯实与否,取决于数据的采集方式. 目前,国内大数据埋点方式多样,按照埋点位置不同,可以分为前端(客户端)埋点与后端(服务器端)埋点.其中全埋点 ...

  4. 重磅!《Android 全埋点技术白皮书》开源所有项目源码!

    随着大数据行业的快速发展,越来越多的人们意识到--数据基础夯实与否,取决于数据的采集方式. 目前,国内大数据埋点方式多样,按照埋点位置不同,可以分为前端(客户端)埋点与后端(服务器端)埋点.其中全埋点 ...

  5. Android项目源码分享

    ├─android web应用 │      jqmDemo_static.zip │      jqmMobileDemo-master.zip │      jqmMobileDemo1_1-ma ...

  6. 140款Android开源优秀项目源码

    140款Android开源优秀项目源码 BeautifulRefreshLayout-漂亮的美食下拉刷新 https://github.com/android-cjj/BeautifulRefresh ...

  7. 已上线运营的斗地主APP项目源码,出售(超低价)----(Android商业项目客户端)

    已上线运营的斗地主APP商业项目源码,低价售于热爱学习的同行.您只需支付167元,即可拥有这套商业源码,提高您的技术开发能力.增色您的项目经历.增涨您的工资收入. 您可以运行此商业项目APK,尝试客户 ...

  8. android 快传 源码_最新安卓仿茄子快传APP源码包括服务端源码Android开发快传类项目源码全套...

    适使用范围:  安卓APP茄子快传源码 Android项目源码相似茄子快传的快传项目包括服务端 运行环境:  Android app 源码详细: 本项目是一个基于安卓的相似茄子快传的项目源码,包括安卓 ...

  9. 网上的很多Android项目源码有用吗?Android开发注意的地方。

    在Android项目开发中,我们可以在网上看到很多项目源码,大部分也不是很精致, 比如 06.Android阿福多媒体播放器开发教程+源码 还有什么浏览器源码. 那么这些有用吗?  价值在哪里? 精致 ...

  10. Android图片爬虫,看妹纸神器项目源码,ListView单张图片查看

    Android图片爬虫,看妹纸神器项目源码 刚自学完安卓,国庆7七天花了6天纯手写入门级app,从集成图片爬虫到整个项目结束真是一步一个坑. 整个项目没有用框架,都是手写纯属练手,项目中主要用到的技术 ...

最新文章

  1. 【贪心】P1056 排座椅
  2. SQL Server 2005 XML 操作总结(七)属性操作——插入、修改操作
  3. centos php配置mysql数据库_CentOS+Nginx+PHP+MySQL详细配置(图解)
  4. 深入浅出时序逻辑电路(1)
  5. android butterknife使用详解
  6. mysql给数据量大的表添加索引的办法
  7. 邀请合作如何表达_适时表达想法 才有利于彼此的合作
  8. 解压tar.xz文件和tar.gz文件
  9. 项目微管理24 - 横向
  10. Mac WebStorm 破解
  11. iPhoneXS、XS Max与iPhoneXR 适配
  12. “esxcli software vib” commands to patch an ESXi 5.x/6.x host (2008939)
  13. 移动端滑动事件---实战笔记
  14. button渐变色 ios_UIButton 背景色渐变动画
  15. 苹果无线耳机连接不上_苹果AirPods Pro鉴别真伪最简单有效的方法
  16. IOS 利用运行时机制来进行归档解档
  17. 12306系统架构,凭什么支撑每天如洪水般的各种抢票插件?
  18. ios html mainfest,编写高性能的 iOS HybridApp
  19. 【雕刻机】高质量双面PCB板的全过程
  20. 揭开均线系统的神秘面纱_在应用程式审查API中揭开新玩法的神秘面纱

热门文章

  1. 是程序员的都得知道为啥1024是程序员节
  2. linux 中hadoop相关命令学习
  3. 第5章 电容元件与电感元件
  4. 鲍斯科机器人_芜湖鲍斯柯机器人有限公司、上海富治机械科技有限公司服务合同纠纷二审民事判决书...
  5. 秦岭三面佛已经拆除,觉得惋惜
  6. 如何批量将 Xls 格式的 Excel 文档转为 Xlsx 格式
  7. 网页制作中PS图像处理的应用
  8. MOS驱动电路设计需要注意的地方
  9. 互联网、大公司常用的英文缩写
  10. 上交计算机考研专业课,上海交大计算机专业考研必知考试科目与内容