bingoogolapple.github.io
bingoogolapple.github.io copied to clipboard
Android 面试笔记
ListView
- Adapter 适配器模式
- 保证数据和 ListView 的分离,ListView 相当于 MVC 中的 V,Adapter 相当于 MVC 中的 C
- getCount、getView
- RecycleBin 机制
- 复用 item,保障了 ListView 中存储很多数据时不会导致 OOM
- ListView 优化
- 复用 convertView
- 使用 ViewHolder,将其作为 convertView 的 tag,避免多次 findViewById(二叉树遍历)
- 数据源中有多种数据类型时使用 getViewTypeCount、getItemViewType
- getView 方法中少做耗时操作,保证 ListView 的滑动流畅性
- 监听 ListView 滑动事件,停止滑动时才加载图片
- 异步加载图片,三级缓存
- 避免半透明,半透明绘制需要大量乘法计算。实在要弄半透明的话可以在滑动的时候把半透明设置成不透明,滑动完再重新设置成半透明
MVC/MVP/MVVM
MVC
- Model:业务逻辑处理
- 对数据库的操作
- 对网络等的操作
- 业务计算、变更等操作
- View:处理数据显示
- XML 布局文件,也可以使用 JavaScript + HTML 等的方式作为 View 层
- 负责显示从 Controller 上获取到的数据,但 XML 布局作为 View 来说功能很无力,所以通常 Activity 也会承担一部分 View 的工作
- Controller:处理用户交互问题
- Android 中的控制层主要由 Activity 充当
- 将从 Model 获取到的数据绑定到 View 上,在 Model 和 View 之间起中间桥梁的作用
- 监听用户的触摸、输入等操作
Android 中的 Adapter 也是使用的 MVC 模式,自定义的 Adapter 相当于 Controller ,ListView 相当于 View
缺点
- Activity 太重,既充当了 Controller,又充当了 View
- View 和 Model 之间有交互
MVP
- Model:业务逻辑处理
- 对数据库的操作
- 对网络等的操作
- 业务计算、变更等操作
- View:处理数据显示
- XML 布局文件、Activity、Fragment、View,也可以使用 JavaScript + HTML 等的方式作为 View 层
- 监听用户的触摸、输入等操作,在有用户交互等操作时调用 Presenter 层的方法
- Presenter:负责完成 View 与 Model 之间的交互
- Presenter 和 View 之间是双向的交互,通过接口持有对方的引用
- 从 Model 层获取数据,并且处理之后传递给 View 层
优点
- View 和 Model 分离,View 层不再涉及业务逻辑代码,某层的改动不需要到处去修改代码
- 方便测试
缺点
- 代码量增加
MVVM
- Model:同 MVP 里的 Model
- View:处理数据显示
- XML 布局文件、Activity、Fragment、View
- 监听用户的触摸、输入等操作,在有用户交互等操作时调用 ViewModel 层的方法
- ViewModel
- 注意这里的 Model 指的是 View 的 Model,跟 MVVM 中的一个 Model 不是一回事。所谓 View 的 Model 就是包含 View 的一些数据属性和操作
- 这种模式的关键技术是数据绑定(Data Binding),View 的变化会直接影响 ViewModel,ViewModel 的变化也会直接体现在 View 上
优点
- 支持 View 和 ViewModel 的双向数据绑定
缺点
- 据绑定使得 Bug 很难被调试。当看到界面异常时,有可能是 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得一个位置的 Bug 被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了
- 对于过大的项目,数据绑定需要花费更多的内存
可以根据具体业务复杂情况来划分,一个项目具有多种风格的模式
Activity 生命周期
- onStart 和 onStop 是从 Activity 是否可见这个角度来回调的
- onResume 和 onPause 是从 Activity 是否位于前台这个角度来回调的
- 从 Activity A 打开 Activity B 时,回调是:A onPause -> B onCreate -> B onStart -> B onResume -> A onStop
- 如果 Activity B 采用了透明主题,Activity A 不会回调 onStop 方法
- 不能在 onPause 中做重量级的操作,因为必须 onPause 执行完成以后新 Activity 才能 onResume
- onPause 和 onStop 都不能执行耗时的操作,尤其是 onPause。如果业务需求非要在 onPause 或者 onStop 里执行某操作,我们应当尽量在 onStop 中做操作,从而使得新 Activity 尽快显示出来并切换到前台
销毁重建
onActivityResult 在 onRestoreInstanceState 之后执行,在 onResume 之前
屏幕旋转
滑动返回
- B onPause -> B 里面控件 dispatchDraw -> A onStart -> A onResume -> A 里面控件 dispatchDraw -> B onStop
- 相当于 onPause 后,该 Activity 里的控件会重绘一次,onResume 之后 Activity 里的控件也会重绘一次
Activity 四种启动模式以及应用场景
standard
- 不论当前任务栈中是否存在该 Activity,都会新建一个 Activity。如任务栈为 A -> B,此时再启动 B 那么任务栈为 A->B->B
- 这种启动模式的 Activity 可以被实例化多次,一个任务栈可以包含多个这种 Activity 的实例
应用场景
- 大部分界面都是这种启动模式
singleTop
- 如果当前要创建的 Activity 就在任务栈的顶端,那么不会创建新的 Activity,仅仅调用 Activity 的onNewIntent
- 如果不在栈顶或者栈中没有该 Activity,那么还是会创建新的 Activity,如任务栈为 A -> B。启动 B,任务栈变为 A->B。如果启动 A,那么任务栈为 A->B->A
- 这种启动模式的 Activity 可以被实例化多次,一个任务栈可以包含多个这种 Activity 的实例
应用场景
- 消息推送
- 通知栏弹出 Notification,点击 Notification 跳转到指定 Activity,但是如果我现在页面就停留在那个指定的 Activity,会再次打开我当前的 Activity,这样返回的时候回退的页面和当前页面一样,感官上就会很奇怪,此时就可以用 singleTop 来解决
singleTask
- 这种启动模式的 Activity,它在启动的时候,会先在系统中查找相同属性值 taskAffinity 的任务栈是否存在,如果存在就会在这个任务栈中启动,否则就会在新的任务栈中启动。因此, 如果我们想要这种启动模式的 Activity 在新的任务栈中启动,就要为它设置一个独立的 taskAffinity 属性值
- 如果现有任务栈当中不存在该 Activity 的实例,将会创建新的实例放入栈中
- 如果现有任务栈当中已经存在该 Activity 的实例,那么不会创建新的 Activity,仅仅调用 Activity 的onNewIntent,同时也会销毁该 Activity 上面的所有 Activity,如任务栈为 A->B->C,启动 B ,那么任务栈就会变为 A->B
- 这种启动模式的 Activity,在同一个任务当中只会存在一个实例
应用场景
- 微信的主界面(一般应用主界面都 singleTask)
- 你打开微信主界面(在栈底)后,进入朋友圈界面(在栈顶),然后按 Home 键回桌面
- 打开网易新闻。将网易新闻的一条新闻分享给微信好友,那么就按照「分享 -> 微信 -> 好友 A -> 分享给他 -> 留在微信
- 接着会跳转微信的主界面,而不是你原本所在的朋友圈
singleInstance
- 仅存在一个实例,独自占用一个任务栈。也就是说这种启动模式的 Activity 的任务栈中始终只会有一个 Activity,通过这个 Activity 再打开的其它 Activity 也会被放入到别的任务栈中
应用场景
- 闹铃的响铃界面
- 设置了一个闹铃:上午 6 点
- 在上午 5 点 58 分时,你启动了闹铃设置界面,然后按 Home 键回桌面
- 在上午 5 点 59 分时,你在微信和朋友聊天
- 在 6 点时,闹铃响了,并且弹出了一个对话框形式的 AlarmAlertActivity(启动模式就是 singleInstance)提示你到 6 点了
- 你按返回键,回到的是微信的聊天界面,这是因为 AlarmAlertActivity 所在的任务栈只有它一个 Activity,因此按返回键时该任务栈就空了。如果是以 singleTask 打开 AlarmAlertActivity,那么当闹铃响了的时候,按返回键应该进入闹铃设置界面
Activity A 打开了 B,B 打开了 C,C 里面有个关闭按钮,怎样做到点击 C 里的关闭按钮时,就直接关闭 BC 回到 A
- 方案一:将 A 的启动模式设为 singleTask,在 C 中 startActivity A
- 方案二:自定义 AppManager 来管理当前应用的 Activity,在 C 中调用 appManager.finishActivity 方法来关闭 B 和 C
- 方案三:B 通过 startActivityForResult 的方式打开 C,在 B 的 onActivityResult 中关闭 B
- 方案四:通过 LocalBroadcastReceiver、EventBus、RxBus
- 方案五:在 AndroidManifest.xml 中为 C 增加下面的 xml 代码块,然后在 C 中执行下面的 Java 代码块
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="ActivityA" />
Intent parentIntent = getSupportParentActivityIntent();
if (supportShouldUpRecreateTask(parentIntent)) {
TaskStackBuilder.create(this).addNextIntentWithParentStack(parentIntent).startActivities();
} else {
supportNavigateUpTo(parentIntent);
}
推荐使用方案一或者方案二,方案二不适合需要回传参数的情况
事件传递
Activity
- 如果 Activity 的 dispatchTouchEvent 方法返回 true 或者 false,则直接消费事件,不会传递到 Activity 里的其他控件,并且 Activity 自身的 onTouchEvent 方法也不会被调用
- 必须调用 super.dispatchTouchEvent(event) 时,Activity 的 onTouchEvent 方法才有可能会被调用「Activity 里的其他控件都不消费该事件时」
ViewGroup
- 事件能够传递到这个 View 的话,dispatchTouchEvent 方法肯定会被调用。如果 ViewGroup 的 dispatchTouchEvent 方法返回 true 或者 false,则直接消费事件,不会传递到 ViewGroup 里的其他控件,并且 ViewGroup 的 onTouchEvent 和 onInterceptTouchEvent 方法也不会被调用,当前 Activity 的 onTouchEvent 方法也不会被调用。必须调用 super.dispatchTouchEvent(event) 时,ViewGroup 的 onTouchEvent 方法才有可能会被调用「ViewGroup 里的其他控件都不处理该事件时」
- 如果 onInterceptTouchEvent 返回了 true,那么本次 touch 事件之后的所有 action 都不会再向深层的 View 传递,通通都会传给该 ViewGroup 的 onTouchEvent,就是说父层已经截获了这次 touch 事件,「之后的 action 也不必询问 onInterceptTouchEvent,在这次的 touch 事件之后发出的 action 时 onInterceptTouchEvent 不会再次调用,并且所有子控件都会收到 ACTION_CANCEL 事件」,直到下一次重新 ACTION_DOWN。onInterceptTouchEvent 拦截器 return super.onInterceptTouchEvent()和 return false 是一样的,不会拦截事件,事件会继续往子 View 的 dispatchTouchEvent 传递
- 收到 ACTION_DOWN 事件时调用 getParent().requestDisallowInterceptTouchEvent(true) 告诉父组件不要拦截我的事件,调用该方法后所有父控件的 onInterceptTouchEvent 都不会再被调用,直到下一次重新 ACTION_DOWN 或调用 getParent().requestDisallowInterceptTouchEvent(false),在 dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent 或 OnTouchListener 的 onTouch 方法里调该方法都可以
- ACTION_DOWN 事件在哪个控件消费了,那么 ACTION_MOVE 和 ACTION_UP 就会从上往下(通过 dispatchTouchEvent)做事件分发往下传,并且只会传到这个控件,不会继续往下传,如果 ACTION_DOWN 事件是在 dispatchTouchEvent 消费,那么事件到此为止停止传递,如果 ACTION_DOWN 事件是在 onTouchEvent 消费的,那么会把 ACTION_MOVE 或 ACTION_UP 事件传给该控件的 onTouchEvent 处理并结束传递
- 如果给 View 设置了 OnTouchListener,当 onTouch 返回 false 时,onTouchEvent会被调用;当 onTouch 返回 true 时,onTouchEvent 不会被调用,那么如果设置了 OnClickListener,onClick 也不会被调用,因为 OnClickListener 的 onClick 是在 onTouchEvent 里面触发的
- clickable、longClickable、enable 属性不会影响 ViewGroup 的事件传递
View
- 与 ViewGroup 基本一样,只是 View 中没有 onInterceptTouchEvent 方法
- 与 ViewGroup 的另一个区别是 enable 属性不会影响 View 的事件传递,只要 clickable 和 longClickable 任意一个为 true,View 就能正常接收触摸事件
处理 requestDisallowInterceptTouchEvent(true) 失效
- ViewGroup 的 dispatchTouchEvent 在 ACTION_DOWN 时有调用 resetTouchState 方法,重置了 mGroupFlags 标志位 FLAG_DISALLOW_INTERCEPT,所以需要在收到 ACTION_DOWN 事件时,在 dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent 或 OnTouchListener 的 onTouch 方法里调用 requestDisallowInterceptTouchEvent(true)
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
自定义控件、自定义组合控件
- 在 attrx.xml 文件中通过 declare-styleable 添加自定义属性
- 在构造方法中获取 TypedArray 获取自定义属性
- onFinishInflate 方法中校验子控件个数是否合法
-
onMeasure 方法中测量宽高
- measureChild 让子 View 测量宽高
- resolveSize(expectSize, widthMeasureSpec) 计算尺寸
- UNSPECIFIED 父控件不对子控件施加任何束缚,子控件可以得到任意想要的大小,如 ScrollView
- EXACTLY 父控件决定子控件的确切大小,子控件将被限定在给定的边界里而忽略它本身大小,如 100dp、fill_parent、match_parent
- AT_MOST 表示子 View 的大小最多是多少,这样子 View 会根据这个上限来设置自己的尺寸,例如 wrap_content
- setMeasuredDimension 设置宽高
- onLayout 方法中调用子控件的 layout 方法来设置子控件的位置
- draw 方法绘制背景
- onDraw View 的话在该方法中绘制自己
- dispatchDraw ViewGroup 的话在该方法中绘制自己
- invalidate 触发 draw 方法,如果视图大小没有发生变化,则不会触发 measure 和 layout。invalidate 只能在 UI 线程中调用,在非 UI 线程中需要使用 postInvalidate
- requestLayout 触发 measure 和 layout,但不会触发 draw 方法
Handler
- 在 Android 中只有创建 UI 的原始线程才能更新 UI,即只有主线程才能更新 UI
- 主要的两种使用方式
- sendMessage 一个 Message
- post 一个 Runnable,该方式最终还是通过 sendMessageDelayed(getPostMessage(runnable), 0) 实现的,在 getPostMessage 中将 runnable 赋值给 Message 的 callback
- 在创建一个 Handler 的时候 Handler 会绑定到当前代码执行的线程
- 子线程是不自带 Looper 的,想要 UI 线程或子线程与其他子线程通信需要在子线程内开启 Looper,分三步
- 每个线程只能有一个 Looper 实例,初始化 Looper 前先通过 Looper.myLooper() 获取是否已经有 Looper,如果没有则通过 Looper.prepare() 来初始化 Looper
- 初始化 new Handler(Looper.myLooper())
- 调用 Looper.loop(),当前线程会进入阻塞状态
原理
- ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据
- Looper 是用来封装消息循环和 MessageQueue 的一个类
- Looper.prepare() 为当前线程创建一个新的 Looper 实例,并将该 Looper 实例放入到 ThreadLocal 中
- Looper 的构造方法中初始化 MessageQueue
- Handler 的构造方法中通过 Looper.myLooper() 获取当前线程的 Looper 实例,该构造方法可以接收一个 Callback 参数
- Looper.myLooper() 方法内部又是从 ThreadLocal 中获取当前线程的 Looper 实例
- 调用 Looper.loop() 方法后,当前线程会进入阻塞状态
- Looper.loop() 方法内部开启了一个死循环,不断的从 MessageQueue 中获取 Message
- 从 MessageQueue 中获取 Message 后,调用 Message 的 target 属性的 dispatchMessage 方法
- Message 的 target 属性就是 Handler
- Handler 的 post 和 sendMessage 方法最终会调用 enqueueMessage 方法将 Message 放入 MessageQueue 中
- post 会通过 getPostMessage 方法获取一个 Message 实例,并将 Runnable 赋值给 Message 的 callback 属性
- 在 enqueueMessage 方法中将 Handler 赋值给 Message 的 target 属性
- 在 dispatchMessage 方法中判断 Message 的 callback(Runnable 类型) 属性是否为空,如果不会空则直接调用 Runnable 的 run() 方法
- 如果 Message 的 callback 属性为空,在判断构造方法中传入的 Callback 是否为空,如果不为空则调用 Callback 的 handleMessage 方法
- 当 Callback 的 handleMessage 方法返回 true,表示已经处理了该消息,Handler 自己的 handleMessage 不会被执行
- 如果构造方法中传入的 Callback 为空,则直接调用 Handler 自己的 handleMessage 方法来处理消息
AsyncTask、HandlerThread、IntentService
AsyncTask
// 核心线程数
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
// 最大线程数
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
// 超时时间,当线程数超过核心线程数时,超过这个时间的空线程就会被销毁,直到线程数等于核心线程数
private static final int KEEP_ALIVE_SECONDS = 30;
- 封装线程池和 Handler 的轻量级异步类
- onPreExecute 耗时任务执行前,在 UI 线程
- doInBackground 执行耗时任务,在 子线程
- publishProgress 发送耗时任务进度
- 内部通过 Handler 发送消息 MESSAGE_POST_PROGRESS,在 Handler 的 handleMessage 方法中调用 AsyncTask 的 onProgressUpdate 方法
- onProgressUpdate 耗时任务进度,在 UI 线程
- publishProgress 发送耗时任务进度
- onPostExecute 耗时任务执行结束,在 UI 线程
- cancel 取消耗时任务
AsyncTask 缺陷
- 并行执行的最大任务个数 MAXIMUM_POOL_SIZE
- sPoolWorkQueue 中的任务数目超过 128 会抛异常
AsyncTask 版本差异
- CORE_POOL_SIZE、MAXIMUM_POOL_SIZE、KEEP_ALIVE_SECONDS 在不同的版本上值不一样
- 1.6 之前是串行执行的,同时只能执行 1 个任务
- 1.6-2.3 版本是并行执行的,最大并行任务数为 MAXIMUM_POOL_SIZE
- 3.0 后增加了 executeOnExecutor 方法供开发者选择串行还是并行执行,默认情况是串行
- AsyncTask.SERIAL_EXECUTOR
- AsyncTask.THREAD_POOL_EXECUTOR
- 其实 SERIAL_EXECUTOR 的 execute 方法中也是调用 THREAD_POOL_EXECUTOR 的 execute 方法
HandlerThread
- HandlerThread 继承自 Thread,就是 Thread 加上一个 Looper
- 通过 HandlerThread 的 Looper 对象传递消息给 Handler 对象,可以在 handleMessage 方法中执行异步任务
- 优点是不会阻塞 UI 线程,避免多次创建销毁线程
- 缺点是不能同时执行多个任务
- quite()、quitSafely() 方法退出消息循环,内部调用的是 looper.quit()、looper.quitSafely() 方法
- 作为工作线程,建议将优先级设置低一些 Process.THREAD_PRIORITY_BACKGROUND
- 应用场景:串行执行一些列耗时任务
handlerThread = new HandlerThread("handler-thread") ;
//开启一个线程
handlerThread.start();
//在这个线程中创建一个handler对象
handler = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
//这个方法是运行在 handler-thread 线程中的 ,可以执行耗时操作
}
};
IntentService
- 继承自 Service,内部封装了 HandlerThread 和 Handler,不需要开发者单独再开启线程
- onStart 时,将启动请求发给 Handler 进行处理
- 不需要开发者关闭它,执行完串行任务队列里的所有异步任务后它会自己关闭自己
- 在 handleMessage 方法中调用了 onHandleIntent 方法后会调用 stopSelf(msg.arg1),这里的 msg.arg1 就是 onStart 和 onStartCommand 方法中传进来的参数 startId
- 如果 startId 是最近 onStartCommand 被调用时的 startId 服务将被停止
- 开发者只需要在空构造方法中调用 super("线程名称"),在抽象方法 onHandleIntent 执行耗时任务即可
- setIntentRedelivery 方法,如果为 true,onStartCommand 将会返回 START_REDELIVER_INTENT,如果 onHandleIntent 返回之前进程死掉了,那么进程将重启,Intent 重新投递,如果有大量的 Intent 投递只保证最近的 Intent 会被重投递
- 应用场景:串行执行一些列耗时任务
public class DownloadImgService extends IntentService {
public DownloadImgService() {
super("download-img");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
try {
Log.d(DownloadImgService.class.getSimpleName(), Thread.currentThread().getName() + " - 下载图片 - " + intent.getStringExtra("url"));
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Bitmap
- 如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存
- 使用缓存。LruCache 内部通过 LinkedHashMap 来缓存,trimToSize 方法移除最近最少使用
- BitmapFactory.Options.inJustDecodeBounds = true 只获取图宽高,但不把图片加载到内存中
- 压缩图片、缩略图 BitmapFactory.Options.inSampleSize 采样,不加载原始图片到内存中
- 及时回收
- Android 2.3 之前 Bitmap 的引用是放在堆中的,而 Bitmap 的数据部分是放在栈中的,需要用户调用recycle 方法手动进行内存回收,而在 Android 2.3 之后,整个 Bitmap 包括数据和引用都放在了堆中
- Android 中一张图片(Bitmap)占用的内存 = 图片长度 x 图片宽度 x 单位像素占用的字节数
- 图片长度和图片宽度的单位是像素
- 单位像素占用的字节数由其参数 BitmapFactory.Options 的 inPreferredConfig 变量决定
- 无论图片质量好坏,加载到内存中占用内存的大小只与图片大小、inPreferredConfig 图片质量参数配置有关,压缩图片只是让打包的 apk 减小,而运行时的内存大小是无关的
- 占用的内存代码直接计算 bitmap.getRowBytes() * bitmap.getHeight()
- drawable 目录一定要放对
- PC 上看 480 * 800 的图片,读入内存 bitmap 后 getWidth: 640, getHeight: 1067。drawable 存放目录错误导致
- drawable 目录,Android 图片处理有自动缩放功能,如果 xhdpi 的机型,读取 hdpi 目录,则会认为图适配的机型是 hdpi,用在 xhdpi 的机型需要等比例放大
内存泄漏
- 长生命周期的对象持有短生命周期对象的引用
引起内存泄露的场景
- 静态集合类引起内存泄漏
- 当集合里面的对象属性被修改后,再调用 remove 方法时不起作用
- 监听器
- 各种连接资源未关闭造成的内存泄漏「例如数据库连接、Socket连接、BraodcastReceiver」
- 匿名内部类/非静态内部类和异步线程「例如程序员 A 负责 A 模块,调用了 B 模块的一个方法如: public void registerMsg(Object b) 这种调用就要非常小心了,传入了一个对象,很可能模块 B 就保持了对该对象的引用,这时候就需要注意模块 B 是否提供相应的操作去除引用」
- 单例模式「Android 中传入 context 时,应该用 context.getApplicationContext(),使用使用 Application 的 context」
避免内存泄漏
- 只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存,不必等到内存吃紧的时候
- BGAQRCode-Android 中避免 AsyncTask 内存泄露
- BGAPhotoPicker-Android 中避免 AsyncTask 内存泄露
- BGABanner-Android 中通过 静态内部类 + WeakReference 避免内存泄露
- BGAProgressBar-Android 中通过 静态内部类 + WeakReference 避免 Handler 导致内存泄漏
Service
https://github.com/bingoogolapple/AndroidCustomViewPlayground/blob/master/testservice/src/main/java/cn/bingoogolapple/testservice/service/TestService.java
Binder
https://github.com/bingoogolapple/AndroidCustomViewPlayground/blob/master/testservice/src/main/java/cn/bingoogolapple/testservice/service/TestService.java
https://www.zhihu.com/question/39440766/answer/89210950
计算一共有多少个 Context 的公式
- Context 数量 = Activity 数量 + Service 数量 + 1 个 Application
- Service 和 Application 都是继承自 ContextWrapper,Activity 继承自 ContextThemeWrapper
使用 Fragment 时 add 和 replace 的区别
TODO
内存模型
TODO
性能优化
TODO
多线程
- new Thread的弊端
- 每次 new Thread 新建对象性能差
- 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或 oom
- 缺乏更多功能,如定时执行、定期执行、线程中断
- Java 中四种线程池
- newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO、LIFO、优先级)执行
- Java 提供的四种线程池的好处
- 重用存在的线程,减少对象创建、消亡的开销,性能佳
- 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞
- 提供定时执行、定期执行、单线程、并发数控制等功能
Java 中怎样停止线程
- 非阻塞的情况
- 通过 flag 来标记是否要结束异步任务
- 阻塞的情况
- AsyncTask.cancel(mayInterruptIfRunning)
- FutureTask.cancel(mayInterruptIfRunning)
- Thread.interrupt()