2018-01-23 15:10:51 Jumenglo 阅读数 17317

1、上表格 DPI,DP,PX

宽×高(标准值) 240×320 320×480 480×800 720×1280 1080×1920 1440×2560
DPI等级 LDPI MDPI HDPI XHDPI XXHDPI XXXHDPI
DPI数值 120 160 240 320 480 640
对应比例 3 4 6 8 12 16
1DP=?PX 0.75 1 1.5 2 3 4


2、adb 修改

 adb shell

 wm size 1080x1920

wm density 480

//wm size reset

//wm desity reset




2018-11-14 17:28:37 qq2364121253 阅读数 44
  1. dpi等级、px与dp的换算

    dpi等级 ldpi mdpi hdpi xhdpi xxhdpi xxxhdpi
    dpi范围 120及以下 120 ~ 160 160 ~ 240 240 ~ 320 320 ~ 480 480 ~ 640
    dp:px 0.75 1 1.5 2.0 3.0 4.0

    一遍比较全面的博客介绍Android屏幕适配全攻略
    官方文档关于屏幕适配的介绍点击直达

2016-11-12 21:26:43 joy_liu_study 阅读数 132

首先

Android 中有这样一个公式 px = dp *(dpi/160)

px:像素

dp:

dpi:接下来会有两个dpi,请具体区分  像素密度  每英寸有的像素数 具体每个设备不同

Android中对于dpi有四个级别:120(low)、160(medium)、240(high)、320(xhigh)

Android在编译的时候会对于手机的dpi先匹配一个等级的dpi,带入上面的公式计算所占的像素数

例如:有一个长度为100dp的控件,运行在dpi分别为252和217的手机上,首先Android会先给他们匹配dpi,都为240

那么他们所占的像素数px = 100*(240/160)

但是在具体每个手机上面他们的实际长度是不一样的

具体长度L = px*dpi

所以两部手机的具体长度分别为

100*(240/160)*252

100*(240/160)*217


综上所述:

dpi1:手机的dpi     dpi2:系统匹配的dpi   L:实际长度

L = px*dpi1 = dp*(dpi/160)*dpi2

所以dp并不是实际物理长度的单位,它在每部dpi不同的手机上都不一样

2019-03-21 22:04:18 u014743238 阅读数 399

dpi(每英寸的点数):像素密度是屏幕上单位面积内的像素数。

分辨率 DPI数值范围 DPI等级 1dp =?px
240*320 120dpi ldpi 0.75
320*480 120-160dpi mdpi 1
480*800/480*854 160-240dpi hdpi 1.5
720*1280 240-320dpi xhdpi 2
1080*1920 320-480dpi xxhdpi 3
1440*2560 480-640dpi xxxhdpi 4

1.drawable-nodpi :存放与像素密度无关的资源。无论当前屏幕的像素密度如何,系统都不会缩放使用此限定符标记的资源。

2.drawable-anydpi:资源优先于任何dpi得到使用。

 

2019-11-29 21:54:58 flueky 阅读数 11

0. 前言

Android 图片适配,真的不是你想像的那样,至少在写这篇文章之前,我陷在一个很大很大的误区中。

1. 关于适配

所有关于适配的基本概念,这里不多介绍,资料有很多。下面只介绍点比较重要的部分。

等级 密度 比例
ldpi 120dpi 1dp=0.75px
mdpi 160dpi 1dp=1px
hdpi 240dpi 1dp=1.5px
xhdpi 320dpi 1dp=2px
xxhdpi 480dpi 1dp=3px
xxxhdpi 640dpi 1dp=4px

上面这张表介绍了 dpi 与 px 之间的关系。而多数手机厂商没有严格按照上述规范生产屏幕,才会有如今令人恶心的 Android 适配问题。

如:三星 C9,6英寸屏幕,分辨率 1920x1080 ,按照公式计算屏幕密度 367 dpi ,更接近 320dpi ,因此适配时,会取 xhdpi 目录下的数据。

但实际中,会取 xxhdpi 数据,因为实际屏幕密度是 420 dpi。(通过代码的方式获取)

DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
Log.d(TAG, "onCreate: "+dm.density);
Log.d(TAG, "onCreate: "+dm.densityDpi);

2019-11-29 14:50:03.879 28034-28034/com.flueky.demo D/MainActivity: onCreate: 2.625

2019-11-29 14:50:03.879 28034-28034/com.flueky.demo D/MainActivity: onCreate: 420

2.625 是 420/160 的结果。表示在 C9 上,1dp=2.625 px ,411dp 约等于 1080px ,表示整个屏幕的宽度。

如:三星 S8,5.8英寸屏幕,分辨率 2960x1440 ,屏幕密度 568 dpi,接近 640 dpi ,因此适配时,会取 xxxhdpi 目录下数据。

但实际中,会取 xxhdpi 数据,因为实际屏幕密度是 560 dpi 。

2019-11-29 14:59:49.604 20793-20793/com.flueky.demo D/MainActivity: onCreate: 3.5

2019-11-29 14:59:49.604 20793-20793/com.flueky.demo D/MainActivity: onCreate: 560

在 S8 上 ,1dp=3.5px ,411dp 约等于 1440px ,表示整个屏幕的宽度。

很庆幸,这两台手机上的适配数据是一样的,高度会存在差异,但是通常都是滚动长页面,或者留白端页面不受太大影响。若恰好是满屏页面,则不适用。

今日头条的适配方案即是通过修改 density 的 值进行适配。不知道什么原因,他们在《今日头条》7.5 版本中未使用此适配方式。

2. 图片适配

言归正传,关于图片适配才是我们的主题。

秉着实践是检验真理的唯一标准这一原则,做了如下实验。三种尺寸的图片,放置在四个目录目录,用三种尺寸的 ImageView ,用三种方式加载图片,检查其内存使用的情况。

  1. 图片尺寸

    • large 1600x900 ,占用内存 1600x900x4/1024/1024 = 5.49m
    • middle 800x450 ,占用内存 800x450x4/1024/1024 = 1.37m
    • small 400x225 ,占用内存 400x225x4/1024/1024 = 0.34m
  2. 图片目录

    • asset
    • drawable hdpi
    • drawable xhdpi
    • drawable xxhdpi
  3. ImageView

    • wrap-content
    • 280dp
    • 160dp
  4. 引用方式

    • android:src
    • setImageResource
    • setImageBitmap

加载 asset 目录下的图片,只能使用 setImageBitmap 的方式。

第一组实验,使用 1 2 3 以及 setImageBitmap ,得出 3x4x3x1 = 36 条数据,如下表。

  • B 表示内存中图片的 bitmap 大小。
  • G 表示内存中 Graphics 占用的空间。
  • N 表示内存中 Native 占用的空间。
  • 序号 0 表示,未使用图片时的情况。
  • 实验基于屏幕密度 540dpi 的设备。
序号 目录 分辨率 宽度 B G N
0 - - - - 1.8m 7.8m
1 asset 1600x900 wrap 5.49m 8.7m 14.6m
2 asset 1600x900 w280 5.49m 8.7m 14.7m
3 asset 1600x900 w160 5.49m 8.6m 13.2m
4 asset 800x450 wrap 1.37m 3.8m 9.3m
5 asset 800x450 w280 1.37m 3.8m 9.2m
6 asset 800x450 w160 1.37m 3.8m 9.3m
7 asset 400x225 wrap 0.34m 2.6m 8.2m
8 asset 400x225 w280 0.34m 2.6m 8.2m
9 asset 400x225 w160 0.34m 2.6m 8.2m
10 hdpi 1600x900 wrap 27.8m 37.1m 37.3m
11 hdpi 1600x900 w280 27.8m 37.1m 31.7m
12 hdpi 1600x900 w160 27.8m 31.7m 36.9m
13 hdpi 800x450 wrap 6.95m 9.7m 14.9m
14 hdpi 800x450 w280 6.95m 9.7m 14.8m
15 hdpi 800x450 w160 6.95m 9.7m 15.3m
16 hdpi 400x225 wrap 1.73m 4.1m 9.9m
17 hdpi 400x225 w280 1.73m 4m 9.7m
18 hdpi 400x225 w160 1.73m 4.1m 10.1m
19 xhdpi 1600x900 wrap 15.6m 18.9m 24.9m
20 xhdpi 1600x900 w280 15.6m 18.9m 24.7m
21 xhdpi 1600x900 w160 15.6m 18.9m 24.7m
22 xhdpi 800x450 wrap 3.9m 6.3m 12.4m
23 xhdpi 800x450 w280 3.9m 6.3m 11.5m
24 xhdpi 800x450 w160 3.9m 6.3m 12.2m
25 xhdpi 400x225 wrap 0.97m 3.2m 9m
26 xhdpi 400x225 w280 0.97m 3.2m 8.8m
27 xhdpi 400x225 w160 0.97m 3.2m 9.1m
28 xxhdpi 1600x900 wrap 6.95m 9.7m 16.7m
29 xxhdpi 1600x900 w280 6.95m 9.7m 16m
30 xxhdpi 1600x900 w160 6.95m 9.7m 16m
31 xxhdpi 800x450 wrap 1.73m 4.1m 9.7m
32 xxhdpi 800x450 w280 1.73m 4.1m 9.7m
33 xxhdpi 800x450 w160 1.73m 4.1m 9.6m
34 xxhdpi 400x225 wrap 0.43m 2.6m 8.4m
35 xxhdpi 400x225 w280 0.43m 2.6m 8.4m
36 xxhdpi 400x225 w160 0.43m 2.6m 8.7m

结果分析:

  1. 使用的图片越大,越耗内存。实验数据:1/4/7。
  2. 图片内存与其显示大小无关。实验数据:1/2/3,4/5/6,7/8/9。误区1:图片显示区域越大,越耗内存。
  3. 加载 asset 目录的图片,图片占用内存等于实际大小,实验数据:1/2/3,4/5/6,7/8/9。计算方式:l x w x 4,长乘宽乘 4 (每个像素点占用 4 字节)。
  4. 加载 drawable 目录的图片,图片占用内存存在缩放。如:large 占用内存 5.49m , hdpi 对应 240 dpi 。因此图片实际占用内存 5.49 x (540/240)^2 = 27.79m 。误区2:5.49 x (540/240) = 12.35m。

关于 B/G/N 之间的关系还未研究透彻,如有了解还请告知。

第二组实验基于屏幕密度 360dpi 的设备,排除多数无用项。

序号 目录 分辨率 宽度 B G N
37 - - - - 1.8m 7.4m
38 asset 1600x900 w160 5.49m 8.7m 14.7m
39 asset 800x450 w280 1.37m 3.8m 9.3m
40 asset 400x225 wrap 0.34m 2.6m 8.3m
41 hdpi 1600x900 wrap 12.3m 15.4m 21.4m
41 hdpi 1600x900 w280 12.3m 15.4m 21.3m
42 hdpi 1600x900 w160 12.3m 15.4m 21.4m
43 hdpi 800x450 w280 3.08m 5.9m 11m
44 hdpi 400x225 w160 0.77m 3m 8.8m
45 xhdpi 1600x900 wrap 6.95m 9.7m 16m
46 xhdpi 1600x900 w280 6.95m 9.7m 16.1m
47 xhdpi 1600x900 w160 6.95m 9.7m 16.1m
48 xhdpi 800x450 w280 1.73m 4.1m 9.7m
49 xhdpi 400x225 w160 0.43m 2.6m 8.3m
50 xxhdpi 1600x900 wrap 3.08m 5.9m 12.3m
51 xxhdpi 1600x900 w280 3.08m 5.9m 12.4m
52 xxhdpi 1600x900 w160 3.08m 5.9m 12.2m
53 xxhdpi 800x450 w280 0.77m 3m 8.7m
54 xxhdpi 400x225 w160 0.19m 2.4m 8.1m

结果分析:

  1. 图片内存与屏幕密度无关。

第三组实验基于屏幕密度 540 dpi 的设备,使用 setImageResource 方式加载图片。

序号 目录 分辨率 宽度 B G N
55 hdpi 1600x900 w160 5.49m 8.7m 19m
56 hdpi 800x450 wrap 1.37m 3.8m 9.3m
57 hdpi 400x225 w280 0.34m 2.6m 8.2m
58 xhdpi 1600x900 w280 5.49m 8.7m 19.9m
59 xhdpi 800x450 w160 1.37m 3.8m 9.3m
60 xhdpi 400x225 wrap 0.34m 2.6m 8.6m
61 xxhdpi 1600x900 wrap 5.49m 8.7m 14.6m
62 xxhdpi 800x450 w280 1.37m 3.9m 9.6m
63 xxhdpi 400x225 w160 0.34m 2.6m 8.3m

结果分析:

  1. 使用 setImageResource 加载图片,没有对图片进行缩放。实验数据:55/58/61。误区3:使用不同屏幕密度下的图片存在缩放情况。

实验的最后发现,在布局用使用 android:src 引用图片时,图片内存也不缩放。因此,没有列出实验数据。

3. 源码分析

基于以上结果,通过分析源码,得以验证。

  1. asset 目录下图片占用内存是图片实际大小。
// 通过流的方式解析图片。
bitmap = BitmapFactory.decodeStream(getAssets().open("test.jpg"));

public static Bitmap decodeStream(InputStream is) {
    return decodeStream(is, null, null);
}
/**
 * 实际执行到下面的代码
 */
public static Bitmap decodeStream(@Nullable InputStream is, @Nullable Rect outPadding,
        @Nullable Options opts) {
    
    ......

    Bitmap bm = null;

    Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
    try {
        if (is instanceof AssetManager.AssetInputStream) {
            final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
            // 解析 asset 目录下的 文件,opts == null ,所以按照设备的 density 解析。
            bm = nativeDecodeAsset(asset, outPadding, opts);
        } else {
            // 解密普通的文件流
            bm = decodeStreamInternal(is, outPadding, opts);
        }

        if (bm == null && opts != null && opts.inBitmap != null) {
            throw new IllegalArgumentException("Problem decoding into existing bitmap");
        }
        // 更新 bitmap 的 density 
        setDensityFromOptions(bm, opts);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
    }

    return bm;
}

private static void setDensityFromOptions(Bitmap outputBitmap, Options opts) {
    // opts==null,因此未做处理。
    if (outputBitmap == null || opts == null) return;

    ......
}
  1. drawable 目录下图片占用内存被缩放。
// 只有在使用下面的方式获取 bitmap 会缩放。
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);

public static Bitmap decodeResource(Resources res, int id) {
    return decodeResource(res, id, null);
}
public static Bitmap decodeResource(Resources res, int id, Options opts) {
    validate(opts);
    Bitmap bm = null;
    InputStream is = null; 
    
    try {
        final TypedValue value = new TypedValue();
        // 根据 id 得到文件流,AssetInputStream
        is = res.openRawResource(id, value);
        // 根据流得到 bitmap
        bm = decodeResourceStream(res, value, is, null, opts);
    } catch (Exception e) {
    ......
    }
    return bm;
}
@Nullable
public static Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value,
        @Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts) {
    validate(opts);
    if (opts == null) {
        // 生成 Option 
        opts = new Options();
    }
    // 以 设备 320dpi ,图片在 xxhdpi 为例
    if (opts.inDensity == 0 && value != null) {
        final int density = value.density; // density = 480
        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) {
        // res.getDisplayMetrics().densityDpi = 320
        opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
    }
    
    return decodeStream(is, pad, opts);
}
@Nullable
public static Bitmap decodeStream(@Nullable InputStream is, @Nullable Rect outPadding,
        @Nullable Options opts) {
    
    ......

    Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
    try {
        if (is instanceof AssetManager.AssetInputStream) {
            final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
            // opts inDensity 480 ,inTargetDensity 320 ,因此需要缩放。
            bm = nativeDecodeAsset(asset, outPadding, opts);
        } else {
            bm = decodeStreamInternal(is, outPadding, opts);
        }

        if (bm == null && opts != null && opts.inBitmap != null) {
            throw new IllegalArgumentException("Problem decoding into existing bitmap");
        }
        // 根据 opts 设置图片的 density 
        setDensityFromOptions(bm, opts);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
    }

    return bm;
}
private static void setDensityFromOptions(Bitmap outputBitmap, Options opts) {
    if (outputBitmap == null || opts == null) return;

    final int density = opts.inDensity;
    if (density != 0) {
        // 先设置成 480
        outputBitmap.setDensity(density);
        final int targetDensity = opts.inTargetDensity;
        if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
            return;
        }

        byte[] np = outputBitmap.getNinePatchChunk();
        final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
        // 由于支持缩放,再设置成 320 
        if (opts.inScaled || isNinePatch) {
            outputBitmap.setDensity(targetDensity);
        }
    } else if (opts.inBitmap != null) {
        // bitmap was reused, ensure density is reset
        outputBitmap.setDensity(Bitmap.getDefaultDensity());
    }
}
  1. 通过 setImageResource,或布局引用,图片不缩放。
// 布局引用时,在 ImageView 的构造函数中加载图片
public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
        int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    ......
    final TypedArray a = context.obtainStyledAttributes(
            attrs, R.styleable.ImageView, defStyleAttr, defStyleRes);
    // 得到 Drawable 对象,如果使用 png 或 jpg 等图片,则是 BitmapDrawable 
    final Drawable d = a.getDrawable(R.styleable.ImageView_src);
    ......
}

// TypedArray 类
public Drawable getDrawable(@StyleableRes int index) {
    // 注意此处的 density 是 0
    return getDrawableForDensity(index, 0);
}

public Drawable getDrawableForDensity(@StyleableRes int index, int density) {
    if (mRecycled) {
        throw new RuntimeException("Cannot make calls to a recycled instance!");
    }

    final TypedValue value = mValue;
    if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
        ......
        // density = 0 ,执行下面代码
        return mResources.loadDrawable(value, value.resourceId, density, mTheme);
    }
    return null;
}
// ResourcesImpl 类
Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
        int density, @Nullable Resources.Theme theme)
        throws NotFoundException {
    // useCache = true,后面的代码忽略
    final boolean useCache = density == 0 || value.density == mMetrics.densityDpi;
    ......
    try {
        ......

        // 读加载过的 BitmapDrawable
        if (!mPreloading && useCache) {
            final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
            if (cachedDrawable != null) {
                cachedDrawable.setChangingConfigurations(value.changingConfigurations);
                return cachedDrawable;
            }
        }
        final Drawable.ConstantState cs;
        if (isColorDrawable) {
            cs = sPreloadedColorDrawables.get(key);
        } else {
            cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
        }

        Drawable dr;
        boolean needsNewDrawableAfterCache = false;
        if (cs != null) {
            ......
        } else if (isColorDrawable) {
            dr = new ColorDrawable(value.data);
        } else {
            // 最终执行到此处加载图片
            dr = loadDrawableForCookie(wrapper, value, id, density);
        }
        ......
        return dr;
    } catch (Exception e) {
        ......
    }
}

private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value,
            int id, int density) {
    ......
    final Drawable dr;
    Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
    LookupStack stack = mLookupStack.get();
    try {
        // Perform a linear search to check if we have already referenced this resource before.
        if (stack.contains(id)) {
            throw new Exception("Recursive reference in drawable");
        }
        stack.push(id);
        try {
            // 处理使用 shape selector 等 使用 xml 生成的资源文件
            if (file.endsWith(".xml")) {
                final XmlResourceParser rp = loadXmlResourceParser(
                        file, id, value.assetCookie, "drawable");
                dr = Drawable.createFromXmlForDensity(wrapper, rp, density, null);
                rp.close();
            } else {
                // 通过 asset 的方式读取资源  file:///res/drawable-xhdpi/test.jpg
                final InputStream is = mAssets.openNonAsset(
                        value.assetCookie, file, AssetManager.ACCESS_STREAMING);
                AssetInputStream ais = (AssetInputStream) is;
                // 解析得到 BitmapDrawable
                dr = decodeImageDrawable(ais, wrapper, value);
            }
        } finally {
            stack.pop();
        }
    } catch (Exception | StackOverflowError e) {
        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        final NotFoundException rnf = new NotFoundException(
                "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
        rnf.initCause(e);
        throw rnf;
    }
    Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    .......
    return dr;
}
// 使用 setImageResource 方式同布局引用一致。
public void setImageResource(@DrawableRes int resId) {
    ......
    resolveUri();
    ......
}

private void resolveUri() {
    ......
    if (mResource != 0) {
        try {
            // 读取 Drawable
            d = mContext.getDrawable(mResource);
        } catch (Exception e) {
            Log.w(LOG_TAG, "Unable to find resource: " + mResource, e);
            // Don't try again.
            mResource = 0;
        }
    } else if (mUri != null) {
        ......
    } else {
        return;
    }
    updateDrawable(d);
}
// Context 类
public final Drawable getDrawable(@DrawableRes int id) {
    return getResources().getDrawable(id, getTheme());
}
// Resources 类 
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
        throws NotFoundException {
    return getDrawableForDensity(id, 0, theme);
}

public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) {
    final TypedValue value = obtainTempTypedValue();
    try {
        final ResourcesImpl impl = mResourcesImpl;
        impl.getValueForDensity(id, density, value, true);
        // 依然执行到 ResourcesImpl.loadDrawable 且 density = 0
        return impl.loadDrawable(this, value, id, density, theme);
    } finally {
        releaseTempTypedValue(value);
    }
}

4. 总结

经过上述实践验证,建议在使用图片时,控制好图片尺寸。避免直接根据 resId 转化成 bitmap 对象。如需实时释放 bitmap 对象,建议通过 BitmapDrawable 取到 bitmap 引用再释放。

另外,以前存在的三个误区请避免。

  1. 图片占用的内存只与图片大小有关。非图片文件大小。
  2. 图片缩放计算,长scalescale = 长宽*scale^2。
  3. 布局中引用的图片以及 setImageResource 方式使用图片,图片不会根据密度缩放。

源码地址

觉得有用?那打赏一个呗。去打赏

android平板分辨率

阅读数 57305

MTK 移植笔记

阅读数 2524

没有更多推荐了,返回首页