问题背景
学而思应用在设备A上必现crash,在B设备上没有问题。A和B是相同的厂商相同的系统版本。
问题原因
首先看crash的日志
W/Bitmap: Called getByteCount() on a recycle()'d bitmap! This is undefined behavior!
? W/System.err: java.lang.IllegalStateException: Can't compress a recycled bitmap
? W/System.err: at android.graphics.Bitmap.checkRecycled(Bitmap.java:514)
? W/System.err: at android.graphics.Bitmap.compress(Bitmap.java:1560)
? W/System.err: at com.tal.xes.app.share.WeixinUtil.bmpToByteArray(WeixinUtil.java:37)
? W/System.err: at com.tal.xes.app.share.XesShareTool.sendWXMessage(XesShareTool.java:364)
? W/System.err: at com.tal.xes.app.share.XesShareTool.shareLink(XesShareTool.java:316)
? W/System.err: at com.xes.jazhanghui.activity.mvp.web.AppWebViewActivity.lambda$showShare$74$AppWebViewActivity(AppWebViewActivity.java:3513)
? W/System.err: at com.xes.jazhanghui.activity.mvp.web.-$$Lambda$AppWebViewActivity$ky7U6HBCmrioBHyzGwtb6oeVh-8.onClick(Unknown Source:2)
? W/System.err: at com.tal.xes.app.share.XesShareTool$4.onClick(XesShareTool.java:513)
? W/System.err: at android.view.View.performClick(View.java:7603)
? W/System.err: at android.view.View.performClickInternal(View.java:7577)
? W/System.err: at android.view.View.access$3800(View.java:865)
? W/System.err: at android.view.View$PerformClick.run(View.java:29384)
? W/System.err: at android.os.Handler.handleCallback(Handler.java:955)
? W/System.err: at android.os.Handler.dispatchMessage(Handler.java:102)
? W/System.err: at android.os.Looper.loopOnce(Looper.java:206)
? W/System.err: at android.os.Looper.loop(Looper.java:296)
? W/System.err: at android.app.ActivityThread.main(ActivityThread.java:8985)
? W/System.err: at java.lang.reflect.Method.invoke(Native Method)
? W/System.err: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:569)
? W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:976)
反编译学而思代码:
代码流程分析:
1。在屏幕密度为400dpi(A)的设备上,Bitmap.createScaledBitmap传入的参数bitmap被decodeResource后的大小已经是目前指定的150,150,createScaledBitmap最终走到createBitmap,createBitmap判断如果传入的长宽大小和source一致,则返回source本身。
2。bitmap被释放。createScaledBitmap=bitmap 导致createScaledBitmap也是被释放的。
3。 createScaledBitmap 也是被释放的。所以导致了第三步的异常。
BitmapFactory.decodeResource
上节中Bitmap source是通过decodeResource加载进来的。
Bitmap bimap = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher_background);
android不同的drawable目录,是针对不同的屏幕密度的。
反编译可以看到原图是在xxhdpi目录下,原始的图片大小是180*180,xxhdpi是针对480屏幕密度的。
BitmapFactory加载图片的时候,有两个重要的属性值。
inTargetDensity当前手机的densityDpi,即当前的手机运行环境的手机密度
**inDensity **是把从资源中读取的density赋值给它,其实就是我们的图片资源所在drawable对应的density,也就是下面表格的对应的值:
举例原始图片在不同屏幕密度上都显示60dp的显示效果:
- 原始图片180*180 在xxhdpi inDensity = 480 inTargetDensity = 480 1dp=3px 180/3=60dp
- A设备 150*150 inDensity = 480 inTargetDensity = 400 1dp=2.5px 150/2.5 = 60 显示60DP的尺寸
每英寸 276.462像素 60dp =150/276.46 = 0.54英寸 - B设备 105*105 inDensity = 480 inTargetDensity = 280 1dp=1.75px 105/1.75=60 显示60DP的尺寸
每英寸 195.384像素 60dp =105/195.38 = 0.537英寸
px = dp * (dpi / 160)。
因为原始图片是180*180并且Dpi是480的图片,所以要显示在60dp的长宽高上面。所以在A设备和B设备上也要转换到60DP的长度。
Dpi表示每英寸的像素数,decodeResource也会针对不同的inTargetDensity做相应的大小转换。
所以decodeResource会把图片,进行480/400的缩放,缩放1.2倍。刚好等于150;导致createScaledBitmap时将返回原始的source。进而导致第三步bitmap被释放
屏幕像素密度(dpi)
dpi是 屏幕每英寸显示的像素点数,在同一尺寸下,分辨率越高,屏幕越清晰,dpi越高。得到屏幕像素密度的计算方式如下
dpi不同
屏幕密度不同,即设备分辨率的不同,如在同尺寸的屏幕中,不同分辨率会使屏幕密度不同。如下图示例
在上面两个图中,屏幕的尺寸、比例,都是一样的,但是分辨率是不一样的,这就造成屏幕密度不一样。这样画一条 360px 的直线,在图5中是满屏,在图6中就只有三分之一,在图6中可以显示的内容,在图5中就有可能超过底部边缘,显示不下。这种情况也是造成需要适配的原因。
Android 屏幕适配原因
总结一下适配的 2 个主要原因
1.屏幕像素密度不一样
2.屏幕比例不同(屏幕尺寸不一样)
其实适配,主要是针对这两点进行适配的。理解了屏幕的概念,知道了适配屏幕的原因,这样处理问题就有了方向。下面说说几种官方推荐的主流适配方式,与民间使用比较多的适配方式。
官方推荐适配方式----dp
Dpi表示每英寸的像素数
屏幕适配原理可以参考
https://blog.csdn.net/xiaojinlai123/article/details/103542363?spm=1001.2014.3001.5502