Bitmap的加载简单优化

本文介绍了Android中Bitmap的加载方式及优化方法,包括BitmapFactory的使用、图片尺寸压缩原理及实践,有效避免内存溢出。

Bitmap的加载简单优化

Bitmap如何加载

Bitmap可以认为是Android系统将图片加载GPU的一个映射,Android可以读取png格式的,也可以读取jpg格式的。那么Android是如何加载一张图片的呢?有个类叫做BitmapFactory,它提供了四个方法:decodeFile(从文件系统中加载),decodeResource(从资源中加载),decodeStream(从输入流中加载),decodeByteArray(从字节数组中加载);其中decodeFile和decodeResource又间接调用了decodeStream方法;

下面是decodeFile的代码:

public static Bitmap decodeFile(String pathName, Options opts) {
        Bitmap bm = null;
        InputStream stream = null;
        try {
            stream = new FileInputStream(pathName);
            bm = decodeStream(stream, null, opts);
        } catch (Exception e) {
            /*  do nothing.
                If the exception happened on open, bm will be null.
            */
            Log.e("BitmapFactory", "Unable to decode stream: " + e);
        } finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    // do nothing here
                }
            }
        }
        return bm;
    }

下面是decodeResource的代码:

    public static Bitmap decodeResource(Resources res, int id, Options opts) {
        Bitmap bm = null;
        InputStream is = null; 

        try {
            final TypedValue value = new TypedValue();
            is = res.openRawResource(id, value);

            bm = decodeResourceStream(res, value, is, null, opts);
        } catch (Exception e) {
            /*  do nothing.
                If the exception happened on open, bm will be null.
                If it happened on close, bm is still valid.
            */
        } finally {
            try {
                if (is != null) is.close();
            } catch (IOException e) {
                // Ignore
            }
        }

        if (bm == null && opts != null && opts.inBitmap != null) {
            throw new IllegalArgumentException("Problem decoding into existing bitmap");
        }

        return bm;
    }

其中的decodeResourceStream方法是这样的:

    public static Bitmap decodeResourceStream(Resources res, TypedValue value,
            InputStream is, Rect pad, Options opts) {

        if (opts == null) {
            opts = new Options();
        }

        if (opts.inDensity == 0 && value != null) {
            final int density = value.density;
            if (density == TypedValue.DENSITY_DEFAULT) {
                opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
            } else if (density != TypedValue.DENSITY_NONE) {
                opts.inDensity = density;
            }
        }

        if (opts.inTargetDensity == 0 && res != null) {
            opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
        }

        return decodeStream(is, pad, opts);
    }

最后依然调用的是decodeStream方法。

这几个方法是在底层实现的,对应着几个native方法,这里不说了。

BitmapFactory.Option的几个参数简介

我们都知道在Android里内存溢出的最大元凶就是图片,如何避免这种情况呢?其实也挺简单,就是采用BitmapFactory.Option这个对象设置图片显示的尺寸。

我们一般显示图片会使用ImageView等控件,但是它们需要的尺寸一般都会比我们提供的图片的尺寸下,所以我们要对图片压缩,下面我们就看一下BitmapFactory.Option提供的方法。

属性说明
int inSampleSize字面意思为取样的尺寸,为int 型;虽然它只设定一个值,但是它会影响到解析图片为Bitmap时长宽两个方向上的像素大小。它设定值会使Bitmap按倍数缩小。默认为1,最小值也为1,表示不缩小;当大于1时,它才会按照比例缩小;比如我们设置为2,Bitmap的长宽会变为原大小的一半,那么相应的它的像素所占有的内存就会缩小为原来的1/4。需要注意的是它的值必须为2的n次幂,例如:1,2,4,8,16
boolean inJustDecodeBounds字面意思为只解析Bitmap边界,也就是长宽;为Boolean型,当我们设置为true时,我们使用BitmapFactory的decode方式解析图片时就不会返回Bitmap,而只会读取该图片的尺寸和类型信息。我们一般通过这个方法来获取尺寸,然后再按照其尺寸缩小。当然用完记得重新置为false。
Bitmap.Config inPreferredConfig字面为Bitmap优先的设置,它是一个enum,它设置的是Bitmap的像素类型;默认为ARGB_8888,这个后面再具体介绍。
int outHeightBitmap 的高
int outWidthBitmap 的宽
String outMimeType图片的MIME类型,比如“image/jpeg”

上面的只是其中的一部分,但是已经够用了。下面我们通过实例来看一下它的使用。

如何利用inSampleSize压缩图片

我们上面说过,我们不想加载图片的原尺寸,我们需要对它进行尺寸的压缩。那么我们一般的步骤是什么样的呢?

  1. 获取图片尺寸
  2. 计算Bitmap合适的尺寸,即计算inSampleSize的值
  3. 设置inSampleSize,压缩Bitmap,并显示在ImageView上

下面我们根据这个步骤来实现一下,这里我随便在网上找了了一张图片,放到了Android的外部存储设备中,我们直接读取这个图片,它的像素为1024 × 683,格式为”jpg“。

首先我们不压缩,看看什么情况:

        Bitmap b = BitmapFactory.decodeFile(getBeautyPath());
        ivBeauty.setImageBitmap(b);

上面的ImageView宽高都为wrap_content,然后我们开始压缩之旅。

1. 获取尺寸

BitmapFactory.Options options = new BitmapFactory.Options();
//这里我获取图片的尺寸和类型信息
options.inJustDecodeBounds = true;
//我们这里获取到的Bitmap是空的,图片没有真正的加载到内存,只有尺寸和类型信息
Bitmap bitmap = BitmapFactory.decodeFile(getBeautyPath(), options);
//为了确定为空,我们做一下判断
if (bitmap == null) {
    loge("图片的MIME类型 = " + options.outMimeType);
    loge("图片的高 = " + options.outHeight);
    loge("图片的宽 = " + options.outWidth);
}

上面的loge方法为我自己封装的,调用的是Log.e()。运行之后我们得到了下面的log,证明我们的是没有问题的。

09-03 11:16:41.426 13194-13194/com.liteng.mytest E/BitmapFactoryTest: 图片的MIME类型 = image/jpeg
09-03 11:16:41.426 13194-13194/com.liteng.mytest E/BitmapFactoryTest: 图片的高 = 683
09-03 11:16:41.426 13194-13194/com.liteng.mytest E/BitmapFactoryTest: 图片的宽 = 1024

2.计算Bitmap合适的尺寸

我们获取了原图的尺寸,我们想把它显示到一个宽高分别为200,100的ImageView上,那我们怎么计算呢?

这里我们封装一个方法:

private int computeInSampleSize(BitmapFactory.Options options, int targetW, int targetH) {
    int width = options.outWidth;
    int height = options.outHeight;

    int inSampleSize = 1;
    //判断一下原图的宽高与我们的目标宽高大小,如果原图的长或者宽大于目标宽高才计算
    if (width > targetW || height > targetH) {
        int halfH = height / 2;
        int halfW = width / 2;
        //inSimpleSize的必须是2的指数次幂,所以我们取最可能的inSampleSize的最大值
        while ((halfH / inSampleSize) > targetH && (halfW / inSampleSize) > targetW) {
            inSampleSize *= 2;
        }
    }
    return  inSampleSize;
}

3.设置inSampleSize,压缩Bitmap,并显示在ImageView上

BitmapFactory.Options options = new BitmapFactory.Options();
//这里我获取图片的尺寸和类型信息
options.inJustDecodeBounds = true;
//我们这里获取到的Bitmap是空的,图片没有真正的加载到内存,只有尺寸和类型信息
Bitmap bitmap = BitmapFactory.decodeFile(getBeautyPath(), options);
//为了确定为空,我们做一下判断
if (bitmap == null) {
    loge("图片的MIME类型 = " + options.outMimeType);
    loge("图片的高 = " + options.outHeight);
    loge("图片的宽 = " + options.outWidth);
}

//获取计算到的inSampleSize
options.inSampleSize = computeInSampleSize(options,200,100);
//我们不再只获取图片的尺寸和类型了,下一步我们需要加载Bitmap了
options.inJustDecodeBounds = false;
Bitmap targetBitmap = BitmapFactory.decodeFile(getBeautyPath(),options);
ivBeauty.setImageBitmap(targetBitmap);

那么经过我们压缩过的图片是什么样的呢?看图

那么我们在看一下log呢?

09-03 12:03:19.957 8642-8642/com.liteng.mytest E/BitmapFactoryTest: 图片的MIME类型 = image/jpeg
09-03 12:03:19.957 8642-8642/com.liteng.mytest E/BitmapFactoryTest: 图片的高 = 683
09-03 12:03:19.957 8642-8642/com.liteng.mytest E/BitmapFactoryTest: 图片的宽 = 1024
09-03 12:03:19.957 8642-8642/com.liteng.mytest E/BitmapFactoryTest: inSampleSize = 4

我们计算出来的inSampleSize为4,之前我们说过,in SampleSize只能是2的指数次幂,我们这里只能尽可能接近我们需要值,而不能完全精确。

上面我们说到Bitmap.Config 这个枚举,它的代码如下(去掉了注释和空行):

public enum Config {
    ALPHA_8     (1),
    RGB_565     (3),
    @Deprecated
    ARGB_4444   (4),
    ARGB_8888   (5);
    final int nativeInt;
    private static Config sConfigs[] = {
        null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888
    };
    Config(int ni) {
        this.nativeInt = ni;
    }
    static Config nativeToConfig(int ni) {
        return sConfigs[ni];
    }
}

我们都知道Bitmap占有内存为:

Bitmap×××

我们在上面代码中看到的 ALPHA_8,RGB_565,ARGB_4444 ,ARGB_8888就是Bitmap的四种像素形式,它们每个像素占用的字节数分别为4、2、2、1;即
ALPHA8:RGB565:ARGB4444:ARGB88884:2:2:1

其中ARGB4444在API13被废弃了。

下面是整个Activity的代码,感兴趣的可以再封装一下,做一个工具类:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "BitmapFactoryTest";

    private ImageView ivBeauty;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ivBeauty = (ImageView) this.findViewById(R.id.ivBeauty);

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.ARGB_4444;
        //这里我获取图片的尺寸和类型信息
        options.inJustDecodeBounds = true;
        //我们这里获取到的Bitmap是空的,图片没有真正的加载到内存,只有尺寸和类型信息
        Bitmap bitmap = BitmapFactory.decodeFile(getBeautyPath(), options);
        //为了确定为空,我们做一下判断
        if (bitmap == null) {
            loge("图片的MIME类型 = " + options.outMimeType);
            loge("图片的高 = " + options.outHeight);
            loge("图片的宽 = " + options.outWidth);
        }

        //获取计算到的inSampleSize
        options.inSampleSize = computeInSampleSize(options,200,100);
        loge("inSampleSize = " + options.inSampleSize);
        //我们不再只获取图片的尺寸和类型了,下一步我们需要加载Bitmap了
        options.inJustDecodeBounds = false;

        Bitmap targetBitmap = BitmapFactory.decodeFile(getBeautyPath(),options);
        ivBeauty.setImageBitmap(targetBitmap);
    }

    private int computeInSampleSize(BitmapFactory.Options options, int targetW, int targetH) {
        int width = options.outWidth;
        int height = options.outHeight;

        int inSampleSize = 1;
        //判断一下原图的宽高与我们的目标宽高大小,如果原图的长或者宽大于目标宽高才计算
        if (width > targetW || height > targetH) {
            int halfH = height / 2;
            int halfW = width / 2;
            //inSimpleSize的必须是2的指数次幂,所以我们取最可能的inSampleSize的最大值
            while ((halfH / inSampleSize) > targetH && (halfW / inSampleSize) > targetW) {
                inSampleSize *= 2;
            }
        }
        return  inSampleSize;
    }


    private String getBeautyPath() {
        String path = Environment.getExternalStorageDirectory() + "/Download/beauty.jpg";
        return path;
    }


    private void loge(String msg) {
        Log.e(TAG, msg);
    }
}
<think>好的,我现在需要帮助用户了解如何在Avalonia中加载位图图片。首先,我得回忆一下Avalonia的图片处理相关API。Avalonia的位图处理主要涉及Bitmap类,可能还有资源加载的方式。 用户的问题是关于加载图片的,所以可能需要分几种情况:加载嵌入式资源、文件系统中的图片,或者网络图片。不同的来源可能需要不同的方法。比如,嵌入式资源通常使用资源管理器,而文件系统则需要文件路径或者流来读取。 接下来,我需要确认Avalonia中加载位图的具体类和方法。记得在Avalonia中,Bitmap类可能位于Avalonia.Media.Imaging命名空间下。加载资源的话,可能会用到AssetLoader或者Application.Current的Resources。比如,AssetLoader.Open或者new Bitmap(文件路径)。 另外,用户可能还会遇到跨平台的问题,比如文件路径的处理,特别是在不同操作系统上路径分隔符的不同。需要提醒用户使用正确的路径格式,或者使用System.IO中的方法来处理路径。 然后,示例代码的结构也很重要。应该分步骤说明如何加载不同来源的图片,并展示如何将位图赋值给Image控件。可能需要用到using语句来确保资源正确释放,特别是当使用流的时候。 还要注意异常处理,比如文件不存在或者资源名称错误的情况,这时候程序可能会崩溃,用户需要知道如何处理这些异常。不过用户的问题可能更关注基本用法,暂时可以不深入异常处理,但如果有必要的话可以简单提及。 另外,可能需要提到异步加载,特别是在加载网络图片时,应该使用异步方法避免阻塞UI线程。比如,使用HttpClient异步获取图片流,然后加载到位图中。 最后,确保示例代码的语法正确,比如正确的命名空间引用,比如Avalonia.Media.Imaging、Avalonia.Controls等。同时,需要检查是否所有的类和方法在用户使用的Avalonia版本中存在,比如AssetLoader是否在特定版本中可用,或者是否有替代方法。 总结一下,回答的结构应该是先介绍Bitmap类,然后分点说明不同加载方式,每种方式给出示例代码,并解释注意事项,比如路径问题、资源管理、异步加载等。同时提醒用户确保资源文件的生成操作设置为正确的类型,比如嵌入式资源或复制到输出目录。</think>在 Avalonia 中加载位图(Bitmap)主要通过 `Avalonia.Media.Imaging.Bitmap` 类实现。以下是逐步说明: ### 一、基础概念 Avalonia 的 `Bitmap` 类(命名空间:`Avalonia.Media.Imaging`)用于处理图像数据,支持格式包括 PNG、JPEG、BMP 等。 --- ### 二、加载图片的三种常见方式 #### 1. 从嵌入式资源加载 **适用场景**:图片嵌入在程序集资源中 **步骤**: 1. 将图片文件添加到项目,属性设置为 `Embedded Resource` 2. 使用 `AssetLoader` 加载: ```csharp using var stream = AssetLoader.Open(new Uri("avares://程序集名称/Images/image.png")); var bitmap = new Bitmap(stream); ``` #### 2. 从文件系统加载 **适用场景**:图片存储在本地磁盘 **步骤**: ```csharp var filePath = "C:/path/to/image.jpg"; var bitmap = new Bitmap(filePath); ``` #### 3. 从网络加载 **适用场景**:下载远程图片 **步骤**(异步示例): ```csharp using var httpClient = new HttpClient(); var response = await httpClient.GetAsync("https://example.com/image.png"); await using var stream = await response.Content.ReadAsStreamAsync(); var bitmap = new Bitmap(stream); ``` --- ### 三、在界面显示图片 将 `Bitmap` 绑定到 `Image` 控件: ```xml <Image Width="200" Height="200" Source="{Binding MyBitmap}" /> ``` 或在代码中设置: ```csharp var imageControl = new Image(); imageControl.Source = bitmap; ``` --- ### 四、注意事项 1. **路径问题** - 嵌入式资源使用 `avares://` URI 协议 - 文件路径需注意平台差异(Windows/macOS/Linux) 2. **资源释放** 使用 `using` 语句或手动调用 `Dispose()` 释放非托管资源 3. **跨线程访问** 在非 UI 线程加载图片后,需通过 `Dispatcher` 更新界面 --- ### 五、性能优化 - 对大图使用 `Bitmap.DecodeToWidth` 或 `Bitmap.DecodeToHeight` 进行缩放解码 - 异步加载避免阻塞 UI 线程 - 缓存常用图片资源 如果需要更具体的实现示例,可以告知您的使用场景(如 MVVM 模式或代码后台),我将提供针对性代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值