Android 其他问题点
Posted danfengw
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 其他问题点相关的知识,希望对你有一定的参考价值。
1 Serializable与Parcelable 区别
Sericalizable 是java 提供的进行序列化的方式,对对象整体序列化,更适合网络数据进行序列化的时候使用。使用IO操作进行的序列化。
Parcelable 是android提供的一种序列化的方式,可以将一个完整的对象进行分解,对部分分别序列化,效率更高,更适合内存中进行数据传输的时候使用。
二者速度的比较:Parcelable更快,因为Parcelable是在内存中进行的序列化,Sericalizable 则需要进行大量的io操作,会慢一些。
2 Android为什么使用Bundle 传输数据,为啥不是hashMap
2个原因:
(1)小的数据量:Bundle 内部封装了ArrayMap进行的存储,ArrayMap本身就是适合小数据量的存储的,Bundle的数据传递不能超过1M,刚好也是小数据量传输,HashMap适合大数据传输,因此Bundle更合适
(2)序列化方式: Intent传递数据的时候,需要的是基本数据类型或者是序列化的数据,而序列化的数据必须是通过Parcelable序列化的,Bundle就是实现的Parcelable的序列化,而HashMap则实现的是Serializable
Bitmap的大小计算
不考虑分辨率作用:长X宽X4
考虑分辨率及每个像素点占用的字节:
长x宽x字节数* targetDpi/ 图片所在目录分辨率(比如我target是xh 320的,但图片放在mdpi下面则是 320/160)
ps:ARGB-8888—32位–4字节、RGB-565----16位-----2字节
图片一般放在xhdpi 和xxhdpi下
aapt作用
Android Asset Packing Tool (Android资源打包工具)
编译Android下的资源resource文件。
AAPT2 支持通过增量编译实现更快的资源编译,资源处理拆分为2个步骤:编译+链接
编译:将资源文件编译为二进制格式文件(.flat)
链接:将编译后的所有文件合并,打包成一个单独文件
讲下Context
类结构:Context可以理解为“上下文”:它贯穿整个应用;Activity切换,Service启动都需要Context。
Context 是一个抽象类,它的实现分成ContextImpl 和ContextWrapper ,其中ContextWrapper又有3个实现类:Application、Service、ContextThemeWrapper,ContextThemeWrapper的实现类是Activity。
关系:所有Context都是在应用的主线程ActivityThread中创建的,由于 Application,Service,Activity的祖先都是Context抽象类,所以在创 建它们的同时也会为每一个类创建一个ContextImpl类,ContextImpl 是Context的之类,真正实现Context功能方法的类。因此 Application,Service,Activity都关联着一个ContextImpl对象。
可能存在的问题:
尽量少用Context对象去获取静态变量,静态方法,以及单例对象。 以免导致内存泄漏
Context内存泄漏问题
静态资源导致的内存泄漏
单例模式导致内存泄漏
怎么理解Instrumentation
用于实现应用程序检测代码的基类。
Instrumentation主要参与application 和 activity 生命周期的调用,因为其强大的跟踪application及activity生命周期的功能,用于android 应用测试框架中,被做为基类使用。
LayoutInflate 相关参数
LayoutInflater 有好几个inflate方法,但内部最终都会调用到三个参数的inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot
的方法。这里需要解释2个点,root,和 attachToRoot
root 不为null 的时候,会调用generateLayoutParams生成相关LayoutParam参数
attachToRoot=true ,会调用addView的方法
apk打包流程
(1)打包资源文件。
(2)处理aidl文件,生成相应java 文件。对于没有使用到aidl的android工程,可以跳过此步骤。
(3)编译工程源代码,生成相应class 文件。
这一步调用了javac编译工程src目录下所有的java源文件,生成的class文件位于工程的bin\\classes目录下,上图假定编译工程源代码时程序是基于android SDK开发的,实际开发过程中,也有可能会使用android NDK来编译native代码,因此,如果可能的话,这一步还需要使用android NDK编译C/C++代码,当然,编译C/C++代码的步骤也可以提前到第一步或第二步。通过Java Compiler编译R.java、Java接口文件、Java源文件,生成.class文件。
(4)转换所有class文件,生成classes.dex文件。Android虚拟机的可执行文件为dex格式,所以需要此步骤。
(5)打包生成apk。打包后的res文件夹(除res/raw资源被原装不动地打包进APK之外)、打包后类文件(.dex文件)、libs文件(包括.so文件,当然很多工程都没有这样的文件,如果你不使用C/C++开发的话)、resources.arsc、assets、AndroidManifest.xml打包成apk文件。
(6)对apk文件进行签名。
(7)对签名后的apk文件进行对其处理。在 Android SDK 中包含一个名为 “zipalign” 的工具,它能够对打包后的 app 进行优化。 即对签名后的apk进行对齐处理。
app启动优化
Android 黑白屏优化
(1)修改AppTheme,设置系统取消预览(空白窗体)为true,或者设置空白窗体颜色为透明色 (不被推荐,在不同手机上可能会有不同的效果)
<!--设置 取消预览窗口-->
<item name="android:windowDisablePreview">false</item>
<!--设置 窗口背景色透明-->
<item name="android:windowIsTranslucent">true</item>
(2)
自定义继承AppTheme的主题,将Activity的theme 设置为自定义的主题,在Activity的onCreate方法中在super.onCreate 和 setContentView 方法之前调用setTheme方法,将主题设置为最初的AppTheme
1 自定义继承AppTheme的主题(drawble设置的是layer-list的xml)
<style name="AppTheme.LauncherTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="android:windowBackground">@drawable/abc_vector_test</item>
<item name="android:windowFullscreen">true</item>
<item name="windowNoTitle">true</item>
</style>
2 将Activity的theme 设置为自定义的主题
<activity
android:name=".SplashActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.LauncherTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
3 在Activity的onCreate方法中在super.onCreate 和 setContentView 方法之前调用setTheme方法,将主题设置为最初的AppTheme
class SplashActivity : AppCompatActivity()
override fun onCreate(savedInstanceState: Bundle?)
setTheme(R.style.AppTheme_LauncherTheme)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
(3)Application、Activity的onCreate方法中减少耗时操作或不必要的三方初始化( 可以通过异步线程或者懒加载的方式,减少onCreate方法中的耗时操作)。
(查看耗时时间:
方式1:可以通过adb 命令 查看启动耗时adb shell am start -W com.example.SplashActivity
, 会显示三个信息:ThisTime: 最后一个Activity启动时间 totalTime:一系列Activity启动时间 waitTime:总启动时间,包括了系统在冷启动时,需要加载的app信息到内存的时间, 这里只需要看totalTime即可
方式2 :
val file=File(Environment.getDataDirectory().toString(),"time.trace"); Debug.startMethodTracing(file.absolutePath); init() Debug.stopMethodTracing()
)
进程保活
进程保活的关键点有两个,
1、是进程优先级的理解,优先级越高存活几率越大。
2、是弄清楚哪些场景会导致进程会 kill。
所以我们需要 (1)让进程优先级提高(oom_adj数值越大优先级越低,oom_adj 存储在 proc/PID/oom_adj 文 件中,直接 adb shell 进入手机根目录查看这个文件即可) (2)在进程被kill之后能够唤醒
ps:进程优先级:前台进程(Foreground process)、可见进程、服务进程、后台进程、空进程,优先级依次降低。
看下进程被kill的场景
场景一
1.点击 home 键使 app 长时间停留在后台,内存不足被 kill。
这种情况下,需要你的app至少运行一个service,通过service启动的时候调用Service.startForground() 设置成前台服务。
startForground的方式在Android4.3 之后会在通知栏弹出一个通知,解决这个问题可以同时再开启一个service,然后新启动的service stop掉后,通知栏也会消失
Android 7.1 之后google修复了这个bug,暂时没有好的解决方案。
场景二
2、锁屏后,为节约资源,省电机制会kill掉后台进程。
可以注册广播通过对锁屏(ACTION_SCREEN_OOF)解锁(ACTION_USER_PRESENT)事件监听,锁屏后开启一个1px的透明Activity,让这个进程优先级最高,解锁后销毁这个Activity
场景三
3、手机自带清理工具的清理。
加入手机白名单
其他方式
开启双进程守护
如何缩减 APK 包大小?
1、代码
不使用的代码及时删除
使用 proguard 混淆代码,它会对不用的代码做优化,并且混淆后也能够减少安装包的大小。
native code 的部分,大部分只支持 armabi 与 x86 的架构即可
2、资源
使用lint 工具查找没使用到的资源,去除不使用的图片、string、xml等
图片资源可以使用webp 格式图片代替,另外还可以使用tinypng对图片进行压缩预处理
so库
so库可以只支持armabi-v7a即可
进程保活
当前业界的 Android 进程保活手段主要分为** 黑、白、灰 **三种,其大致的实现思路如下:
黑色保活:不同的 app 进程,用广播相互唤醒(包括利用系统提供的广播进行唤醒)
白色保活:启动前台 Service
灰色保活:利用系统的漏洞启动前台 Service
黑色保活
所谓黑色保活,就是利用不同的 app 进程使用广播来进行相互唤醒。举个 3 个比较常见的场 景:
场景 1:开机,网络切换、拍照、拍视频时候,利用系统产生的广播唤醒 app
场景 2:接入第三方 SDK 也会唤醒相应的 app 进程,如微信 sdk 会唤醒微信,支付宝 sdk 会唤醒支付宝。由此发散开去,就会直接触发了下面的 场景 3
场景 3:假如你手机里装了支付宝、淘宝、天猫、UC 等阿里系的 app,那么你打开任意一个 阿里系的 app 后,有可能就顺便把其他阿里系的 app 给唤醒了。(只是拿阿里打个比方,其 实 BAT 系都差不多)
白色保活
白色保活手段非常简单,就是调用系统 api 启动一个前台的 Service 进程,这样会在系统的 通知栏生成一个 Notification,用来让用户知道有这样一个 app 在运行着,哪怕当前的 app 退到了后台。如LBE 和 QQ 音乐这样
灰色保活
灰色保活,这种保活手段是应用范围最广泛。它是利用系统的漏洞来启动一个前台的 Service 进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个 Notification,看起来就 如同运行着一个后台 Service 进程一样。这样做带来的好处就是,用户无法察觉到你运行着 一个前台进程(因为看不到 Notification),但你的进程优先级又是高于普通后台进程的。那 么如何利用系统的漏洞呢,
大致的实现思路和代码如下:
(1):API < 18,启动前台 Service 时直接传入 new Notification();
(2):API >= 18 && API <25,同时启动两个 id 相同的前台 Service,然后再将后启动的 Service 做stop处理
(3)API >=25 暂无解决方案
//设置 service 为前台服务,提高优先级 if (Build.VERSION.SDK_INT < 18)
//Android4.3 以下 ,此方法能有效隐藏 Notification 上的图标
service.startForeground(GRAY_SERVICE_ID, new Notification()); else if(Build.VERSION.SDK_INT>18 && Build.VERSION.SDK_INT<25)
//Android4.3 - Android7.0,此方法能有效隐藏 Notification 上的图标 Intent innerIntent = new Intent(service, GrayInnerService.class); service.startService(innerIntent); service.startForeground(GRAY_SERVICE_ID, new Notification());
else
//Android7.1 google 修复了此漏洞,暂无解决方法(现状:Android7.1 以上
app 启动后通知栏会出现一条"正在运行"的通知消息) service.startForeground(GRAY_SERVICE_ID, new Notification());
熟悉 Android 系统的童鞋都知道,系统出于体验和性能上的考虑,app 在退到后台时系统并 不会真正的 kill 掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。 在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要 kill 掉哪些进程, 以腾出内存来供给需要的 app。这套杀进程回收内存的机制就叫 Low Memory Killer ,它是 基于 Linux 内核的 OOM Killer(Out-Of-Memory killer)机制诞生。
其他(加入白名单)
有些手机厂商把这些知名的 app 放入了自己的白名单中,保证了进程不死来提高用户体验 (如微信、QQ、陌陌都在小米的白名单中)。如果从白名单中移除,他们终究还是和普通 app 一样躲避不了被杀的命运
Intent Service 有什么用
Intent Service 可用于执行后台耗时的任务,当任务执行完成后会自动停止,同时由于IntentService是服务的原因,不同于Service,IntentService 可自动创建子线程来执行任务,导致它的优先级比单纯的线程要高,不容易被系统杀死,所以IntentService 比较适合执行一些高优先级的后台任务
App 启动流程图
涉及到4个进程:Launcher进程、systemServer进程、zygote进程和app进程
几个步骤:
首先因为systemServer中会持有ActivityManagerService(AMS),而ams中是有一些Binder通信注册的服务的,所以在启动activity的过程中会通过ams进行调度。
1 从launcher 进程向system server进程发起startActivity的请求,
2 systemServer接收请求会通知给zygote进程,通过zygote进程for新的app进程也就是我们的应用进程。
3 App进程 attach application,最终交给system-server进程的ApplicationThreadProxy进行处理
4 ApplicationThreadProxy会发起scheduleLaunchActivity的请求,交给App进程的ActivityThread来handleLaunchActivity,调用activity的生命周期。
几种图片库区别
Picasso 与 Glide对比
1、相似
Glide 和 Picasso在API的调用上 非常相似,且都支持图片的内存缓存,都是非常优秀的图片加载框架,可以说Glide是Picasso的升级,在性能上有所提升。
2、差异
(1)缓存方式不同
首先Picasso是2级缓存,它支持内存缓存而不支持磁盘缓存;而Glide是3级缓存,也就是说依次按照内存 > 磁盘 >网络的优先级来加载图片。
picasso 默认格式是ARGB-8888 加载的,Glide 默认是按照RGB-565加载的,内存上glide更节约,但是加载质量没有picasso好
picasso 是图片的大小缓存的, Glide 是按 ImageView 的大小来缓存的。
(2)生命周期的问题
Glide 的 with() 方法不光接受 Context,还能接收 Activity 和 Fragment的实例,这样做的好处是:图片加载会和 Activity/Fragment 的生命周期保持一致,比如 Paused 状态在暂停加载,在 Resumed 的时候又自动重新加载
(3)GIF
Glide可以加载GIF动态图,而Picasso不能。
(4)库的大小不同
Picasso的大小大概100k,而Glide的大小大概500k。
Glide 与Fresco 对比
Fresco和Glide的区别
1 缓存
Fresco 图片存储在安卓系统的匿名共享内存, 而不是虚拟机的堆内存中, 图片的中间缓冲数据也存放在本地堆内存,所以, 应用程序有更多的内存使用, 不会因为图片加载而导致 oom, 同时也减少垃圾回收器频繁调用回收 Bitmap,导致的界面卡顿, 性能更高.
Fresco 的三级缓存: 1 disk 资源缓存( 文件缓存) 2 Encode 资源缓存(未解码图片缓存)3.Bitmap 资源缓存
Glide:只有内存和磁盘缓存
2 使用
Fresco 需要使用库中提供的SimpleDraweeView控件,Glide不需要特殊控件,且使用简单。
3 性能
Fresco设计中有个有一个image pipeline模块, 可以最大限度节省网络下载图片, 在不考虑缓存的情况下, Fresco也比Glide快很多。
4 使用场景
Fresco 适合app中有大量图片的
5 库的大小
Fresco是重量级的,比较臃肿,Glide是轻量级的。
CPU 与GPU
CPU:负责计算显示内容,比如视图创建、布局计算、图片解码、文本绘制、
GPU:负责栅格化处理(UI元素绘制到屏幕上)
每隔16ms发出vsync 信号触发ui渲染
大多数Android手机设备是60fps,有的是120fps
以上是关于Android 其他问题点的主要内容,如果未能解决你的问题,请参考以下文章
Jquery Mobile & Phonegap 3 上的白色闪光和过渡问题