《Android校招面试指南》之Android基础复习梳理
本文梳理自:https://lrh1993.gitbooks.io/android_interview_guide/content/一、Activity全方位解析1、横竖屏切换生命周期(意外中止生命周期)onPause()->onSaveInstanceState()-> onStop()->onDestroy()->onCreate()-&
本文梳理自:https://lrh1993.gitbooks.io/android_interview_guide/content/
一、Activity全方位解析
1、横竖屏切换生命周期(意外中止生命周期)
onPause()->onSaveInstanceState()-> onStop()->onDestroy()->onCreate()->onStart()->onRestoreInstanceState->onResume()
-
onCreate
和onRestoreInstanceState
方法来恢复Activity的状态的区别:- onRestoreInstanceState回调则表明其中Bundle对象非空,不用加非空判断
-
onSaveInstanceState
方法调用时机- 在onStop之前,和onPause没有既定的时序关系
- 该方法只在Activity被异常终止的情况下调用
2、Activity A 启动Activity B的生命周期方法调用的问题
-
A的onPause() -> B的onCreate() -> B的onStart() -> B的onResume() -> B显示,同时A在屏幕上不可见 -> A的onStop()
-
onPause中不能进行耗时操作,会影响到新Activity的显示。因为onPause必须执行完,新的Activity的onResume才会执行
3、onNewIntent(Intent intent)
-
参数Intent是启动复写onNewIntent的Activity时传入的Intent
-
在SingleTask/SingleInstance/SingleTop中都会回调
-
重新启动自身:
onPause -> onNewIntent -> onResume
onRestart()
不会执行
4、SingleTask()模式
- 可以在AndroidManifest中通过
TaskAffinity
指定任务栈-
taskAffinity属性的值为字符串,且中间必须有包名分隔符
-
只能搭配SingleTask()启动模式和allowTaskReparenting属性配对使用
-
二、Service全方位解析
1、Service种类
-
(1)按运行进程分
-
本地服务(Local Service):依附于主进程
- 主进程被Kill后,服务就会中止
- 应用:音乐播放器
-
远程服务(Remote Service):独立进程,不依附于主进程
- 配置:AndroidManifest中对应的Service标签下添加
android:process="remote"
- 应用:一些提供系统服务的Service
- 配置:AndroidManifest中对应的Service标签下添加
-
-
(2)按运行类型分
- 前台服务:
- 会在通知栏显式onGoing的Notification
- 后台服务:
- 默认为后台服务
- 不会在通知栏显式
- 前台服务:
-
(3)按使用方式分类
类别 | 区别 |
---|---|
startService 启动的服务 | 主要用于启动一个服务执行后台任务,不进行通信。停止服务使用stopService |
bindService 启动的服务 | 方法启动的服务要进行通信。停止服务使用unbindService |
同时使用startService 、bindService 启动的服务 | 停止服务应该同时使用stopService 与unbindService |
2、Service的生命周期
3、三种不同的Service启动方式
-
(1)
startService / stopService
(主要用于不可交互的后台服务)-
生命周期顺序:
onCreate->onStartCommand->onDestroy
-
方法触发次数
-
onCreate
只会在第一次startService时被触发 -
每次 startService 都会触发
onStartCommand
-
不论 startService 多少次,stopService 一次就会停止服务
-
-
Service结束时机:
-
如果系统资源不足,android系统也可能结束服务
-
调用stopService,或自身的stopSelf方法
-
在设置中,通过应用->找到自己应用->停止
-
-
使用场景:
- 如果你只是想要启动一个后台服务长期进行某项任务
-
-
(2)
bindService / unbindService
(主要用于可交互的后台服务)-
生命周期顺序:
onCreate->onBind->onUnBind->onDestroy
-
方法触发次数:
-
onCreate
和onBind
方法只会在第一次bindService时执行 -
之后每次 bindService 都不会触发任何回调
-
onStartCommand
方法始终不会调用
-
-
Service结束时机:
-
调用
unbindService
来接触绑定、断开连接 -
调用该Service的Context不存在了(如Activity被Finish——即通过bindService启动的Service的生命周期依附于启动它的Context)
-
-
使用场景
- 如果你想要与正在运行的 Service 取得联系
-
-
(3)混合型(两种方式一起用)
- 调用
unBindService
将不会停止Service,必须调用stopService
或Service自身的stopSelf
来停止服务
- 调用
4、前台服务
-
创建与销毁
- 启动:
startForeground()
- 销毁:首先使用
stopForeground()
将前台服务降为后台服务,然后通过stopService()
将前台服务中止
- 启动:
-
TaskStackBuilder
在Notification通知栏中的使用- 应用:指定点击通知跳转页面后回退到主界面
mBuilder = new NotificationCompat.Builder(this) .setContent(view) .setSmallIcon(R.drawable.icon).setTicker("新资讯") .setWhen(System.currentTimeMillis()) .setOngoing(false) .setAutoCancel(true); Intent intent = new Intent(this, NotificationShow.class); TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); stackBuilder.addParentStack(NotificationShow.class); stackBuilder.addNextIntent(intent); PendingIntent pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); //PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); mBuilder.setContentIntent(pendingIntent);
- (1)用
TaskStackBuilder.create(this)
创建一个stackBuilder实例 - (2)接下来
addParentStack()
为跳转后的Activity添加一个父Activity(在activity中的manifest中添加parentActivityName即可)<activity android:name="com.lvr.service.NotificationShow" android:parentActivityName=".MainActivity" > </activity>
三、BroadCastReceiver全方位解析
1、应用场景
-
不同组件之间通信(包括应用内 / 不同应用之间)
-
与 Android 系统在特定情况下的通信+
- 如当电话呼入时、网络可用时
-
多线程通信
2、实现原理
-
广播使用了设计模式中的观察者模式:基于消息的发布/订阅事件模型
- 模型中有3个角色:
- (1)消息订阅者(广播接收者)
- (2)消息发布者(广播发布者)
- (3)消息中心(AMS,即Activity Manager Service)
- 模型中有3个角色:
-
广播发送者 和 广播接收者的执行 是 异步的
- 广播接收者通过 消息循环 拿到此广播,并回调
onReceive()
- 广播接收者通过 消息循环 拿到此广播,并回调
3、具体使用
-
(1)定义广播接收者实体类
- 继承
BroadcastReceiver
- 实现
onReceive(Context context, Intent intent)
- 继承
-
(2)注册广播接收器
-
静态注册(XML中的
receiver
标签)-
android:exported=["true" | "false"]
:决定此broadcastReceiver能否接收其他App的发出的广播。如果有intent-filter
,默认值为true,否则为false -
<action android:name="">
:匹配intent-filter
中的action,用于指定广播接收器将接收的广播类型
-
-
动态注册(通过调用
Context的registerReceiver()
方法)-
动态广播最好在Activity的onResume()注册、onPause()注销
-
有注册就必须有注销,否则会内存泄漏
-
-
-
(3)广播发送者向AMS发送广播(五类广播类型)
-
类型一:普通广播(Normal Broadcast)
- 开发者自身定义的Intent广播
Intent intent = new Intent(); //对应BroadcastReceiver中intentFilter的action intent.setAction(BROADCAST_ACTION); //发送广播 sendBroadcast(intent);
-
类型二:系统广播(System Broadcast)
-
根据系统相关条件变化而自动发送广播
-
每个广播都有特定的Intent - Filter(包括具体的action)
-
只需要定义Reciver接受即可
-
-
类型三:有序广播(Ordered Broadcast)
-
sendOrderedBroadcast(intent)
-
定义:发送出去的广播被广播接收者按照先后顺序接收
-
特点1:先接收的广播接收者可以对广播进行截断,即后接收的广播接收者不再接收到此广播;
-
特点2:先接收的广播接收者可以对广播进行修改,那么后接收的广播接收者将接收到被修改后的广播
-
-
类型四:粘性广播(Sticky Broadcast)
- 在Android5.0 & API 21中已经失效
-
类型五:App应用内广播(Local Broadcast)
-
局部广播
-
解决安全性&效率性问题
-
将全局广播设为局部广播:1⃣
注册时将exported
设为false 2⃣ 在广播发送和接收时,增设相应权限permission,用于权限验证 3⃣ 发送广播时指定该广播接收器所在的包名,此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中(intent.setPackage(packageName)
) -
使用封装好的
LocalBroadcastManager
类,使用方式上与全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将参数的context变成了LocalBroadcastManager的单一实例
-
-
四、ContentProvider全方位解析
- 作用:跨进程通信
- 也可用于进程内通信
-
原理:ContentProvider的底层是采用 Android中的Binder机制
-
理解:相当于各个进程与自身数据部分的一个中间件,各进程通过暴露自己的ContentProvider对数据进行统一、标准化的操作
-
优点:
-
(1)安全:
- 允许把自己的应用数据根据需求开放给 其他应用 进行 增、删、改、查,而不用担心因为直接开放数据库权限而带来的安全问题
-
(2)解耦了底层数据的存储方式:
- 使得无论底层数据存储采用何种方式,外界对数据的访问方式都是统一的,这使得访问简单 & 高效
-
1、统一资源标识符(URI)
-
定义:Uniform Resource Identifier,即统一资源标识符
-
作用:唯一标识 ContentProvider & 其中的数据
- 设置URI
Uri uri = Uri.parse("content://com.carson.provider/User/1")
2、MIME数据类型
-
作用:指定某个扩展名的文件用某种应用程序来打开
- eg:指定.html文件采用text应用程序打开
-
格式:类型组成 = 类型 + 子类型
- eg:text / html
-
ContentProvider根据 URI 返回MIME类型
ContentProvider.geType(uri);
3、ContentProvider类(对内操作)
-
组织数据方式:表格
-
主要方法(增删改查)
-
供外部进程调用,运行在ContentProvider进程的Binder线程池中(不是主线程)
-
得到数据类型,即返回当前 Url 所代表数据的MIME类型:
public String getType(Uri uri)
-
(1)增:
public Uri insert(Uri uri, ContentValues values)
-
(2)删:
public int delete(Uri uri, String selection, String[] selectionArgs)
-
(3)改:
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
-
(4)查:
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
-
-
ContentProvider类并不会直接与外部进程交互,而是通过ContentResolver类
4、ContentResolver类(对外交互)
-
作用:统一管理不同 ContentProvider间的操作
-
通过 URI 即可操作 不同的ContentProvider 中的数据
-
外部进程通过 ContentResolver类 从而与ContentProvider类进行交互
-
-
出现原因:一款应用可能存在多个Content Provider
-
具体使用:
- 与
ContentProvider
的增删改查调用相同
- 与
5、三个辅助ContentProvide的工具类
-
(1)ContentUris类
- 作用:操作 URI
- 核心方法有
-
withAppendedId()
:向URI追加一个idUri uri = Uri.parse("content://cn.scu.myprovider/user") Uri resultUri = ContentUris.withAppendedId(uri, 7); // 最终生成后的Uri为:content://cn.scu.myprovider/user/7
-
parseId()
:从URL中获取IDUri uri = Uri.parse("content://cn.scu.myprovider/user/7") long personid = ContentUris.parseId(uri); //获取的结果为:7
-
-
(2)UriMatcher类
-
作用:
- 在ContentProvider 中注册URI
- 根据 URI 匹配 ContentProvider 中对应的数据表
-
使用:
// 步骤1:初始化UriMatcher对象 UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); //常量UriMatcher.NO_MATCH = 不匹配任何路径的返回码 // 即初始化时不匹配任何东西 // 步骤2:在ContentProvider 中注册URI(addURI()) int URI_CODE_a = 1; int URI_CODE_b = 2; matcher.addURI("cn.scu.myprovider", "user1", URI_CODE_a); matcher.addURI("cn.scu.myprovider", "user2", URI_CODE_b); // 若URI资源路径 = content://cn.scu.myprovider/user1 ,则返回注册码URI_CODE_a // 若URI资源路径 = content://cn.scu.myprovider/user2 ,则返回注册码URI_CODE_b // 步骤3:根据URI 匹配 URI_CODE,从而匹配ContentProvider中相应的资源(match()) @Override public String getType (Uri uri){ Uri uri = Uri.parse(" content://cn.scu.myprovider/user1"); switch (matcher.match(uri)) { // 根据URI匹配的返回码是URI_CODE_a // 即matcher.match(uri) == URI_CODE_a case URI_CODE_a: return tableNameUser1; // 如果根据URI匹配的返回码是URI_CODE_a,则返回ContentProvider中的名为tableNameUser1的表 case URI_CODE_b: return tableNameUser2; // 如果根据URI匹配的返回码是URI_CODE_b,则返回ContentProvider中的名为tableNameUser2的表 } }
-
-
(3)ContentObserver类
-
作用:观察 Uri引起ContentProvider 中的数据变化(增删改),并 通知外界(即访问该数据访问者)
-
具体使用:
// 步骤1:注册内容观察者ContentObserver getContentResolver().registerContentObserver(uri); // 通过ContentResolver类进行注册,并指定需要观察的URI // 步骤2:当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者) public class UserContentProvider extends ContentProvider { public Uri insert(Uri uri, ContentValues values) { db.insert("user", "userid", values); getContext().getContentResolver().notifyChange(uri, null); // 通知访问者 } } // 步骤3:解除观察者 getContentResolver().unregisterContentObserver(uri); // 同样需要通过ContentResolver类进行解除
-
五、Fragment详解
1、添加任务到回退栈
FragmentTransaction.addToBackStack(String tag)
-
虽然实例不会被销毁,但是视图层次依然会被销毁。当再次返回该界面时,视图层仍旧是重新按照代码绘制视图
-
会调用
onDestoryView
和onCreateView
,只是未执行onDestroy
- eg:即使加入了回退栈,其EditText中原先输入的还是会丢失
-
如果不希望视图重绘(保持数据),就要使用
hide/show
,而不要用replace
-
2、transaction.replace()
- 此方法是
transaction.remove()
然后transaction.add()
的组合-
transaction.remove()
: 从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈(回退栈后面会详细说),这个Fragment实例将会被销毁 -
transaction.add()
: 向Activity中添加一个Fragment -
直接使用add/replace/hide/show的话,都要commit其效果才会在屏幕上显示出来(
ransatcion.commit()
提交事务)
-
3、Fragment与Activity通信
-
三个方法:
-
(1)如果你Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法
-
(2)如果Activity中未保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过
getFragmentManager.findFragmentByTag()
或者findFragmentById()
获得任何Fragment实例,然后进行操作 -
(3)Fragment中可以通过getActivity()得到当前绑定的Activity的实例,然后进行操作
-
-
优化:
-
目标:降低Fragment与Activity的耦合,而且Fragment更不应该直接操作别的Fragment
-
解决方法:通过在Fragment中定义回调接口,在Activity中实现接口来实现堆Fragment的统一管理
-
4、避免因Acvtivity发生重新启动时,Fragment也跟着不断重新创建大量耗费资源
-
通过检查onCreate的参数Bundle savedInstanceState就可以判断,当前是否发生Activity的重新创建
- 默认的savedInstanceState会存储一些数据,包括Fragment的实例
- 判断只有在
savedInstanceState==null
时,才进行创建Fragment实例
-
Fragment也有
onSaveInstanceState
的方法,在此方法中进行保存数据,然后在onCreate
或者onCreateView
或者onActivityCreated
进行恢复都可以
六、Android消息机制
- 经典实现:
public class Activity extends android.app.Activity {
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
System.out.println(msg.what);
}
};
@Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
...............耗时操作
Message message = Message.obtain();
message.what = 1;
mHandler.sendMessage(message);
}
}).start();
}
}
-
Handler线程切换的原理
-
线程2持有在线程1中创建的Handler的引用
-
Handler接收消息是在创建它的线程当中
-
-
MessageQueue,Handler和Looper三者之间的关系
-
Looper是保存在ThreadLocal中的
- 每个线程只能有一个Looper
-
每个线程可以有多个Handler
- 一个Looper可以处理来自多个Handler的消息
-
Looper中维护一个MessageQueue
-
-
使用消息机制:
-
初始化Looper:
Looper.prepare()
-
开启Looper:
Looper.loop()
-
loop()进入循环模式,直到消息为空时退出循环:
-
当遇到Message 时,读取MessageQueue的下一条Message,把Message分发给相应的target(handler)。
-
当next()取出下一条消息时,队列中已经没有消息时,next()会无限循环,产生阻塞。等待MessageQueue中加入消息,然后重新唤醒。
-
-
创建handler
-
发送消息
- 最终都调用了
sendMessageAtTime()
,在该方法内调用enqueueMessage()
- 最终都调用了
-
接收消息:
next()
-
原理:
next()
轻易不会返回null,当消息队列为空时,next方法会阻塞,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回继续轮询 -
nativePollOnce()
是阻塞操作
,nextPollTimeoutMillis
代表下一个消息到来前,还需要等待的时长 -
当nextPollTimeoutMillis = -1时,表示消息队列中无消息,会一直等待下去。
Message next() { final long ptr = mPtr; if (ptr == 0) { //当消息循环已经退出,则直接返回 return null; } int pendingIdleHandlerCount = -1; // 循环迭代的首次为-1 int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } //阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回继续执行下面的Synchronized代码块 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { //当消息Handler为空时,查询MessageQueue中的下一条异步消息msg,为空则退出循环。 do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { //当异步消息触发时间大于当前时间,则设置下一次轮询的超时时长 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 获取一条消息,并返回 mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; //设置消息的使用状态,即flags |= FLAG_IN_USE msg.markInUse(); return msg; //成功地获取MessageQueue中的下一条即将要执行的消息 } } else { //没有消息 nextPollTimeoutMillis = -1; } //消息正在退出,返回null if (mQuitting) { dispose(); return null; } ............................... } }
-
-
分发消息:
dispatchMessage()
-
优先级最高:Message的回调方法:
message.callback.run()
-
优先级次之:Handler中Callback的回调方法:
Handler.mCallback.handleMessage(msg)
-
优先级最低:Handler的默认方法:
Handler.handleMessage(msg)
-
-
七、Android事件分发机制
-
主要发生的Touch事件有如下四种:
- MotionEvent.ACTION_DOWN:按下View(所有事件的开始)
- MotionEvent.ACTION_MOVE:滑动View
- MotionEvent.ACTION_CANCEL:非人为原因结束本次事件
- MotionEvent.ACTION_UP:抬起View(与DOWN对应)
-
三个重要方法的关系(此为Activity事件分发中的dispatchTouchEvent()源码)
// 点击事件产生后,会直接调用dispatchTouchEvent()方法
public boolean dispatchTouchEvent(MotionEvent ev) {
//代表是否消耗事件
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
//如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件
//则该点击事件则会交给当前View进行处理
//即调用onTouchEvent ()方法去处理点击事件
consume = onTouchEvent (ev) ;
} else {
//如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件
//则该点击事件则会继续传递给它的子元素
//子元素的dispatchTouchEvent()就会被调用,重复上述过程
//直到点击事件被最终处理为止
consume = child.dispatchTouchEvent (ev) ;
}
return consume;
}
-
默认事件传递情况:(如图下所示)
- 从Activity A---->ViewGroup B—>View C,从上往下调用dispatchTouchEvent()
- 再由View C—>ViewGroup B —>Activity A,从下往上调用
onTouchEvent()
(子类的onTouchEvent会return super.onTouchEvent()
)
-
假设View C希望处理这个点击事件
-
C被设置成可点击的(Clickable)
-
覆写C的onTouchEvent方法返回true
-
-
拦截DOWN的后续事件:假设ViewGroup B没有拦截DOWN事件(还是View C来处理DOWN事件),但它拦截了接下来的MOVE事件。
-
DOWN事件传递到C的onTouchEvent方法,返回了true。
-
在后续到来的第一个MOVE事件,B的onInterceptTouchEvent方法返回true拦截该MOVE事件,但该事件并没有传递给B;这个MOVE事件将会被系统变成一个CANCEL事件传递给C的onTouchEvent方法
-
后续又来了一个MOVE事件,该MOVE事件才会直接传递给B的onTouchEvent()
- 后续事件将直接传递给B的onTouchEvent()处理
- 后续事件将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。
-
C再也不会收到该事件列产生的后续事件。
-
-
事件分发机制的三部分组成
- Activity对点击事件的分发机制
- ViewGroup对点击事件的分发机制
- View对点击事件的分发机制
1、Activity对点击事件的分发机制
- 当一个点击事件发生时,调用顺序如下(Activity向ViewGroup的传递)
-
事件最先传到Activity的dispatchTouchEvent()进行事件分发
-
调用Window类实现类PhoneWindow的superDispatchTouchEvent()
-
调用DecorView的superDispatchTouchEvent()
-
最终调用DecorView父类的dispatchTouchEvent(),即ViewGroup的dispatchTouchEvent()
-
2、ViewGroup对点击事件的分发机制
1、判断ViewGroup是否对事件拦截
- ViewGroup的
dispatchTouchEvent()
源码分析
// 发生ACTION_DOWN事件或者已经发生过ACTION_DOWN,并且将mFirstTouchTarget赋值,才进入此区域,主要功能是拦截器
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
//disallowIntercept:是否禁用事件拦截的功能(默认是false),即不禁用
//可以在子View通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改,不让该View拦截事件
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//默认情况下会进入该方法
if (!disallowIntercept) {
//调用拦截方法
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
// 当没有触摸targets,且不是down事件时,开始持续拦截触摸。
intercepted = true;
}
-
进入拦截判断的两个条件
-
(1)如果当前事件的
MotionEvent.ACTION_DOWN
,则进入判断,调用ViewGrouponInterceptTouchEvent()
方法的值,判断是否拦截 -
(2)如果
mFirstTouchTarget != null
,即已经发生过MotionEvent.ACTION_DOWN
,并且该事件已经有ViewGroup的子View进行处理了,那么也进入判断,调用ViewGrouponInterceptTouchEvent()
方法的值,判断是否拦截
-
-
如果不是以上两种情况,即已经是MOVE或UP事件了,并且之前的事件没有对象进行处理,则将
intercepted
设置成true,开始拦截接下来的所有事件- 解释了如果子View的onTouchEvent()方法返回false(ViewGroup中的
mFirstTouchTarget
为null),那么接下来的一些列事件都不会交给他处理
- 解释了如果子View的onTouchEvent()方法返回false(ViewGroup中的
2、ViewGroup不拦截时继续向下分发的逻辑
-
dispatchTransformedTouchEvent()
if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); }
-
由于其中传递的child不为空,所以就会调用子元素的dispatchTouchEvent()
-
如果子元素的dispatchTouchEvent()方法返回true,那么mFirstTouchTarget就会被赋值,同时跳出找寻newTouchTarget(即上面的mFirstTouchTarget)的for循环
-
-
对
mFirstTouchTarget
赋值//添加TouchTarget,则mFirstTouchTarget != null。 newTouchTarget = addTouchTarget(child, idBitsToAssign); //表示以及分发给NewTouchTarget alreadyDispatchedToNewTouchTarget = true;
-
其中在
addTouchTarget(child, idBitsToAssign);
内部完成mFirstTouchTarget被赋值 -
如果mFirstTouchTarget为空,将会让ViewGroup默认拦截所有操作
-
如果遍历所有子View或ViewGroup,都没有消费事件。ViewGroup会自己处理事件
-
3、View对点击事件的分发机制
- View的
dispatchTouchEvent()
源码分析public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); }
- 只有三个条件都为真,dispatchTouchEvent()才返回true;否则执行onTouchEvent(event)方法
3.1 三个判断条件分析
-
(1)
mOnTouchListener!= null
//mOnTouchListener是在View类下setOnTouchListener方法里赋值的 public void setOnTouchListener(OnTouchListener l) { //即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空) mOnTouchListener = l; }
-
(2)
(mViewFlags & ENABLED_MASK) == ENABLED
- 该条件是判断当前点击的控件是否enable
- 由于很多View默认是enable的,因此该条件恒定为true
-
(3)
mOnTouchListener.onTouch(this, event)
- 回调控件注册Touch事件时的onTouch方法
//手动调用设置 button.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return false; } });
-
如果在onTouch方法返回true,就会让上述三个条件全部成立,从而整个方法直接返回true。
-
如果在onTouch方法里返回false,就会去执行onTouchEvent(event)方法。
3.2 onTouchEvent(event)
的源码
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//如果该控件是可以点击的就会进入到下两行的switch判断中去;
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
//如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
// 在经过种种判断之后,会执行到关注点1的performClick()方法。
//请往下看关注点1
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//关注点1
//请往下看performClick()的源码分析
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
//如果该控件是可以点击的,就一定会返回true
return true;
}
//如果该控件是不可以点击的,就一定会返回false
return false;
}
performClick()
public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false; }
-
只要mOnClickListener不为null,就会去调用onClick方法
public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } mOnClickListener = l; }
-
3.3 onTouch()
的执行高于onClick()
-
(1)如果在回调onTouch()里返回false,就会让dispatchTouchEvent方法返回false,那么就会执行onTouchEvent();如果回调了setOnClickListener()来给控件注册点击事件的话,最后会在performClick()方法里回调onClick()
- onTouch()返回false(该事件没被onTouch()消费掉) = 执行onTouchEvent() = 执行OnClick()
-
(2)如果在回调onTouch()里返回true,就会让dispatchTouchEvent方法返回true,那么将不会执行onTouchEvent(),即onClick()也不会执行;
- onTouch()返回true(该事件被onTouch()消费掉) = dispatchTouchEvent()返回true(不会再继续向下传递) = 不会执行onTouchEvent() = 不会执行OnClick()
总结:
1、onTouch()
和onTouchEvent()
的区别
- 这两个方法都是在View的dispatchTouchEvent中调用,但onTouch优先于onTouchEvent执行。
- 如果在onTouch方法中返回true将事件消费掉,onTouchEvent()将不会再执行。
2、Touch事件的后续事件(MOVE、UP)层级传递
-
分析三个重要方法得知
-
dispatchTouchEvent()和 onTouchEvent()消费事件、终结事件传递(返回true)
-
而onInterceptTouchEvent 并不能消费事件,它相当于是一个分叉口起到分流导流的作用,对后续的ACTION_MOVE和ACTION_UP事件接收起到非常大的作用
- 接收了ACTION_DOWN事件的函数不一定能收到后续事件(ACTION_MOVE、ACTION_UP)(接收到Down后,在OnClick里返回false?)
-
-
ACTION_MOVE和ACTION_UP事件的传递结论
-
如果在某个对象(Activity、ViewGroup、View)的dispatchTouchEvent()消费事件(返回true),那么收到ACTION_DOWN的函数也能收到ACTION_MOVE和ACTION_UP
-
如果在某个对象(Activity、ViewGroup、View)的onTouchEvent()消费事件(返回true),那么ACTION_MOVE和ACTION_UP的事件从上往下传到这个View后就不再往下传递了,而直接传给自己的onTouchEvent()并结束本次事件传递过程。
-
八、AsyncTask详解
1、概述
- 是一个抽象类
public abstract class AsyncTask<Params, Progress, Result>
- Params:传入参数
- Progress:任务进度参数类型
- Result:结果类型
2、使用
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
@Override
protected void onPreExecute() {
progressDialog.show();
}
@Override
protected Boolean doInBackground(Void... params) {
try {
while (true) {
int downloadPercent = doDownload();
publishProgress(downloadPercent);
if (downloadPercent >= 100) {
break;
}
}
} catch (Exception e) {
return false;
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values) {
progressDialog.setMessage("当前下载进度:" + values[0] + "%");
}
@Override
protected void onPostExecute(Boolean result) {
progressDialog.dismiss();
if (result) {
Toast.makeText(context, "下载成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();
}
}
}
-
继承抽象类并实现四个方法后,通过
new DownloadTask().execute();
创建一个实例来开始任务-
AsyncTask对象必须在UI线程中创建
- InternalHandler是一个静态类,为了能够将执行环境切换到主线程,因此这个类必须在主线程中进行加载。所以变相要求AsyncTask的类必须在主线程中进行加载。
-
一个任务实例只能执行一次,如果执行第二次将会抛出异常
-
-
通过在
doInBackground
中显式调用publishProgress(downloadPercent);
来回调onProgressUpdate
3、源码分析
(1)构造函数
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}
- 仅创建了两个实例
- mWorker中的call()方法执行了耗时操作,即
result = doInBackground(mParams);
- 然后把执行得到的结果通过
postResult(result);
,传递给内部的Handler跳转到主线程中
- mWorker中的call()方法执行了耗时操作,即
(2)将任务加载进线程池:execute()
-
execute()
调用了executeOnExecutor()
方法 -
executeOnExecutor()
方法中,执行耗时任务是在exec.execute(mFuture)
- exec是SerialExecutor类,
-
SerialExecutor 是个静态内部类
-
是所有实例化的AsyncTask对象公有的
-
SerialExecutor 内部维持了一个队列,通过锁使得该队列保证AsyncTask中的任务是串行执行的,即多个任务需要一个个加到该队列中,然后执行完队列头部的再执行下一个
-
调用
scheduleNext()
方法,调用THREAD_POOL_EXECUTOR执行队列头部的任务
-
-
在线程池中调用
execute()
方法执行具体的耗时任务(即构造函数Call方法中所定义的内容) -
实行完耗时任务后,
postResult()
发送消息给InternalHandler
- 如果收到的消息是
MESSAGE_POST_RESULT
(即执行完了doInBackground()方法并传递结果),那么就调用finish()方法。
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; }
- 如果收到的消息是
4、AsyncTask使用不当的后果
(1)生命周期
AsyncTask不与任何组件绑定生命周期,所以在Activity/或者Fragment中创建执行AsyncTask时,最好在Activity/Fragment的onDestory()调用 cancel(boolean);
(2)内存泄漏
如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。
(3)结果丢失
屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask(非静态的内部类)会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。
九、HandlerThread详解
一、概述
1、使用场景
-
避免重复开启销毁线程解决方案:
- 线程池
- HandlerThread
-
HandlerThread
可以用来执行多个耗时操作,而不需要多次开启线程- 里面是采用
Handler
和Looper
实现的
- 里面是采用
2、理解:
-
每一个HandlerThread对应一个Thread
-
存在意义:是一个对Looper做了内部封装的Thread
- 每次创建时,不需要再Looper.prepare()等操作
-
一个继承了线程的类(HandlerThread),搭配Handler使用,将Thread的Looper绑定到Handler上,然后在Handler的handlerMessage方法中检查是否有耗时逻辑需要处理
3、基本使用
-
(1)创建Handler的实例对象
HandlerThread handlerThread = new HandlerThread("myHandlerThread");
- 参数为线程名
-
(2)启动创建的HandlerThread线程
handlerThread.start();
-
(3)通过handlerThread将线程的looper与Handler绑定到一起
mThreadHandler = new Handler(mHandlerThread.getLooper()) { @Override public void handleMessage(Message msg) { checkForUpdate(); if(isUpdate){ mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO); } } };
-
例子:
public class MainActivity extends AppCompatActivity {
private static final int MSG_UPDATE_INFO = 0x100;
Handler mMainHandler = new Handler();
private TextView mTv;
private Handler mThreadHandler;
private HandlerThread mHandlerThread;
private boolean isUpdate = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTv = (TextView) findViewById(R.id.tv);
initHandlerThread();
}
private void initHandlerThread() {
mHandlerThread = new HandlerThread("xujun");
mHandlerThread.start();
mThreadHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
checkForUpdate();
if (isUpdate) {
mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
}
}
};
}
/**
* 模拟从服务器解析数据
*/
private void checkForUpdate() {
try {
//模拟耗时
Thread.sleep(1200);
mMainHandler.post(new Runnable() {
@Override
public void run() {
String result = "实时更新中,当前股票行情:<font color='red'>%d</font>";
result = String.format(result, (int) (Math.random() * 5000 + 1000));
mTv.setText(Html.fromHtml(result));
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
protected void onResume() {
isUpdate = true;
super.onResume();
mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
}
@Override
protected void onPause() {
super.onPause();
isUpdate = false;
mThreadHandler.removeMessages(MSG_UPDATE_INFO);
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandlerThread.quit();
mMainHandler.removeCallbacksAndMessages(null);
}
}
二、源码分析
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
//持有锁机制来获得当前线程的Looper对象
synchronized (this) {
mLooper = Looper.myLooper();
//发出通知,当前线程已经创建mLooper对象成功,这里主要是通知getLooper方法中的wait
notifyAll();
}
//设置线程的优先级别
Process.setThreadPriority(mPriority);
//这里默认是空方法的实现,我们可以重写这个方法来做一些线程开始之前的准备,方便扩展
onLooperPrepared();
Looper.loop();
mTid = -1;
}
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// 直到线程创建完Looper之后才能获得Looper对象,Looper未创建成功,阻塞
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
/**
* Returns the identifier of this thread. See Process.myTid().
*/
public int getThreadId() {
return mTid;
}
}
(1)使用HandlerThread时必须调用start()
方法
-
调用
start()
方法之后,才可以将HandlerThread和handler绑定在一起 -
原因:就是我们是在run()方法才开始初始化我们的looper,而我们调用HandlerThread的start()方法的时候,线程会交给虚拟机调度,由虚拟机自动调用run方法
2、为什么要使用锁机制和notifyAll()
- 原因:在获得mLooper对象的时候存在一个同步的问题,只有当线程创建成功并且Looper对象也创建成功之后才能获得mLooper的值。
- 这里等待方法wait和run方法中的notifyAll方法共同完成同步问题
3、quit与quitSafe
- 两个方法最终都会调用MessageQueue的
quit(boolean safe)
方法
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
//安全退出调用这个方法
if (safe) {
removeAllFutureMessagesLocked();
} else {//不安全退出调用这个方法
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
-
(1)不安全的退出调用
removeAllMessagesLocked()
- 会遍历Message链表,移除所有信息的回调,并重置为null
private void removeAllMessagesLocked() { Message p = mMessages; while (p != null) { Message n = p.next; p.recycleUnchecked(); p = n; } mMessages = null; }
-
(2)安全的退出会调用
removeAllFutureMessagesLocked()
-
会根据
Message.when
这个属性,判断我们当前消息队列是否正在处理消息,没有正在处理消息的话,直接移除所有回调,正在处理的话,等待该消息处理处理完毕再退出该循环 -
因此说quitSafe()是安全的,而quit()方法是不安全的
- quit方法不管是否正在处理消息,直接移除所有回调
private void removeAllFutureMessagesLocked() { final long now = SystemClock.uptimeMillis(); Message p = mMessages; if (p != null) { //判断当前队列中的消息是否正在处理这个消息,没有的话,直接移除所有回调 if (p.when > now) { removeAllMessagesLocked(); } else {//正在处理的话,等待该消息处理处理完毕再退出该循环 Message n; for (;;) { n = p.next; if (n == null) { return; } if (n.when > now) { break; } p = n; } p.next = null; do { p = n; n = p.next; p.recycleUnchecked(); } while (n != null); } } }
-
十、IntentService详解
- 定义:IntentService是Android里面的一个封装类,继承自四大组件之一的Service(是一种Service)。
1、概述
(1)与普通线程的区别
-
IntentService内部采用了HandlerThread实现,作用类似于后台线程
-
与后台线程相比,IntentService是一种后台服务,优势是:优先级高(不容易被系统杀死),从而保证任务的执行
- 对于后台线程,若进程中没有活动的四大组件,则该线程的优先级非常低,容易被系统杀死,无法保证任务的执行
(2)与普通Service区别
-
不建议在Service中编写耗时的逻辑和操作,否则会引起ANR;
-
从属性 & 作用上来说
类别 | 特性 |
---|---|
Service | 依赖于应用程序的主线程(不是独立的进程 or 线程) |
IntentService | 创建一个工作线程来处理多线程任务 |
- 从关闭方式来说
类别 | 特性 |
---|---|
Service | 需要主动调用stopSelf() 来结束服务 |
IntentService | 不需要主动关闭,在所有intent被处理完后,系统会自动关闭服务 |
(3)使用场景
-
作用:处理异步请求,实现多线程
-
特征:线程任务需要按顺序、在后台执行的使用场景
- eg:最常见的场景:离线下载
-
不适合场景:由于所有的任务都在同一个Thread looper里面来做,所以不符合多个数据同时请求的场景。
(4)工作流程
- 若启动IntentService多次,那么每个耗时操作则以队列的方式在
IntentService的onHandleIntent
回调方法中依次执行,执行完自动结束。
2、使用(三步)
(1)定义IntentService的子类:传入线程名称、复写onHandleIntent()
方法(执行耗时逻辑)
package com.example.carson_ho.demoforintentservice;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
/**
* Created by Carson_Ho on 16/9/28.
*/
public class myIntentService extends IntentService {
/*构造函数*/
public myIntentService() {
//调用父类的构造函数
//构造函数参数=工作线程的名字
super("myIntentService");
}
/*复写onHandleIntent()方法*/
//实现耗时任务的操作
@Override
protected void onHandleIntent(Intent intent) {
//根据Intent的不同进行不同的事务处理
String taskName = intent.getExtras().getString("taskName");
switch (taskName) {
case "task1":
Log.i("myIntentService", "do task1");
break;
case "task2":
Log.i("myIntentService", "do task2");
break;
default:
break;
}
}
@Override
public void onCreate() {
Log.i("myIntentService", "onCreate");
super.onCreate();
}
/*复写onStartCommand()方法*/
//默认实现将请求的Intent添加到工作队列里
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("myIntentService", "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.i("myIntentService", "onDestroy");
super.onDestroy();
}
}
(2)在Manifest.xml中注册服务
<service android:name=".myIntentService">
<intent-filter>
<action android:name="cn.scu.finch"/>
</intent-filter>
</service>
(3)在Activity中开启Service服务
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//同一服务只会开启一个工作线程
//在onHandleIntent函数里依次处理intent请求。
Intent i = new Intent("cn.scu.finch");
Bundle bundle = new Bundle();
bundle.putString("taskName", "task1");
i.putExtras(bundle);
startService(i);
Intent i2 = new Intent("cn.scu.finch");
Bundle bundle2 = new Bundle();
bundle2.putString("taskName", "task2");
i2.putExtras(bundle2);
startService(i2);
startService(i); //多次启动
}
}
- 实现结果:
三、源码分析
(1)IntentService如何单独开启一个新的工作线程
// IntentService源码中的 onCreate() 方法
@Override
public void onCreate() {
super.onCreate();
// HandlerThread继承自Thread,内部封装了 Looper
//通过实例化HandlerThread新建线程并启动
//所以使用IntentService时不需要额外新建线程
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
//获得工作线程的 Looper,并维护自己的工作队列
mServiceLooper = thread.getLooper();
//将上述获得Looper与新建的mServiceHandler进行绑定
//新建的Handler是属于工作线程的。
mServiceHandler = new ServiceHandler(mServiceLooper);
}
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
// IntentService的handleMessage方法把接收的消息交给onHandleIntent()处理
// onHandleIntent()是一个抽象方法,使用时需要重写的方法
@Override
public void handleMessage(Message msg) {
// onHandleIntent 方法在工作线程中执行,执行完调用 stopSelf() 结束服务。
onHandleIntent((Intent) msg.obj);
//onHandleIntent 处理完成后 IntentService会调用 stopSelf() 自动停止。
stopSelf(msg.arg1);
}
}
// onHandleIntent()是一个抽象方法,使用时需要重写的方法
@WorkerThread
protected abstract void onHandleIntent(Intent intent);
(2)IntentService如何通过onStartCommand()传递给服务intent被依次插入到工作队列中
public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
public void onStart(Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
//把 intent 参数包装到 message 的 obj 中,然后发送消息,即添加到消息队列里
//这里的Intent 就是启动服务时startService(Intent) 里的 Intent。
msg.obj = intent;
//每次startService()的时候,就会最终发送消息给onCreate()中的handleMessage(),然后回调被复写的onHandleIntent处理耗时逻辑
mServiceHandler.sendMessage(msg);
}
//清除消息队列中的消息
@Override
public void onDestroy() {
mServiceLooper.quit();
}
四、总结
(1)实现原理
- IntentService本质是采用Handler & HandlerThread方式:
-
通过HandlerThread单独开启一个名为IntentService的线程
-
创建一个名叫ServiceHandler的内部Handler
-
把内部Handler与HandlerThread所对应的子线程进行绑定
-
通过onStartCommand()传递给服务intent,依次插入到工作队列中,并逐个发送给onHandleIntent()
-
通过onHandleIntent()来依次处理所有Intent请求对象所对应的任务
-
(2)使用逻辑
-
调用时传入不同的Intent(比如用Action区分)
-
复写方法onHandleIntent(),在里面根据Intent的不同进行不同的线程操作
(3)注意
-
工作任务队列是顺序执行的
- 如果一个任务正在IntentService中执行,此时你再发送一个新的任务请求,这个新的任务会一直等待直到前面一个任务执行完毕才开始执行。
-
原因:
-
由于onCreate() 方法只会调用一次,所以只会创建一个工作线程
-
当多次调用 startService(Intent) 时(onStartCommand也会调用多次)其实并不会创建新的工作线程,只是把消息加入消息队列中等待执行,所以多次启动 IntentService 会按顺序执行事件
-
如果服务停止,会清除消息队列中的消息,后续的事件得不到执行。
- 因为源码中调用的是
quit()
而不是quitSafe()
- 因为源码中调用的是
-
十一、LruCache原理解析
1、概述
-
关键点:使用了Lru(最近最少使用)算法
-
基本理解
-
LruCache是个泛型类
-
主要算法原理是把最近使用的对象用强引用(即我们平常使用的对象引用方式)存储在 LinkedHashMap 中
-
当缓存满时,把最近最少使用的对象从内存中移除,并提供了get和put方法来完成缓存的获取和添加操作。
-
2、使用
eg:图片缓存
(1)设置LruCache缓存的大小,一般为当前进程可用容量的1/8
- 缓存的总容量和每个缓存对象的大小所用单位要一致
(2)重写sizeOf方法,计算出要缓存的每张图片的大小
int maxMemory = (int) (Runtime.getRuntime().totalMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
3、原理解析
(1) 核心思想:
- 维护一个缓存对象列表,其中对象列表的排列方式是按照访问顺序实现的
- 一直没访问的对象,将放在链表尾,即将被淘汰
- 最近访问的对象将放在链表头,最后被淘汰
(2)使用LinkedHashMap(HashMap+双向链表)实现
-
LinkedHashMap 继承自 HashMap
- HashMap实现Map接口
-
双向链表的结构可以实现访问顺序和插入顺序,使得LinkedHashMap中的对按照一定顺序排列
- LinkedHashMap的构造函数指定实现顺序
//其中accessOrder设置为true则为访问顺序,为false,则为插入顺序 public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
- 例子:设置为访问顺序时(accessOrder = true)
- 访问顺序:链表头插入,链表尾删除,访问时从链表尾访问(类似于队列)
public static final void main(String[] args) { LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>(0, 0.75f, true); map.put(0, 0); map.put(1, 1); map.put(2, 2); map.put(3, 3); map.put(4, 4); map.put(5, 5); map.put(6, 6); map.get(1); map.get(2); for (Map.Entry<Integer, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + ":" + entry.getValue()); } }
- 输出
0:0 //链表尾 3:3 4:4 5:5 6:6 1:1 2:2 //链表头
-
使用HashMap+双向链表好处:
-
双向链表用于LRU中数据的存储和对使用时间新旧的维护
- 在链表头的是最新使用的。
- 在尾部的是最旧的。也是下次要清除的。
- 如果加入的值是链表内存在的则要移动到头部
-
HashMap是来配合双向链表,用于减少时间复杂度
- 它是可以快速的(O(1)的时间)定位,链表中某个值是否存在
- 要不然需要遍历双向链表,时间复杂度为O(n) n为链表长度)
-
-
不使用单链表原因:
- 获取前驱时需要遍历,时间效率下降
- 虽然市面上单链表使用较多,因为单链表相较于双向链表虽然牺牲了时间效率,但是因为没有前驱,所以节省了空间
- 获取前驱时需要遍历,时间效率下降
(3)实现插入和获取的put()/get()与他们分别对应的trimToSize()与recordAccess()
- 1⃣
put()
方法- 在添加过缓存对象后,调用
trimToSize()
方法,来判断缓存是否已满,如果满了就要删除近期最少使用的算法
- 在添加过缓存对象后,调用
public final V put(K key, V value) {
//不可为空,否则抛出异常
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
//插入的缓存对象值加1
putCount++;
//增加已有缓存的大小
size += safeSizeOf(key, value);
//向map中加入缓存对象(put返回value)
previous = map.put(key, value);
//如果已有缓存对象,则缓存大小恢复到之前
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
//entryRemoved()是个空方法,可以自行实现
if (previous != null) {
entryRemoved(false, key, previous, value);
}
//调整缓存大小(关键方法)
trimToSize(maxSize);
return previous;
}
- 2⃣
trimToSize()
方法
public void trimToSize(int maxSize) {
//死循环
while (true) {
K key;
V value;
synchronized (this) {
//如果map为空并且缓存size不等于0或者缓存size小于0,抛出异常
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
//如果缓存大小size小于最大缓存,或者map为空,不需要再删除缓存对象,跳出循环
if (size <= maxSize || map.isEmpty()) {
break;
}
//迭代器获取第一个对象,即队尾的元素,近期最少访问的元素
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
//删除该对象,并更新缓存大小
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
- 3⃣
get()
方法与recordAccess()
- LruCache的
get()
-> LinkedHashMap的get()
-> LinkedHashMap的recordAccess()
实现排序
- LruCache的
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
//判断是否是访问排序
if (lm.accessOrder) {
lm.modCount++;
//删除此元素
remove();
//将此元素移动到队列的头部
addBefore(lm.header);
}
}
十二、Activity、Window、DecorView及ViewRoot的关系
1、四个部分的职能介绍
(1)Activity
-
不负责视图控制,只是控制生命周期和处理事件
-
一个Activity包含了一个Window
- Window才是真正代表一个窗口
(2)Window
-
Window是一个抽象类,实际在Activity中持有的是其子类PhoneWindow
- PhoneWindow中以内部类的形式持有一个DecorView
- DecorView是View的根布局
-
Window是视图的承载器
-
Window 通过
WindowManager
将DecorView加载其中 -
Window将DecorView交给ViewRoot,进行视图绘制以及其他交互
-
(3)DecorView
-
是FrameLayout的子类
-
是Android视图树的根结点视图
-
内容:包含一个竖直方向的LinearLayout,里面有三个部分
-
1⃣
ViewStub
:延迟加载的视图(应该是设置ActionBar,根据Theme设置) -
2⃣标题栏:根据Theme设置,有的布局没有
-
3⃣内容栏:Activity中
setContentView
所设置的布局文件,是内容栏唯一的子View<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:fitsSystemWindows="true" android:orientation="vertical"> <!-- Popout bar for action modes --> <ViewStub android:id="@+id/action_mode_bar_stub" android:layout_width="match_parent" android:layout_height="wrap_content" android:inflatedId="@+id/action_mode_bar" android:layout="@layout/action_mode_bar" android:theme="?attr/actionBarTheme" /> <FrameLayout style="?android:attr/windowTitleBackgroundStyle" android:layout_width="match_parent" android:layout_height="?android:attr/windowTitleSize"> <TextView android:id="@android:id/title" style="?android:attr/windowTitleStyle" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@null" android:fadingEdge="horizontal" android:gravity="center_vertical" /> </FrameLayout> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1" android:foreground="?android:attr/windowContentOverlay" android:foregroundGravity="fill_horizontal|top" /> </LinearLayout>
-
(4)ViewRoot
-
所有View的绘制及事件分发等交互都是通过它来执行或传递的
-
View的三大流程(测量(measure),布局(layout),绘制(draw))均通过ViewRoot来完成
-
Android的所有触屏事件、按键事件、界面刷新等事件都是通过ViewRoot进行分发的
-
-
ViewRoot对应
ViewRootImpl
类- 它是连接WindowManagerService和DecorView的纽带
-
ViewRoot并不属于View树的一份子
2、四部分的关系
-
Activity持有一个PhoneWindow(继承Window抽象类的实现类)
-
PhoneWindow以内部类的形式持有一个DecorView
-
Window通过WindowManager将DecorView加载
-
Window将DecorView交给ViewRoot,进行视图绘制及其他交互
3、DecorView的创建
(1)引出:
- Activity的
setContentView()
方法中通过Window的方法来装载视图public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
(2)创建流程:
-
Activity通过
attach()
方法生成PhoneWindow实例(获取Window对象)attach()
方法在ActivityThread的handleLaunchActivity()
方法中先于生命周期开始执行
-
然后Window在
setContentView()
中通过installDecor()
方法创建DecorView -
在
insatallDecor()
中,通过generateDecor()
方法从主题中获取样式,然后根据样式,通过decor.addView()
加载布局到DecorView中 -
然后从DecorView中通过
findViewById
获取mContentParent
mContentParent
即布局中@android:id/content所对应的FrameLayout。
4、DecorView的显示
(1)引出
-
目前DecorView还只是创建,但还没有显示
-
只有在
onResume()
的时候才能显示到前台
(2)流程
-
在ActivityThread中的
handleLaunchActivity()
中通过handleResumeActivity()
来回调Activity.onResume()
- 此时界面仍不可见
-
然后在
handleResumeActivity()
中通过windowManager.addView()
方法将DecorView添加进WindowManager中,并创建ViewRootImpl对象- WindowManager接口 -> WindowManagerImpl实现类 -> WindowManagerGlobal的
addView()
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; ...... synchronized (mLock) { ViewRootImpl root; //实例化一个ViewRootImpl对象 root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); } ...... try { //将DecorView交给ViewRootImpl root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { } }
- 在
addView()
方法中将DecorView交给ViewRootImpl,执行一系列的setView()
方法 - 在
setView()
方法中最终调用到performTraversals()
方法,开始View的三大绘制流程
- WindowManager接口 -> WindowManagerImpl实现类 -> WindowManagerGlobal的
-
最后在
handleResumeActivity()
中通过makeViesable()
的mDecor.setVisibility(View.VISIBLE)
可见来使界面可见
5、ViewRoot对事件的分发
(1)引出
-
ViewRootImpl是个连接器
-
通过硬件的感知来通知视图,进行用户之间的交互
-
负责WindowManagerService与DecorView之间的通信
(2)流程
-
硬件 -> ViewRootImpl -> DecorView -> PhoneWindow -> Activity
-
用户点击屏幕产生一个触摸行为,这个触摸行为则是通过底层硬件来传递捕获,然后交给ViewRootImpl,接着将事件传递给DecorView,而DecorView再交给PhoneWindow,PhoneWindow再交给Activity,然后接下来就是我们常见的View事件分发了
十三、View测量、布局及绘制原理
1、Measure流程
-
setMeasuredDimension()
:设定View的宽高信息,完成View的测量操作 -
MeasureSpec的确定
-
View的测量流程
- 在Measure过程中,ViewGroup一般是先测量子View的大小,然后再确定自身的大小
2、Layout流程
-
View的布局流程
- ViewGroup先在layout()中确定自己的布局,然后在onLayout()方法中再调用子View的layout()方法,让子View布局
3、Draw过程
- View的绘制流程
总结:
方法名 | 特性 |
---|---|
onMeasure()方法 | 单一View,一般重写此方法,针对wrap_content情况,规定View默认的大小值,避免于match_parent情况一致。ViewGroup,若不重写,就会执行和单子View中相同逻辑,不会测量子View。一般会重写onMeasure()方法,循环测量子View。 |
onLayout()方法 | 单一View,不需要实现该方法。ViewGroup必须实现,该方法是个抽象方法,实现该方法,来对子View进行布局。 |
onDraw()方法 | 无论单一View,或者ViewGroup都需要实现该方法,因其是个空方法 |
十六、高效加载Bitmap的方式
1、高效加载Bitmap的流程
(1)将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片。
(2)从BitmapFactory.Options中取出图片的原始宽高信息,它们对应于outWidth和outHeight参数。
(3)根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize。
(4)将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片。
2、使用示例
- 方法定义:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
//加载图片
BitmapFactory.decodeResource(res,resId,options);
//计算缩放比
options.inSampleSize = calculateInSampleSize(options,reqHeight,reqWidth);
//重新加载图片
options.inJustDecodeBounds =false;
return BitmapFactory.decodeResource(res,resId,options);
}
private static int calculateInSampleSize(BitmapFactory.Options options, int reqHeight, int reqWidth) {
int height = options.outHeight;
int width = options.outWidth;
int inSampleSize = 1;
if(height>reqHeight||width>reqWidth){
int halfHeight = height/2;
int halfWidth = width/2;
//计算缩放比,是2的指数
while((halfHeight/inSampleSize)>=reqHeight&&(halfWidth/inSampleSize)>=reqWidth){
inSampleSize*=2;
}
}
return inSampleSize;
}
- 使用:
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(),R.mipmap.ic_launcher,100,100);
十九、Android Context详解
1、Context的源码
-
描述:Context提供了关于应用环境全局信息的接口
-
是一个抽象类,它的执行被Android系统所提供
public abstract class Context {
/**
* File creation mode: the default mode, where the created file can only
* be accessed by the calling application (or all applications sharing the
* same user ID).
* @see #MODE_WORLD_READABLE
* @see #MODE_WORLD_WRITEABLE
*/
public static final int MODE_PRIVATE = 0x0000;
public static final int MODE_WORLD_WRITEABLE = 0x0002;
public static final int MODE_APPEND = 0x8000;
public static final int MODE_MULTI_PROCESS = 0x0004;
.
.
.
}
2、Context与其实现类的关系图
-
ContextWrapper类
-
如其名所言,这只是一个包装而已,ContextWrapper构造函数中必须包含一个真正的Context引用
-
同时ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象
-
调用ContextWrapper的方法都会被转向其所包含的真正的Context对象。
-
-
ContextThemeWrapper类
-
如其名所言,其内部包含了与主题(Theme)相关的接口,
-
只有Activity会需要主题
-
-
ContextImpl类
- 是Context的具体实现类
-
Context在应用中的具体实现
-
(1)Application
-
(2)Activity
-
(3)Service
-
3、Context的作用域(什么情况下该用哪种类型的Context)
- 凡是跟UI相关的,都应该使用Activity做为Context来处理
4、如何获取Context
-
(1)
View.getContext
- 返回当前View对象的Context对象,通常是当前正在展示的Activity对象。
-
(2)
Activity.getApplicationContext
-
获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context。
-
与
getApplication()
区别:结果都是获取到Application的Context,但getApplication()只有在Activity和Service中才能调用的到
-
-
(3)
ContextWrapper.getBaseContext()
- 用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。
-
(3)
Activity.this
- 返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以。
5、Context引起的内存泄漏
- (1)错误的单例
- 原因:生命周期不一致,持有已结束生命周期对象的引用导致无法GC
public class Singleton {
private static Singleton instance;
private Context mContext;
private Singleton(Context context) {
this.mContext = context;
}
public static Singleton getInstance(Context context) {
if (instance == null) {
instance = new Singleton(context);
}
return instance;
}
}
- (2)View持有Activity的引用
- 原因:被static修饰的是常驻内存,MainActivity是它的间接引用,MainActivity被销毁时,也不能被GC掉
public class MainActivity extends Activity {
private static Drawable mDrawable;
@Override
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_main);
ImageView iv = new ImageView(this);
mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
iv.setImageDrawable(mDrawable);
}
}
6、正确使用Context
-
(1)当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context。
- Application的Context对象可以理解为随着进程存在的
-
(2)不要让生命周期长于Activity的对象持有到Activity的引用。
-
(3)尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。
更多推荐
所有评论(0)