Glide
1)listener方法传入的对象中,onException, onReady方法返回值的含义
如果返回true,表示事件已经被消费, 不会走到target的对应的方法中。 此时onException中设置placeHolder或者error图片的代码会失效。
2)如何解决某些网站在图片url后面增加token随机数,使得图片缓存失效
自定义GlideUrl,在getCacheKey方法中将url的随机数去掉。
3)变换
就是在transformation中的transform方法, 给定bitmap,经过变换后返回变换后的bitmap
4)Glide默认宽度全屏的问题
调用override方法,设置宽高为原始宽高
5)downloadOnly传宽高和target的区别
传宽高会使用Glide提供的target,内部又线程阻塞,所以只能在子线程中使用 ,
传target是要自己实现一个Target,可以在主线程中使用,也要自己实现内部逻辑.
6)缓存三级, Active缓存, LRU内存缓存, DiskLruCache磁盘缓存
内存泄漏的例子
资源对象没关闭造成的内存泄漏(如: Cursor、File等)
全局集合类强引用没清理造成的内存泄漏( static 修饰的集合)
接收器、监听器注册没取消造成的内存泄漏,如广播,eventsbus
Activity 的 Context 造成的泄漏,可以使用 ApplicationContext, 将Activity设置为static
Handler 造成的内存泄漏问题(一般由于 Handler 生命周期比其外部类的生命周期长引起的)
为应用申请更大内存,把manifest上的largdgeheap设置为true
减少内存的使用
①使用优化后的集合对象,比如SpaseArray;
②使用微信的mmkv替代sharedpreference;
③对于经常打log的地方使用StringBuilder来组拼,替代String拼接
④统一带有缓存的基础库,特别是图片库,如果用了两套不一样的图片加载库就会出现2个图片各自维护一套图片缓存
⑤给ImageView设置合适尺寸的图片,列表页显示缩略图,查看大图显示原图
⑥优化业务架构设计,比如省市区数据分批加载,需要加载省就加载省,需要加载市就加载失去,避免一下子加载所有数据
避免内存泄漏
编码规范上:
①资源对象用完一定要关闭,最好加finally
②静态集合对象用完要清理
③接收器、监听器使用时候注册和取消成对出现
④context使用注意生命周期,如果是静态类引用直接用ApplicationContext
⑤使用静态内部类
⑥结合业务场景,设置软引用,弱引用,确保对象可以在合适的时机回收
OOM
内存泄漏多了, 会发生;
加载大图;
大对象;
多线程的方式
继承Thread类,重写run函数方法
实现Runnable接口,重写run函数方法
实现Callable接口,重写call函数方法
HandlerThread
AsyncTask很老的一种
线程池
减少资源创建 => 减少内存开销,创建线程占用内存
降低系统开销 => 创建线程需要时间,会延迟处理的请求
提高稳定稳定性 => 避免无限创建线程引起的OutOfMemoryError【简称OOM】
CachedThreadPool
FixedThreadPool
SingleThreadExecutor
ScheduledThreadPool
为什么阿里巴巴不推荐使用官方给的线程池
FixedThreadPool和SingleThreadExecutor => 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而引起OOM异常
CachedThreadPool => 允许创建的线程数为Integer.MAX_VALUE, 可能会创建大量的线程,从而引起OOM异常
核心线程的大小要根据CPU核心数来, 一般是核心数+1 ;
队列要使用有界的, 避免OOM;
拒绝策略: 默认拒绝策略是抛出异常,可自定义策略忽略任务或者在主线程执行任务;
线程同步
volatile关键字,在get和set的场景下是可以的,由于get和set的时候都加了读写内存屏障,在数据可见性上保证数据同步。但是对于++这种非原子性操作,数据会出现不同步
synchronized对代码块或方法加锁,结合wait,notify调度保证数据同步
reentrantLock加锁结合Condition条件设置,在线程调度上保障数据同步
CountDownLatch简化版的条件锁,在线程调度上保障数据同步
cas=compare and swap(set), 在保证操作原子性上,确保数据同步
参照UI线程更新UI的思路,使用handler把多线程的数据更新都集中在一个线程上,避免多线程出现脏读
当然如果只是部分变量存在多线程修改的可能性 建议使用 原子类AtomicInteger AtomicBoolean等 这样会更方便一点。
IntentService
什么是 IntentService?有何优点?
IntentService是 Service 的子类,比普通的 Service增加了额外的功能。先看 Service 本身存在两个问题:
Service 不会专门启动一条单独的进程,Service 与它所在应用位于同一个进程中;
Service 也不是专门一条新线程,因此不应该在Service 中直接处理耗时的任务;
IntentService 特征
会创建独立的 worker线程来处理所有的Intent请求;
会创建独立的 worker 线程来处理onHandleIntent()方法实现的代码,无需处理多线程问题;
所有请求处理完成后,IntentService会自动停止,无需调用 stopSelf()方法停止 Service;
为Service 的 onBind()提供默认实现,返回 null;
为 Service的 onStartCommand 提供默认实现,将请求Intent添加到队列中;
RelativeLayout与LinearLayout的性能对比
(1)RelativeLayout慢于LinearLayout是因为它会让子View调用2次measure过程,而LinearLayout只需一次,但是有weight属性存在时,LinearLayout也需要两次measure。
(2)RelativeLayout的子View如果高度和RelativeLayout不同,会导致RelativeLayout在onMeasure()方法中做横向测量时,纵向的测量结果尚未完成,只好暂时使用自己的高度传入子View系统。而父View给子View传入的值也没有变化就不会做无谓的测量的优化会失效,解决办法就是可以使用padding代替margin以优化此问题。
(3)在不响应层级深度的情况下,使用Linearlayout而不是RelativeLayout。
广播的分类
有序广播和无序广播
BroadcastReceiver所对应的广播分两类:普通广播和有序广播。
普通广播:通过Context.sendBroadcast()方法来发送,它是完全异步的。
所有的receivers(接收器)的执行顺序不确定,因此所有的receivers(接收器)接收broadcast的顺序不确定。
这种方式效率更高,但是BroadcastReceiver无法使用setResult系列、getResult系列及abort(中止)系列API
有序广播:是通过Context.sendOrderedBroadcast来发送,所有的receiver依次执行。
BroadcastReceiver可以使用setResult系列函数来结果传给下一个BroadcastReceiver,通过getResult系列函数来取得上个BroadcastReceiver返回的结果,并可以abort系列函数来让系统丢弃该广播,使用该广播不再传送到别的BroadcastReceiver。
可以通过在intent-filter中设置android:priority属性来设置receiver的优先级,优先级相同的receiver其执行顺序不确定。
(取值范围:-1000~1000)
如果BroadcastReceiver是代码中注册的话,且其intent-filter拥有相同android:priority属性的话,先注册的将先收到广播。
有序广播,即从优先级别最高的广播接收器开始接收,接收完了如果没有丢弃,就下传给下一个次高优先级别的广播接收器进行处理,依次类推,直到最后。如果多个应用程序设置的优先级别相同,则谁先注册的广播,谁就可以优先接收到广播。
这里接收短信的广播是有序广播,因此可以设置你自己的广播接收器的级别高于系统原来的级别,就可以拦截短信,并且不存收件箱,也不会有来信提示音。
实现方法是:
<receiver android:name=".SmsReceiver">
<intent-filter android:priority="100">
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
Broadcast广播,注册方式主要有两种.
第一种是静态注册,也可成为常驻型广播,这种广播需要在Androidmanifest.xml中进行注册,这中方式注册的广播,不受页面生命周期的影响,即使退出了页面,也可以收到广播这种广播一般用于想开机自启动啊等等,由于这种注册的方式的广播是常驻型广播,所以会占用CPU的资源。
第二种是动态注册,而动态注册的话,是在代码中注册的,这种注册方式也叫非常驻型广播,收到生命周期的影响,退出页面后,就不会收到广播,我们通常运用在更新UI方面。这种注册方式优先级较高。最后需要解绑,否会会内存泄露
组件的安全性
Activity,Service,Broadcast通用做法:
xml中配置exported=false
自定义权限
广播特有:
可以使用LocalBroadcastManager;
使用显式广播,即不用action来匹配,用class来匹配
发送时,指定接收的包名
ActivityThread的main方法做了什么
public static void More ...main(String[] args) {
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
// 初始化应用中需要使用的系统路径
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
//增加一个保存key的provider
Security.addProvider(new AndroidKeyStoreProvider());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
//为应用设置当前用户的CA证书保存的位置
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
//设置进程的名称
Process.setArgV0("<pre-initialized>");
// 创建主线程Looper
Looper.prepareMainLooper();
//创建ActivityThread 对象
ActivityThread thread = new ActivityThread();
//
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// 开启主线程looper
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
//为应用设置当前用户的CA证书保存的位置
创建binder线程,用来后续发送msg到looper中
创建主线程Looper
创建ActivityThread对象
将ApplicationThread对象注册到AMS
开启主线程Looper
Android自定义View注意事项
让View支持wrap_content
普通View的MeasureSpec的创建规则
childLayoutParams/parentSpecMode | EXACTLY | AT_MOST |
---|---|---|
dp/px (EXACTLY) | EXACTLY /childSize | EXACTLY /childSize |
match_parent (EXACTLY) | EXACTLY /parentSize | AT_MOST /parentSize |
wrap_content (AT_MOST) | AT_MOST /parentSize | AT_MOST /parentSize |
直接继承View和ViewGroup的控件需要在onMeasure方法中处理wrap_content的方法。否则根据View的getChildMeasureSpec方法,wrap_content默认跟match_parent的表现一致。处理方法是在wrap_content的情况下设置一个固定的尺寸
//处理wrap_content的套路
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//处理WAP_CONTENT
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(200,200);
}else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(200, heightSize);
}else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSize, 200);
}
}
让View支持padding
直接继承View的控件需要在onDraw方法中处理padding,否则用户设置padding属性就不会起作用。直接继承ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素的margin对其造成的影响,不然将导致padding和子元素的margin失效。
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
//获取padding,然后根据实际情况处理就好
mPaddingLeft = getPaddingLeft();
mPaddingRight = getPaddingRight();
mPaddingTop = getPaddingTop();
mPaddingBottom = getPaddingBottom();
mWidth = getWidth() - mPaddingLeft - mPaddingRight;
mHeight = getHeight() - mPaddingTop - mPaddingBottom;
}
尽量不要在View中使用Handler
View中已经提供了post系列方法,完全可以替代Handler的作用。
@UiThread
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
...
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
public boolean postDelayed(Runnable action, long delayMillis) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.postDelayed(action, delayMillis);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().postDelayed(action, delayMillis);
return true;
}
...
}
-
View中如果有线程或者动画,需要及时停止
在View的onDetachedFromWindow方法可以停止线程和动画,因为当View被remove或是包含此View的Activity退出时,就会调用View的onDetachedFromWindow方法。如果不处理的话很可能会导致内存泄漏 -
View带有滑动嵌套时,需要处理好滑动冲突问题
-
在View的onDraw方法中不要创建太多的临时对象,也就是new出来的对象。因为onDraw方法会被频繁调用,如果有大量的临时对象,就会引起内存抖动,影响View的效果
SP
1.SharePreferences是线程安全的 里面的方法有大量的synchronized来保障。
2.SharePreferences不是进程安全的 即使你用了MODE_MULTI_PROCESS 。
3.第一次getSharePreference会读取磁盘文件,异步读取,写入到内存中,后续的getSharePreference都是从内存中拿了。
4.第一次读取完毕之前 所有的get/set请求都会被卡住 等待读取完毕后再执行,所以第一次读取会有ANR风险。
5.所有的get都是从内存中读取。
6.提交都是 写入到内存和磁盘中 。apply跟commit的区别在于
apply 是内存同步 然后磁盘异步写入任务放到一个单线程队列中 等待调用。方法无返回 即void
commit 内存同步 只不过要等待磁盘写入结束才返回 直接返回写入成功状态 true or false
7.从 Android N 开始, 不再支持 MODE_WORLD_READABLE & MODE_WORLD_WRITEABLE. 一旦指定, 会抛异常 。也不要用MODE_MULTI_PROCESS 迟早被放弃。
8.每次commit/apply都会把全部数据一次性写入到磁盘,即没有增量写入的概念 。 所以单个文件千万不要太大 否则会严重影响性能。
建议用微信的第三方MMKV来替代SharePreference
MMKV原理
mmap,增量写入
SQLite
查询:select * from table_name where condition;
select count(*) from table_name where condition;
delete from table_name where condition
update table_name set name=‘张三’ where condition;
sql之left join、right join、inner join的区别
left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录
right join(右联接) 返回包括右表中的所有记录和左表中联结字段相等的记录
inner join(等值连接) 只返回两个表中联结字段相等的行
FROM table1 LEFT JOIN table2 ON table1.field1 compopr table2.field2
SQLite是支持多线性查询, 但是不支持多线程修改.
多线程多个连接的时候, 数据库会被锁住, 只能通过一个连接来操作。 这时候就涉及线程同步问题,因为如果线程1 ,线程2 同时使用连接, 线程2先使用完成,然后关掉了连接, 线程1未完成会抛异常。
常见的做法是将连接和app同生命周期, 一直不关闭。
另一种是引用计数, 在线程使用连接时,引用数加1 , 在线程结束使用时, 引用数-1 , 当引用数为0时, 就可以关掉连接了。
数据库升级, 如果是新增字段,直接新增,将版本号+1
删除字段, 需要将原表备份成.bak,然后新建一个原表名, 表的结构少需要删除的字段, 然后将备份的数据导入到新建的表中。
如果跨版本太多, 需要一个版本, 一个版本的过渡,不可从最低版直接到最新版;
ContentProvider
本质操作数据库,可以提供给本应用或者其他应用数据。
理解ContentProvider原理
假设A应用提供了ContentProvider
安装的时候,ContentProvider会被注册到PKMS中被管理
在A应用启动的时候, 会安装ContentProvider, 所谓安装就是创建相关的数据结构,这样方便在应用内部使用ContentProvider时,不需要通过AMS获取;
在B应用要使用ContentProvider时,获取的时候通过AMS获取 ,如果A应用没启动, 则还需要启动A应用,直到启动完成, B应用才能拿到。B应用获取到ContentProvider之后, 就可直接使用了,而且多次使用不需要重新向AMS查询, 因为ContentProvider本身实现了Binder机制。
WebView
WebView的优化
设置缓存,提前初始化WebView,加载;
WebView加载js,WebView.loadUrl(js);
WebView与JS交互的原理
自定义View
2020跳槽大厂,最常问的9个自定义View面试题!
SurfaceView的双缓冲机制,因为SurfaceView的目的就是快速更新UI,但是如果太过于快, 还未画完,又刷新重绘,那么就会造成屏幕闪烁。因此SurfaceView有两个缓冲区交替使用,back和front,先将需要绘制的缓存到back和front,然后SurfaceView直接从缓冲区绘制即可。避免了屏幕闪烁。
性能优化
2020跳槽大厂,11道Android性能优化面试题你会几个
Handler的原理
- 为什么Looper循环没有阻塞主线程
Native层有休眠机制, 当没有消息时,会休眠让出CPU时间片。 - Looper循环中如何响应生命周期和交互事件
生命周期是binder线程向MessageQueue中发送消息,binder线程在loop开启之前创建,用来与AMS通信。
交互事件是外部进程通过管道唤醒Looper