android 知识点整理
aidl 跨进程
创建对应的 aidl 文件,service 和 client 保持同一份 aidl 文件
service中使用
服务端
Xxxx.Stub binder = new Xxxx.Stub() {
service 中的 IBinder onBind(Intent intent) 方法 返回该对象
客户端
创建 ServiceConnection 对象
serviceConnection = new ServiceConnection() {
实现 onServiceConnected 和 onServiceDisconnected
在 onServiceConnected 中连接服务
xxx = Xxx.Stub.asInterface(service);
监听服务对象死亡回调,处理重连
xxx.asBinder().linkToDeath
activity中使用 2 个 activity 之间实现 aidl跨进程调用
创建对应的 aidl 文件,service 和 client 保持同一份 aidl 文件
无法明确确定服务端和客户端
A 端
创建 IBinder 对象
new Xxx.Stub(){}
通过 Bundle 的 putBinder 方法 把 上面的 IBinder 对象 传递给 B 端
B 端
解析 A 端传过来的 IBinder 对象
xxx = Xxx.Stub.asInterface(getIntent().getExtras().getBinder("xxx"))
通过 xxx 对象来传输数据
Handler
Handler(@NonNull Looper looper)
Handler(@NonNull Looper looper, @Nullable Callback callback)
Looper 消息循环体
Callback 消息处理回调 handleMessage 方法中处理回调业务
Handler(多个) + Looper(1个) + MessageQueue(1个)
new Handler
1、mLooper = Looper.myLooper(); 如果不传 Looper 则创建
2、mQueue = mLooper.mQueue; 拿到 looper 的 消息队列
3、赋值 callback
post方法 会将 runnable 转换成 Message 的 callback 最终会走到 sendMessageAtTime
sendMessageAtTime
调用 enqueueMessage 方法将message添加到消息队列
添加 message 时 调用 nativeWake 唤醒 looper循环
Looper prepare 创建 Looper 对象,创建消息队列,并加载 ThreadLocal 中
Looper loop
第一个死循环 调用 loopOnce
调用 mQueue.next() 取消息,此处有第二个死循环
调用 nativePollOnce(ptr, nextPollTimeoutMillis) 如果没消息则一直等待,直到nativeWake唤醒
拿到消息,则 调用 dispatchMessage 回调消息处理逻辑
如果 message 中的 callback 不为空,则直接执行 runnable 否则回调 handleMessage 方法处理
内存泄露的原因
1、创建非静态内部类 handler 对象
2、小米队列中的 message 没处理是,message会一直持有 handler 对象,handler 对象会持有 activity对象导致内存泄露
解决方案
1、创建静态内部类 handler 对象 handler 内部持有 WeakReference<Activity> reference;
2、onDestory 中 调用 handler removeCallbacksAndMessages(null))回收资源,清空 message队列
Handler是如何实现延时消息的?
handler核心的发送消息的方法是sendMessage
这个delayMillis就是延时的时间。
将DelayMillis加上当前开机的时间(这里可以理解就是这个time就是,现在的时间+需要延迟的时间=实际执行的时间)
把message发送到messageQueue里面,每个消息都会带有一个uptimeMillis参数,这就是延时的时间。
这个msg根据实际执行时间(uptimeMillis)进行排序插入到queue里面
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}
可以看到这里也是一个for循环遍历队列,核心变量就是nextPollTimeoutMillis。可以看到,计算出nextPollTimeoutMillis后就调用nativiePollOnce这个native方法。这里的话大概可以猜到他的运行机制,因为他是根据执行时间进行排序的,那传入的这个nextPollTimeoutMillis应该就是休眠时间,类似于java的sleep(time)。休眠到下一次message的时候就执行。那如果我在这段时间又插入了一个新的message怎么办,所以handler每次插入message都会唤醒线程,重新计算插入后,再走一次这个休眠流程。
linux中的epoll机制
Android中Looper死循环为什么不会卡死主线程?
类加载双亲委托机制
java 类加载采用双亲委托机制
类加载前先判断类是否已经被加载,判断过程:一直往上判断父类是否已经加载直到根类加载器,如果已加载则不做处理
未加载,则从根类加载器一直往下尝试加载
android 的类加载器
1、BoottClassLoder 加载 java 类
2、PathClassLoder 加载 so 等资源
3、DexClassLoder 加载 dex 文件资源
RecyclerView
基本使用
1、定义 RecyclerView
2、设置 布局管理器 setLayoutManager
LinearLayoutManager
GridLayoutManager
StaggeredGridLayoutManager
3、设置 setAdapter
4、创建适配器
extends RecyclerView.Adapter<RecyclerView.ViewHolder>{}
RecyclerView.ViewHolder 适配器的布局对象
onCreateViewHolder 创建布局对象
onBindViewHolder 处理适配器的数据处理逻辑
getItemCount 获取数据源数量
getItemViewType 处理多布局业务,通过标志位处理创建不同的布局逻辑
适配器基本方法
1、notifyDataSetChanged 全量刷新数据源
2、notifyItemInserted 只刷新新增的数据源
3、notifyItemChanged 只刷新指定位置的数据源
4、notifyItemRemoved 只刷新删除的数据源
5、notifyItemRangeChanged 刷新指定数量的数据更新
6、notifyItemRangeInserted 刷新指定数量的数据新增
7、notifyItemRangeRemoved 刷新指定数量的数据删除
RecyclerView 缓存
RecyclerView缓存机制缓存的就是ViewHolder
RecyclerView的缓存分为四级
1、Scrap
屏幕内的缓存数据,就是相当于换了个名字,可以直接拿来复用。
2、Cache
刚刚移出屏幕的缓存数据,默认大小是2个
3、ViewCacheExtension
只有一个public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position, int type);让开发者重写通过position和type拿到ViewHolder的方法,却没有提供如何产生ViewHolder或者管理ViewHolder的方法
4、RecycledViewPool
当Cache缓存满了以后会根据FIFO(先进先出)的规则把Cache先缓存进去的ViewHolder移出并缓存到RecycledViewPool中,RecycledViewPool默认的缓存数量是5个。RecycledViewPool与Cache相比不同的是,从Cache里面移出的ViewHolder再存入RecycledViewPool之前ViewHolder的数据会被全部重置,相当于一个新的ViewHolder,而且Cache是根据position来获取ViewHolder,而RecycledViewPool是根据itemType获取的,如果没有重写getItemType()方法,itemType就是默认的。因为RecycledViewPool缓存的ViewHolder是全新的,所以取出来的时候需要走onBindViewHolder()方法。
事件拦截机制
MotionEvent.ACTION_DOWN - 手指按下
父控件recyclerView的dispatchTouchEvent开始,InterceptTouchEvent返回的是false,也就是不拦截。那么流程正常进行,一直dispatch到了itemView,接着itemView响应了MOVE_DOWN的事件
MotionEvent.ACTION_MOVE - 手指移动
父控件的recyclerView的dispatchTouchEvent,但是发现这是个MOVE事件检测后InterceptTouchEvent返回的是true。那么好了,要开始拦截了。
调用dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits),传给原本的target一个MOTION_CANCEL事件让target将这个CANCEL事件传递下去,这个事件会使得child一下的view的触摸事件全部被取消掉。接着dispatchTouchEvent返回true,层层传递给父亲。
注意这次的MOTION_MOVE事件并没有使得recyclerView滚动,但是recyclerView的mTargetView被置为了null,也就是传递链的targetView被置为了recyclerView。
recyclerView的时候,recyclerView就会传递给自己的onTouchEvent,这样就可以正常地执行这个滚动操作了
优化方案
1、setHasFixedSize(true)
当Item的高度如是固定的,设置这个属性为true可以提高性能
2、避免创建过多监听器 监听器设置成公共属性
3、bindViewHolder 内部避免执行耗时操作
4、采用局部刷新 方法
5、复用RecycledViewPool setRecycledViewPool(recycledViewPool)
6、使用DiffUtil局部刷新 DiffUtil是androidx.recyclerview.widget包下的一个工具类
7、优化滑动操作
onScrollStateChanged
SCROLL_STATE_IDLE 屏幕停止滚动
SCROLL_STATE_DRAGGING 屏幕滚动且用户使用的触碰或手指还在屏幕上
SCROLL_STATE_SETTLING 由于用户的操作,屏幕产生惯性滑动
Gilde同时也为我们提供了两个方法:
resumeRequests() 开始加载图片
pauseRequests() 停止加载图片
case SCROLL_STATE_IDLE:
case SCROLL_STATE_DRAGGING:
case SCROLL_STATE_SETTLING:
事件分发机制
事件分发的本质
将点击事件(MotionEvent)传递到某个具体的View & 处理的整个过程
事件在哪些对象之间进行传递
Activity、ViewGroup、View。Android的UI界面由Activity、ViewGroup、View 及其派生类组成
事件分发的顺序
Activity -> ViewGroup -> View
事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到 View
事件分发过程由哪些方法协作完成
Activity dispatchTouchEvent onTouchEvent
ViewGroup dispatchTouchEvent onInterceptTouchEvent onTouchEvent
view dispatchTouchEvent onTouchEvent
Activity的事件分发机制
1、执行dispatchTouchEvent()进行事件分发
getWindow().superDispatchTouchEvent(ev)
若getWindow().superDispatchTouchEvent(ev)的返回true
则Activity.dispatchTouchEvent()就返回true,则方法结束。即 :该点击事件停止往下传递 & 事件传递过程结束
否则:继续往下调用Activity.onTouchEvent
onTouchEvent
判断是否事件在边界外,是 - 消费事件 否 - 往下传
ViewGroup的事件分发机制
ViewGroup的事件分发机制从dispatchTouchEvent()开始。
1、先判断是否拦截 onInterceptTouchEvent
2、onInterceptTouchEvent false - 不拦截 true - 拦截
3、如果不拦截,则循环拿到子类,并调用子类的dispatchTouchEvent方法
4、如果拦截,调用自己的 onTouchEvent 方法消费
View的事件分发机制
分发机制从dispatchTouchEvent()开始
1、判断 view 是否注册了 touch事件监听
2、如果设置监听,则调用 onTouch 返回 true 自己消费 返回 false 继续往下传
3、如果没有监听,则调用 onTouchEvent 返回 true 自己消费 返回 false 将事件给上级 viewGroup onTouchEvent 事件
Java泛型与Kotlin泛型
Java泛型的基本知识
class ObjectA<T>{}
interface InterfaceB<T>{}
private <T> void fill(ArrayList<T> numbers) {}
限制泛型的范围
<T extends Parent> 只能输出
<? super Child> 只能输入
extends限制的是输出类型的上限,super限制的输入类型下限
Kotlin泛型与Java泛型的差异
inline fun <reified T> printGenerality(data: T) {
println(T::class.java)
}
Kotlin的泛型的型变
Kotlin把泛型拆分为输入泛型和输出泛型,关键字为in和out
<in T, out E>
WebView 性能轻量优化
1、结果请求,gZip压缩
2、glide 缓存机制
3、WebSettings 配置
CacheMode LOAD_DEFAULT
4、H5 缓存机制
Dom Storage 存储机制 setDomStorageEnabled
Web 数据库存储机制 setDatabaseEnabled
5、HTML 应用程序缓存机制 setAppCacheEnabled
6、浏览器缓存机制
通过 HTTP Header 部分的 Cache-Control(或 Expires)和 Last-Modified(或 Etag)等字段来控制文件缓存的机制
7、客户端 H5 缓存机制考虑
资源文件预置
替换 HTML 图片标签,加载 url 前,设置不加载图片资源,页面加载完成时,再加载图片,请求拦截
OKHttp
报错 java.lang.IllegalStateException: closed 处理
一旦连续两次调用response.body().string(),就会抛出该异常
通过BufferedSource获取
ResponseBody responseBody = response.body()
BufferedSource source = responseBody.source()
source.request(Long.MAX_VALUE)
Buffer buffer = source.buffer()
Charset UTF8 = Charset.forName("UTF-8")
String body = buffer.clone().readString(UTF8)
Android属性动画
ViewPropertyAnimator
View.animate() 获取 ViewPropertyAnimator 对象
animate.translationX(500);
animate.alpha(0.6f);
animate.rotation(90);
animate.scaleX(0.1f);
ObjectAnimator
ObjectAnimator.ofFloat(tv, "translationX", 500).setDuration(2000).start();或
ObjectAnimator.ofFloat(tv, View.TRANSLATION_X, 500).setDuration(2000).start();
ValueAnimator
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 500);
valueAnimator.setDuration(2000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int animatedValue = (Integer) animation.getAnimatedValue();
tv.getLayoutParams().width = animatedValue;
tv.requestLayout();
}
});
valueAnimator.start();
Android 如何保存与恢复自定义View的状态
当Activity调用了onSaveInstanceState方法后,便会对它的View Tree进行保存,而进一步对每一个子View调用其onSaveInstanceState()方法来保存状态。
onSaveInstanceState()方法返回Parcelable对象,也即是序列化对象
恢复状态也很简单了,因为在onRestoreInstanceState(Parcelable)方法内,根据传递进来的Parcelable参数,我们可以拿到我们之前保存的数据,再根据需要进行赋值或者调用某些方法来恢复状态就行了
如何优雅、高效地恢复Activity数据
使用 ViewModel 和 onSaveInstanceState()方法。
使用ViewModel来保存因Configuration changes所丢失的数据
使用onSaveInstanceState()备份数据,在系统杀死、重建应用之后恢复
设计模式
单例模式
饿汉 (直接创建)
懒汉 (判空才创建)
双重检查锁
静态内部类
枚举
实例化一次可以大大提高性能 保证运行的时候始终有一个实例在内存中
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
Build模式
Person(Builder builder)
Builder{
Person build(){
return new Person(this);
}
观察者模式
Observer 观察者
class MyObserver extends Observer{
update(Observable o){
if(null!=o) o.deleteObservers()
}
}
Observable 可观察者
class MyObservable extends Observable{
notifyObservers(T t){
setChanged()
notifyObservers(t)
}
}
observer = ofObserver()
MyObservable mO = new MyObservable()
mO.addObserver(observer)
mO.notifyObservers(t)
原型模式
class Person implements Cloneable{
实现clone方法中的拷贝逻辑
@Override
public Object clone(){
深拷贝 重新 new 一个对象
浅拷贝 不重新 new 一个对象
策略模式
类的继承和组合
interface Strategy {
void travel();
WalkStrategy implements Strategy{
PlaneStrategy implements Strategy{
TravelContext {
Strategy strategy;
public void travel() {
if (strategy != null) {
strategy.travel();
}
}
责任链模式
interface Node{
void hanlde()
void nextNode(Node node)
}
class Node1 implements Node{
private Node next;
@Override
public void hanlde() {
if (next!= null) {
next.hanlde();
}
}
@Override
public void nextNode(Node node) {
this.next = node;
}
}
状态模式
当一个对象的内在状态改变时,其行为也随之改变
设置状态基类,实现功能方法
实现对应状态类
设置可切换状态对象来实现操作
ViewModel
ViewModel是负责管理Activity和Fragment的数据的类,他独立于Activity和Fragment之外,即使Activity和Fragment因为屏幕旋转等原因销毁,只要Activity与Fragment重新创建,即可与原先的ViewModel重新绑定。
ViewModelProvider(this).get(YourViewModel.class);
kotlin
by activityViewModels()
by viewModels()
从ViewModelStore 中拿到 ViewModel
ViewModelStore 内部的 hashmap 保存 viewmodel 数据
如果能拿到 则返回,否则 Factory创建一个信息,保存到 ViewModelStore 中
Activity是如何管理ViewModelStore的
##Activity.getViewModelStroe
如果 ViewModelStore 不存在 则
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
nc.viewModelStore
或者
new ViewModelStore();
Activity销毁时,通过Activity.retainNonConfigurationInstances()创建
Activity创建后,在Activity.attch()中拿到NonConfigurationInstances
Fragment如何管理ViewModelStore
FragmentManager.getViewModel()返回ViewModel
ViewModelStore getViewModelStore(@NonNull Fragment f) {
return mNonConfig.getViewModelStore(f);
}
Kotlin ViewModel KTX-内存泄露
ViewModel KTX 中提供了 viewModelScope ,目的是为了减少协程内存泄露。
viewModelScope.launch(Dispatchers.Main) {
ViewModel 当被清除时会回调 onClear() 方法,我们从这个方法中去找对应取消协程相关的操作。
Glide
优点
Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图。甚至是 Video
生命周期集成
高效的缓存策略
内存开销小 RGB_565 gif 的是否需要改成 ARGB_8888
使用Bitmap Pool可以使Bitmap复用
对于回收的Bitmap会主动调用recycle,减小系统回收压力
RequestManager = Glide.with(this)
getRetriever(activity).get(activity);
Glide glide = builder.build(applicationContext);
glide 的 builder 模式创建
registerComponents
Glide build(@NonNull Context context) {
设置各种线程池,执行器、bitmapPool、并且创建 Engine 对象
@NonNull
public RequestManager get(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
} else if (Util.isOnMainThread() && !(context instanceof Application)) {
if (context instanceof FragmentActivity) {
return get((FragmentActivity) context);
} else if (context instanceof Activity) {
return get((Activity) context);
} else if (context instanceof ContextWrapper) {
return get(((ContextWrapper) context).getBaseContext());
}
}
return getApplicationManager(context);
}
return supportFragmentGet(activity, fm, null );
return supportFragmentGet(fragment.getActivity(), fm, fragment);
return fragmentGet(activity, fm, null );
return get(fragment);
依托于activity之下碎片fragment来进行周期的回调
getRequestManagerFragment中通过创建RequestManagerFragment并且添加到activity中,同时把RequestManagerFragments从缓存队列pendingRequestManagerFragments中移除,进行加载;
glide的相关配置:图形属性,
.load(url)
loadGeneric(@Nullable Object model) {
public void onSizeReady(int width, int height) {
engine.load(
loadFromActiveResources(
loadFromCache(
最后
engineJob.addCallback(cb);
engineJob.start(decodeJob);
Glide在RecyclerView中出现item错乱解决方案
解决方案是对比控件的Tag,如果用Glide加载则需要在res/value文件夹中新建一个ids.xml文件,设置一个Id
app / activity 启动流程
涉及到的类
Instrumentation
ActivityManagerService
ActivityThread
1、点击桌面图标
startActivity(intent, optsBundle);
2、启动 activity
调用 Instrumentation execStartActivity
获取AMS实例
ActivityManager.getService()
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
调用AMS startActivity方法 startActivity
不存在,则创建进程
调用 AMS startProcessLocked 创建
通过 socket 通讯 成 zygote 进程 fork 一个进程
ActivityThread main 方法
Looper.prepareMainLooper();
Looper.loop();
跨进程通信,调用AMS的attachApplication方法 创建 application
ActivityThread类的sendMessage()发送 LAUNCH_ACTIVITY
执行 performLaunchActivity 方法 启动 activity
Toast的显示流程
通过 aidl 跨进程获取INotificationManager
INotificationManager service = getService();
service.enqueueToast
lifecycle
Lifecycle
生命周期事件的枚举
enum Event
所处状态的定义
enum State
添加观察者
addObserver
移除观察者
removeObserver
当前状态
getCurrentState
Lificycle 的唯一子类 LifecycleRegistry
LifecycleRegistry(@NonNull LifecycleOwner provider)
handleLifecycleEvent(@NonNull Lifecycle.Event event)
而调用 handleLifecycleEvent 的地方也是 Fragment/Activity 生命周期回调的地方,这样就把生命周期回调 → Event → State 绑定了起来:
LifecycleOwner
Lifecycle getLifecycle();
activity 实现 LifecycleOwner 并成getLifecycle获取Lifecycle(LifecycleRegistry)来实现生命周期的回调
生命周期事件如何在 Activity 和 Fragment 中分发的?
FragmentActivity 的内部类 HostCallbacks 中
class HostCallbacks extends FragmentHostCallback<FragmentActivity> implements
ViewModelStoreOwner,
OnBackPressedDispatcherOwner,
ActivityResultRegistryOwner,
FragmentOnAttachListener
mFragments = FragmentController.createController(new HostCallbacks());
真实的LifecycleRegistry
ComponentActivity
关键点就在这个 ReportFragment 中。我们注意到 ReportFragment.dispatch 中获取到了 Activity 的 Lifecycle,并调用了 dispatchLifecycleEvent 方法从而将生命周期事件通知到了 Activity 的 Lifecycle:
static void dispatch(@NonNull Activity activity, @NonNull Lifecycle.Event event)
LiveData
粘性事件所带来问题的解决方案
1、反射干涉Version
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
每次setValue或postValue时,mVersion会+1,只要mLastVersion>=mVersion即证明之前有过setValue或postValue。现在我们想使在observer调用前的setValue方法不被分发出去,只需要在调用observer之前的某个节点处改,变使其mLastVersion = mVersion即可。
2、UnPeekLiveData
public class ProtectedUnPeekLiveData<T> extends LiveData<T> {
protected boolean isAllowNullValue;
private final HashMap<Integer, Boolean> observers = new HashMap<>();
public void observeInActivity(@NonNull AppCompatActivity activity, @NonNull Observer<? super T> observer) {
LifecycleOwner owner = activity;
Integer storeId = System.identityHashCode(observer);
observe(storeId, owner, observer);
}
private void observe(@NonNull Integer storeId,
@NonNull LifecycleOwner owner,
@NonNull Observer<? super T> observer) {
if (observers.get(storeId) == null) {
observers.put(storeId, true);
}
super.observe(owner, t -> {
if (!observers.get(storeId)) {
observers.put(storeId, true);
if (t != null || isAllowNullValue) {
observer.onChanged(t);
}
}
});
}
@Override
protected void setValue(T value) {
if (value != null || isAllowNullValue) {
for (Map.Entry<Integer, Boolean> entry : observers.entrySet()) {
entry.setValue(false);
}
super.setValue(value);
}
}
protected void clear() {
super.setValue(null);
}
}
Canvas绘制优化
数据量太大的时候,可以考虑在UI效果和流畅度上面平衡一下;
setAntiAlias( ) 抗锯齿功能,会消耗较大资源,绘制图形速度会变慢;
setDither(boolean dither); setFilterBitmap(boolean filter) 等...
显示的效果越好 ,效率越差....
异步加载, 数据量大但是你界面也不能卡啊 ,加载到数据后用线程池维护一下,创建线程也是个耗资源的事情;
onDraw() 方法里面尽量不要用for循环
可以用path去装载要绘制的点,然后drawPath()来代替for循环
用Canvas生成bitmap , 再到这个bitmap上面drawPath( ), 最后将这个bitmap传递给你自己的view的onDraw( )方法上面去执行drawBitmap( ) , 这样相当于主线程上只绘制一张图而已
Canvas生成的bitmap,每次执行canvas.drawXXX() 都会生成新的bitmap, 绘制增量数据的时候只需要再次调用canvas.drawXXX(),然后就可以生成新的bitmap,这时候将bitmap传递给你自己的view的onDraw( )就可以生成新的效果;
RGB_565 替换 ARGB_8888 , 先创造个小的bitmap , drawBitmap的时候将bitmap拉大;例如:屏幕是10241024, 如果使用Bitmap.createBitmap(Width(), Height() , Bitmap.Config.RGB_565);那么bitmap的大小是: (10241024*2) / (1024 *1024 )= 2M;
将bitmap宽高缩小, matrix.setScale(float sx, float sy) 放大;
蓝牙 BLE
GATT,Service,Characteristic 基本概念
GATT
GATT(Generic Attribute Profile),描述了一种使用ATT的服务框架
该框架定义了服务(Server)和服务属性(characteristic)的过程(Procedure)及格式
Procedure定义了characteristic的发现、读、写、通知(Notifing)、指示(Indicating)
及配置characteristic的广播
GATT中最上层是Profile,Profile由一个或多个服务(Service)组成 服务是由Characteristics组成,或是其他服务的引用(Reference) Characteristic包含一个值(Value),可能包含该Value的相关信息
Service
Service 可以理解为一个功能的集合。设备中每一个不同的 Service 都有一个 128 bit 的 UUID 作为这个 Service 的独立标志。
Characteristic
在 Service 下面,又包括了许多的独立数据项,我们把这些独立的数据项称作 Characteristic。同样的,每一个 Characteristic 也有一个唯一的 UUID 作为标识符。
android BLE 开发流程
1、打开蓝牙
2、扫描蓝牙 startLeScan
3、发现蓝牙
4、连接设备 connectGatt
5、发现设备服务 discoverServices
6、读写数据
writeCharacteristic
readCharacteristic
getValue
7、断开连接
8、关闭蓝牙
BluetoothAdapter 拥有基本的蓝牙操作
BluetoothDevice 远程蓝牙设备
BluetoothGatt Bluetooth GATT 的基本功能。例如重新连接蓝牙设备,发现蓝牙设备的 Service 等等
BluetoothGattService 通过 BluetoothGatt getService 获得,如果当前服务不可见那么将返回一个 null。这一个类对应上面说过的 Service。我们可以通过这个类的 getCharacteristic(UUID uuid) 进一步获取 Characteristic 实现蓝牙数据的双向传输
BluetoothGattCharacteristic 对应上面提到的 Characteristic。通过这个类定义需要往外围设备写入的数据和读取外围设备发送过来的数据。
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-feature android:name="android.hardware.location.gps"/>
val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
val bluetoothAdapter = bluetoothManager?.adapter
bluetoothAdapter?.isEnabled!!
bluetoothAdapter.startLeScan(object : BluetoothAdapter.LeScanCallback
val scanner = bluetoothAdapter.bluetoothLeScanner
scanner.startScan(filters, object : ScanCallback
BluetoothDevice.ConnectGatt / BluetoothGatt.connect
device?.connectGatt(context, true, object : BluetoothGattCallback() {
override fun onConnectionStateChange(
gatt: BluetoothGatt?,
status: Int,
newState: Int
) {
super.onConnectionStateChange(gatt, status, newState)
}
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray
) {
super.onCharacteristicChanged(gatt, characteristic, value)
}
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
super.onServicesDiscovered(gatt, status)
}
})
val bluetoothGattService = gatt?.getService(UUID.fromString(""))
val bluetoothGattCharacteristic = bluetoothGattService?.getCharacteristic(UUID.fromString(""))
gatt?.readCharacteristic(bluetoothGattCharacteristic!!)
gatt?.writeCharacteristic(bluetoothGattCharacteristic!!, ByteArray(""), 1)
ThreadLocal
ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。
每个Thread对象中保存一个ThreadLocalMap,ThreadLocal.set()时,取出当前线程的ThreadLocalMap(没有就创建),然后以ThreadLocal作为key,value作为值塞进去。这样一来Thread被销毁时,对应的ThreadLocalMap也会自动被回收。
ThreadLocal.remove
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
kotlin 委托、扩展、inline 等
kotlin 单例实例
class Singleton private constructor() {
companion object {
@Volatile private var instance: Singleton? = null
fun getInstance(): Singleton {
return instance ?: synchronized(this) {
instance ?: Singleton().also { instance = it }
}
}
}
}
委托
一个对象将消息委托给另一个对象来处理
Kotlin 通过 by 关键字可以更加优雅地实现委托。
类委托: 一个类的方法不在该类中定义,而是直接委托给另一个对象来处理。
class <类名>(b : <基础接口>) : <基础接口> by <基础对象>
类的继承 和 组合
属性委托: 一个类的属性不在该类中定义,而是直接委托给另一个对象来处理。
val/var <属性名> : <类型> by <基础对象>
var prop: String by Delegate()
class Delegate {
private var _realValue: String = "彭"
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
println("getValue")
return _realValue
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("setValue")
_realValue = value
}
}
局部变量委托: 一个局部变量不在该方法中定义,而是直接委托给另一个对象来处理。
val lazyValue: String by lazy {
println("Lazy Init Completed!")
"Hello World."
}
延迟属性委托 lazy
val lazyValue: String by lazy {
可观察属性 ObservableProperty
var name: String by Delegates.observable("初始值") {
使用 Map 存储属性值
class User(val map: Map<String, Any?>) {
val name: String by map
}
key 和 属性要一一对应
Kotlin 委托 + Fragment / Activity 传参
lateinit
lateinit:用来修饰var类型成员变量,用来表示该变量可以在晚些时候初始化,用来避免不必要的空检查
if (this::name.isInitialized) 判断是否初始化
编译器不要去检查变量是否为空。然后我们在引用变量的时候就判断一下,如果变量为空,也就是没有初始化,那么就抛出异常,否者就正常返回。
lazy
lazy可以用来实现val类型变量的延迟初始化
public actual fun <T> lazy(initializer: () -> T): Lazy<T> =
SynchronizedLazyImpl(initializer)
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
(_v2 as T)
} else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
Kotlin(let,with,run,apply,also)函数
let fun T.let(block: (T) -> R): R = block(this) 扩展函数
with fun with(receiver: T, block: T.() -> R): R = receiver.block() 内联函数
run fun T.run(block: T.() -> R): R = block()
apply fun T.apply(block: T.() -> Unit): T { block(); return this }
also fun T.also(block: (T) -> Unit): T { block(this); return this }
需要返回操作对象 apply also
需要返回运算结果 let with run
如果不改变返回值使用also或apply
如果是设置属性值使用apply或run,作为参数运算使用also或let
with基本可以不用
java 反编译
javap反编译探寻内部类为何能访问外部私有成员
Java语言理论告诉我们内部类对象持有外部类对象的一个引用,这说明内部类与外部类还是独立的两个类,只不过内部类对象通过持有外部类的对象的引用来维持这个关系。
内部类构造函数会自动添加一个外部类的实例
内部类创建了一个外部类对象的引用,并通过改造构造函数将其传入内部类。
内部类如果不访问外部类的私有成员,并不会生成访问方法,而是需要的时候才生成。
外部类生成的访问方法,是static类型的,并传入外部类对象引用,返回值与参数根据需要访问的变量和函数相对应。
LeakCanary
WeakReference和ReferenceQueue机制
Leak Canary其实内部就是使用这个机制来监控对象是否被回收了
JVM在合适的时间触发GC,并将回收的WeakReference对象放入与之关联的ReferenceQueue中表示GC回收了该对象,Leak Canary通过上面的检测返现有些对象的生命周期本该已经结束了,但是任然在占用内存,这时候就判定是已经泄露了
能够作为 GC Root的对象
虚拟机栈,在大家眼里也叫作栈(栈帧中的本地变量表)中引用的对象;
方法区中类静态属性引用的对象;
方法区中常量引用的对象;
本地方法栈中JNI引用的对象;
Leak Canary是如何判断Activity或Fragment的生命周期结束了呢?
Leak Canary是通过 Application的内部类ActivityLifecycleCallbacks检测Activity的生命周期是否结束了,如果回调了onActivityDestroyed方法,那么表示Activity的声明周期已经结束了,这时候就要执行GC检测了。
对于Fragment是通过FragmentManager的内部接口FragmentLifecycleCallbacks检测Fragment的声明周期的类似ActivityLifecycleCallbacks接口。
RefWatcher类是用来监控对象的引用是否可达,
onActivityDestroyed方法方法中会调用refWatcher.watch方法
LeakCanary是通过WeakReference+Reference机制检测对象是否能被回收;
LeakCanary检测的时机是当某组件的生命周期已经结束,才会触发检测;
调用removeWeaklyReachableReferences方法,从Set中移除不能访问引用,意思就是GC之后该引用对象是否加入队列了,如果已经加入队列说明不会造成泄漏的风险,就将引用从Set集合中移除;
紧接着调用gcTrigger.runGc方法尝试GC,看看能不能回收引用对象;
再次调用removeWeaklyReachableReferences方法,从Set中移除不能访问引用,意思就是GC之后该引用对象是否加入队列了,如果已经加入队列说明不会造成泄漏的风险,也就是在手动触发GC之后,再次检测是否可以回收对象;
*最后通过gone(reference)方法检测Set集合中是否还存在该对象,如果存在说明已经泄漏了,就像前面说的,如果发生GC并且对象是可以被回收的,那么就会加入引用队列, 最后到这一步说明该对象按理来说声明周期是已经结束了的,但是通过前面的GC却不能回收,说明已经造成了内存泄漏,那么解析hprof文件,得到该对象的引用链,也就是要触发堆转储。
kotlin 协程
一种kotlin语言层面支持的 线程调度框架
CPS(Continuation-Passing-Style, 续体传递风格)
class Test {
interface Continuation {
void next(int result);
}
public static void plus(int i1, int i2, Continuation continuation) {
continuation.next(i1 + i2);
}
public static void main(String[] args) {
plus(1, 2, result -> System.out.println(result));
}
}
很简单吧?这就是CPS风格,函数的结果通过回调来传递
suspend fun <T> CompletableFuture<T>.await(): T
编译后
fun <T> CompletableFuture<T>.await(continuation: Continuation<T>): Any?
CoroutineContext
CoroutineContext中的元素
Job 协程的后台工作,可用来将协程取消
CoroutineExceptionHandler 错误处理
ContinuationInterceptor 使用Dispatcher去指定对应的协程执行线程
CoroutineName 协程的名称
Context也可以支持多个上下文元素的拼接,使用+/plus运算符
val context = job + dispatcher + errorHandle
非阻塞:意味着在执行某个操作时,不会导致当前线程被阻塞而无法执行其他任务。
挂起:当协程遇到一个需要等待的操作时,它可以被挂起,即暂停执行。挂起的协程会保存当前的执行状态,包括局部变量、程序计数器等信息。当等待的操作完成后,协程可以被恢复执行,从挂起的地方继续执行下去。
什么是「非阻塞式挂起」
对于线程阻塞很好理解,现实中的例子就是交通堵塞,核心有 3 点:
前面有障碍物,你过不去(线程卡了)
需要等障碍物清除后才能过去(耗时任务结束)
除非你绕道而行(切到别的线程)
「非阻塞式」这个是挂起的一个特点,协程的挂起,就是非阻塞式的,而不是传统的「阻塞式的挂起」。阻塞不阻塞,都是针对单线程讲的,切了线程,肯定是免谈的,你TM都跑到别的线程了,之前的线程就自由了,可以继续做别的事情了。
「非阻塞式」的挂起就是协程在挂起的同时切线程这个概念。
java 语法类
synchronized和ReentrantLock有什么区别
synchronized 是 Java 内建的同步机制
ReentrantLock ,通常翻译为再入锁
线程安全是一个多线程环境下正确性的概念,也就是保证多线程环境下 共享的 、 可修改的 状态的正确性,这里的状态反映在程序中其实可以看作是数据
线程安全需要保证几个基本特性: 原子性(automic)、可见性(volatile)、有序性(锁)
ReentrantLock 。它是表示当一个线程试图获取一个它已经获取的锁时,这个获取动作就自动成功,这是对锁获取粒度的一个概念,也就是锁的持有是以线程为单位而不是基于调用次数。
再入锁可以设置公平性( fairness ),我们可在创建再入锁时选择是否是公平的。
使用synchronized,我们根本 无法进行 公平性的选择,其永远是不公平的,这也是主流操作系统线程调度的选择
1.带超时的获取锁尝试
2.可以判断是否有线程,或者某个特定线程,在排队等待获取锁。
3.可以响应中断请求。
java 创建对象的方法
1、new
2、clone
3、反射
4、序列化 json 转 T
JVM内存模型
堆内存
堆内存可以划分为新生代和老年代
堆里面存放的都是对象的实例
栈内存
栈内存可以再细分为java虚拟机栈和本地方法栈
方法区
它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。
Java String
JVM为了提升性能,减少内存开销,引入入了String Pool(字符串常量池)来存储字符串对象,避免字符串的重复创建。
使用new关键字创建一个新的字符串对象时,涉及到的是两个对象**,String Pool 中的字符串对象,和Heap中的字符串对象。这一点一定要注意。
直接字面赋值返回的是 String Pool 中字符串对象的引用。
new关键字创建,总是会在Heap中生成一个新的字符串对象,并且返回的是这个新对象的引用。
一般是用 equals 比较,String重写了equals()方法
字符串对象是不可变的。天然就是线程安全的
"a"+"b"+"c" 反编译 new StringBuilder()
String 定义为 final 的原因
String被设置为不可变的原因,总结起来主要有两个:安全 和 高效。
在每次使用hashCode的时候不用都计算,只要计算一次就可以,这样更加高效。
String的不变性,保证了在其他类使用时候的正确性
网络
okhttp
HTTPS(Hyper Text Transfer Protocol Secure),超文本传输安全协议,设计HTTPS的目的就是为了安全传输数据
SSL/TLS是什么
TLS(Transport Layer Security),传输层安全协议,它的前身就是SSL(Secure Socket Layer),安全套接层协议。
HTTPS的通信过程
基于 TCP 三次握手 四次挥手
三次握手成功后,SSL/TLS连接——这个连接过程本质来说就是一个生成和传输对称加密密钥的过程
CDN加速原理
内容分发网络
增加一层新的CACHE(缓存)层,将网站的内容发布到最接近用户的网络”边缘“的节点,使用户可以就近取得所需的内容,提高用户访问网站的响应速度。
网络优化
常见的流量优化方案有数据缓存、数据压缩和图片压缩。
常见的质量优化方案有HttpDns 优化、协议版本优化以及资本优化。
网络监控:OkHttp 拦截器
另外就是 OkHttp 默认单个主机能并发执行的请求最多为 5 个,如果我们做了域名收敛,请求的都是同一个主机,就可以把 OkHttpClient 的 maxRequestsPerHost 的值设大一些。
资源压缩,图片
使用 HttpDNS 可以绕过运营商域名解析过程,HttpDNS 使用的不是传统的 DNS 协议,而是使用 HTTP 协议
socket 理解
TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/网际协议)
Socket套接字
socket 的诞生是为了应用程序能够更方便的将数据经由传输层来传输,所以它本质上就是对 TCP/IP 的运用进行了一层封装
基于 tcp 的 socket
Socket socket = new Socket("hostName", portNumber);
基于 udp 的 socket
DatagramSocket serverSocket = new DatagramSocket(12345)
一次https从请求到返回的过程:
1.1 尝试从浏览器DNS缓存获取对应的IP,如果没有获得IP,则执行下一步骤;
1.2 尝试从操作系统的DNS缓存获取对应的IP,如果没有获得IP,则执行下一步骤;
1.3 尝试从hosts文件进行域名解析,如果没有获得IP,则执行下一步骤;
1.4 电信运营商的本地DNS的DNS缓存进行搜索,如果没有获得IP,则执行下一步骤;
1.5 从运营商的根DNS服务器解析域名,如果是.com返回.com域的IP,如果是.cn返回.cn的域的IP;
1.6 获得.com的域IP之后,访问.com域的服务器,从.com域服务器返回baidu.com域名的IP。
获得IP之后,则尝试建立Browser和Server之间的链接。
TCP链接的建立-三次握手:
密钥协商:
3.1 Browser发往服务器的443端口,并携带Browser支持的加密和哈希算法;
3.2 Server收到请求,选择Browser支持的加密算法和哈希算法,返回Server数字证书(一般是RSA非对称加密的签名);
3.3 Browser得到Server的数字证书之后,Browser从内置的CA列表中查找对应CA的证书,如果查到,使用CA证书的公钥验证Server的数字证书是否合法,并判断是否已经过期等。如果验证合法,且证书没有过期,则使用Server返回的公钥PKs,否则告警Server的数字证书不可信;
3.4 Browser生成随机数N(不能使用伪随机数),PKs(N) = m发送给Server;
3.5 Server使用自己的SKs进行解密,即SKs(PKs(N)) = N;
3.6 这样Browser和Server获得了同样的一个保密的随机数N,二则将N作为对称加密(如AES,DES等对称加密算法)的密码。
发送请求
服务端处理请求
tcp四次挥手断开连接
HashMap
HashMap在JDK1.8和JDK1.7的区别
1、底层数据结构的变化
1.7中HashMap采用的底层数据结构是数组+链表的形式
1.8中HashMap采用的是数组+链表+红黑树的数据结构(当链表长度大于8且数组长度大于等于64时链表会转成红黑树,当长度低于6时红黑树又会转成链表)
红黑树能够大大提高查找效率
为啥要链表长度大于8且数组长度大于64才转成红黑树呢,简单来说就是节点太少的时候没必要转换数据结构,因为不仅转换数据结构需要浪费时间同时也要浪费空间。而为什么不直接一直用红黑树呢?这是因为树的结构太浪费空间,只有节点足够多的情况下用树才能体现出它的优势,而如果在节点数量不够多的情况下用链表的效率更高,占用的空间更小
当我们向HashMap中存放数据时,首先会根据key的hashCode方法计算出值,然后结合数组长度和算法(如取余法、位运算等)来计算出向数组存放数据的位置索引值。如果索引位置不存在数据的话则将数据放到索引位中;如果索引位置已经存在数据,这时就发生了hash碰撞(两个不同的原始值在经过哈希运算后得到相同的结果),为了解决hash碰撞,JDK1.8前用的是数组+链表的形式,而JDK1.8后用的是数组+链表+红黑树,这里以链表为例。由于该位置的hash值和新元素的hash值相同,这时候要比较两个元素的内容如果内容相同则进行覆盖操作,如果内容不同则继续找链表中的下一个元素进行比较,以此类推如果都没重复的则在链表中新开辟一个空间存放元素。
1.7中HashMap在构造方法中创建了一个长度是16的Entry[]table用来存储键值对数据
1.8中HashMap构造方法中不创建,put时才创建
2 的幂次原因,根据上述讲解我们已经知道,当向 HashMap 中添加一个元素的时候,需要根据 key 的 hash 值,去确定其在数组中的具体位置。HashMap 为了存取高效,减少碰撞,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现的关键就在把数据存到哪个链表中的算法。这个算法实际就是取模,hash % length,计算机中直接求余效率不如位移运算。所以源码中做了优化,使用 hash & (length - 1),而实际上 hash % length 等于 hash & ( length - 1) 的前提是 length 是 2 的 n 次幂。
(h = key.hashCode()) ^ (h >>> 16),为什么要获取完hashCode()赋值给h后要和h右移16位做异或运算呢,直接让h=key.hashCode()不行吗?实际上这是在进行put操作时尽可能的避免hash冲突,前面说过存放元素的时候寻找数组下标实际上是hash值对数组长度取余,当数组长度为2的n次幂时,(n - 1) & hash和hash%n是等价的而且运算效率比取余高,简单来说之所以这么做就是,高 16bit 不变,低 16bit 和高 16bit 做了一个异或(得到的 hashCode 转化为 32 位二进制,前 16 位和后 16 位低 16bit 和高 16bit 做了一个异或)
HashMap在进行扩容时,每次扩容后的大小都是原来数组的两倍,然后将原数组中的元素分配到新的数组中去
HashMap是非线程安全的,在涉及到多线程并发的情况,进行get操作有可能会引起死循环,导致CPU利用率接近100%。扩容引起线程安全问题
Hashtable和Collections.synchronizedMap(hashMap),不过这两个方案基本上是对读写进行加锁操作,一个线程在读写元素,其余线程必须等待,性能可想而知。
ConcurrentHashMap
1.7 ConcurrentHashMap采用 分段锁的机制,实现并发的更新操作,底层采用数组+链表的存储结构。
Segment继承ReentrantLock用来充当锁的角色
1.8 利用CAS+Synchronized来保证并发更新的安全,底层采用数组+链表+红黑树的存储结构。
put 数据初始化 table
initTable() {
Unsafe U U.compareAndSwapInt
synchronized (f) {
if (tabAt(tab, i) == f) {
扩容
构建一个nextTable,大小为table的两倍。
把table的数据复制到nextTable中。
U.compareAndSwapInt
红黑树
TreeBin
get操作
判断table是否为空,如果为空,直接返回null。
计算key的hash值,并获取指定table中指定位置的Node节点,通过遍历链表或则树结构找到对应的节点,返回value值。
Kotlin Flow
flow channel
冷/热 流
Flow的构造函数
flowOf
asFlow
中间运算函数
debounce 如果两个相邻的值生产出来的时间间隔超过了[timeout]毫秒,就忽过滤掉前一个值
distinctUntilChanged 如果生产的值和上个发送的值相同,值就会被过滤掉
transform 对每个值进行转换
onStart 第一个值被释放之前被执行
onCompletion 最后一个值释放完成之后被执行
drop 忽略最开始的[count]个值
dropWhile 判断第一个值如果满足(T) -> Boolean这个条件就忽略
take 只释放前面[count]个值
takeWhile 判断第一个值如果满足(T) -> Boolean这个条件就释放
flowOn 切换CoroutineContext flowOn只影响该运算符之前的CoroutineContext
buffer 将flow的多个任务分配到不同的协程中去执行,加快执行的速度。
conflate 如果值的生产速度大于值的消耗速度,就忽略掉中间未来得及处理的值,只处理最新的值。
flatMapConcat 将原始的Flow<T>通过[transform]转换成Flow<Flow<T>>,然后将Flow<Flow<T>>释放的Flow<T>其中释放的值一个个释放。
flattenConcat 和flatMapConcat类似,只是少了一步Map操作。
flatMapMerge 将原始的Flow<T>通过[transform]转换成Flow<Flow<T>>,然后将Flow<Flow<T>>释放的Flow<T>其中释放的值一个个释放。
merge 将Iterable<Flow<T>>合并成一个Flow<T>
transformLatest 原始flow会触发transformLatest转换后的flow, 当原始flow有新的值释放后,transformLatest转换后的flow会被取消,接着触发新的转换后的flow
transformLatest
flatMapLatest
mapLatest
filter 通过predicate进行过滤,满足条件则被释放
filterNot 通过predicate进行过滤,不满足条件则被释放
filterIsInstance 如果是某个数据类型则被释放
filterNotNull 如果数据是非空,则被释放
map 将一个值转换成另外一个值
mapNotNull 将一个非空值转换成另外一个值
StateFlow/MutableStateFlow
Flow是冷信号 StateFlow主要用来管理和表示几种不同的状态
StateFlow只有值变化后才会释放新的值
collect对于它不是必需的,StateFlow创建的时候就能开始释放值
view
View的绘制流程
测量、布局、绘制
Activty - window(PhoneWindow) - DecorView
onCreate - 设置布局 setContentView
mLayoutInflater.inflate xml 解析布局
ViewRootImpl的requestLayout
checkThread 检查是否为主线程(当前线程)
发送 handle 消息 执行 performTraversals
performMeasure --- 测量
performLayout --- 布局
performDraw --- 绘制
MeasureSpec 前 2 位 测量模式 后面 测量尺寸
测量模式
EXACTLY 精确模式
AT_MOST 最大模式
UNSPECIFIED 无任何限制模式
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
layout(childLeft, childTop, childLeft + width, childTop + height);
绘制背景
通过onDraw()绘制之身
通过dispatchDraw()绘制子View
绘制滚动条
measure和layout过程会执行多次
有些控件在测量的过程中确实存在多次测量的情况,比如RelativeLayout,LinearLayout中使用layout_weight。因为通过一次测量不能完全测出所有子View的宽高,所以需要多次测量。
onMeasure、onLayout、onDraw在自定义view中的作用
自定义View:如果控件直接继承自View,那么在布局中使用wrap_content的时候,控件会占据父容器的所有剩余空间,这种情况一般需要自己实现onMeasure方法。自定义View不需要重写onLayout,onDraw必须得自己实现。
自定义ViewGroup:如果控件直接继承自ViewGroup,则必须重写onMeasure方法,也需要重写onLayout方法。
继承自View或ViewGroup的子控件:在需要的时候去重写对应的方法。
之后会有一篇文章专门介绍自定义View,在此就不做过多介绍。
获取 view宽高的方法
1、view.post
2、ViewTreeObserver.addOnGlobalLayoutListener
3、重写 onMeasure()
view.post 为什么能拿到宽高
view.post将一个 Runnable 对象添加到视图的消息队列中,这个消息会在视图的下一个绘制循环中被执行。这意味着它会在视图已经完成测量和布局之后被调用。
当视图完成测量和布局后,其宽高属性就有了确定的值,此时在 Runnable 中获取视图的宽高就能得到正确的结果。
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
当 view 还没显示前,attachInfo为空,把 runnable 放到队列,下一次再执行 handler 消息机制
Android 的invalidate 与postInvalidate的区别
invalidate
用于进行View的刷新,在UI线程调用
postinvalidate
用于进行View的刷新,在非UI线程调用,将非UI线程切换到UI线程,最后也是调用invalidate。
线程池
execute(Runnable)执行逻辑
1、调用execute方法 提交一个Runnable
2、execute 调用 addWorker 方法创建 worker 对象 true 创建核心线程,false 创建非核心线程
3、addWorker 调用worker.thread.start 调用 worker内部的 run方法
4、worker run方法 委托给runWorker 方法
5、while 循环体 getTask 取新任务,队列的 take 方法,如果队列为空会阻塞
核心线程如果不设置属性 allowCoreThreadTimeOut 为 true,那么创建后永远不会被关闭中断,会在 getTask() 方法中的 workQueue.take() 处 阻塞等待任务;
如果核心线程设置属性 allowCoreThreadTimeOut 为 true或者是非核心线程,那么就会调用 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) 方法,指定时间内获取不到任务,就会跳出 runWorker(Worker w) 方法中的while循环而关闭线程。
ExecutorService threadsCache = Executors.newCachedThreadPool()
核心线程数 0
最大线程数 Integer.MAX_VALUE
超时 60 秒
队列 SynchronousQueue
ExecutorService threadsFixed = Executors.newFixedThreadPool(5)
核心线程数 5
最大线程数 5
超时 0 秒
队列 LinkedBlockingQueue
ExecutorService threadsSingle = Executors.newSingleThreadExecutor()
核心线程数 1
最大线程数 1
超时 0 秒
队列 LinkedBlockingQueue
ExecutorService threadsScheduled = Executors.newScheduledThreadPool(5)
核心线程数 5
最大线程数 Integer.MAX_VALUE
超时 10 毫秒
队列 DelayedWorkQueue 定时队列
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue)
corePoolSize:核心线程数。
maximumPoolSize:最大线程数。
keepAliveTime:当线程数大于核心线程数时,多余的空闲线程在终止前等待新任务的最长时间。
unit:keepAliveTime 的时间单位。
workQueue:用于保存等待执行的任务的阻塞队列。
线程池的优势
降低资源消耗:通过复用已有的线程,减少了线程创建和销毁的开销。
提高响应速度:任务可以不需要等待线程创建就能立即执行。
便于线程管理:可以方便地管理线程的数量、生命周期等。
线程池执行流程
1、任务提交:
当向线程池提交一个任务线程池首先判断当前运行的线程数量是否小于核心线程数(corePoolSize)。
如果小于核心线程数,线程池会创建一个新的线程来执行任务,即使其他核心线程处于空闲状态。
如果当前运行的线程数量等于或大于核心线程数,新提交的任务会被放入任务队列(workQueue)等待执行。
核心线程会从任务队列中获取任务并执行。
任务队列满时:
如果任务队列已满,并且当前运行的线程数量小于最大线程数(maximumPoolSize),线程池会创建新的非核心线程来执行任务。
非核心线程执行任务:
非核心线程从任务队列中获取任务并执行。
当任务队列中的任务被处理完后,如果一段时间内(由 keepAliveTime 指定)非核心线程处于空闲状态,这些非核心线程会被回收。
拒绝策略:
如果任务队列已满,并且当前运行的线程数量等于最大线程数,此时再提交新任务,线程池会根据拒绝策略来处理这个任务。
常见的拒绝策略有:AbortPolicy(抛出 RejectedExecutionException 异常)、CallerRunsPolicy(在调用者线程中执行任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最旧的任务,然后重新尝试提交新任务)。
可通过调用线程池的shutdown或shutdownNow方法来关闭线程池.
因为单线程池和固定线程池中,线程数量是有限的,因此提交的任务需要在LinkedBlockingQueue队列中等待空余的线程;而缓存线程池中,线程数量几乎无限(上限为Integer.MAX_VALUE),因此提交的任务只需要在SynchronousQueue队列中同步移交给空余线程即可。
可以,在线程池中一个线程报错,或者oom线程池会将其进行回收,避免造成其他线程出错。
如果程序能正常处理这个异常情况,比如不再申请更多的内存或其它资源,或者放弃那个子任务或子线程,系统OOM状态是可以回到正常情况。
包体积优化
下载转化率:安装包越小,转化率越高;
推广成本:渠道推广成本和厂商预装的单价
应用市场:App Store和Google Play对安装包大小都有限制;
Proguard 混淆 minifyEnabled true
瘦身:它可以检测并移除未使用到的类、方法、字段以及指令、冗余代码,并能够对字节码进行深度优化。最后,它还会将类中的字段、方法、类的名称改成简短无意义的名字。
安全:增加代码被反编译的难度,一定程度上保证代码的安全。
压缩(Shrinking): 默认开启,以减小应用体积,移除未被使用的类和成员
优化(Optimization): 默认开启,在 字节码级别执行优化,让应用 运行的更快
混淆(Obfuscation): 默认开启,增大反编译难度,类和类成员会被随机命名
-keepxxx
-donotxxx
-ignorexxx
-optimizationpasses 5
Dex 分包
为了进一步减少 Dex 的数量,我们希望每个 Dex 的方法数都是满的,即分配了 65536 个方法。
通过Redex实现
Dex 压缩
Native Library so 库 abi删减,armeabi-v7a arm64-v8a
资源优化
webp
gif压缩
aab 方案
JNI
1、创建 native 方法
public static native String getStringFromNDK()
2、进入class 目录 执行 javah -jni 生成 .h 头文件
3、创建 cpp 文件,导入头文件,实现对应的方法
JNIEXPORT jstring JNICALL Java_gebilaolitou_ndkdemo_NDKTools_getStringFromNDK
(JNIEnv *env, jobject obj)
4、导入 so 库
static {
System.loadLibrary("ndkdemotest-jni");
}
5、CMake配置
gralde 配置
externalNativeBuild {
cmake {
cppFlags ""
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
add_library( 创建动态库
find_library( 发现三方库
target_link_libraries 自己的 so和三方库关联
ARouter
支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
支持多模块工程使用
支持添加多个拦截器,自定义拦截顺序
支持依赖注入,可单独作为依赖注入框架使用
支持InstantRun
支持MultiDex(Google方案)
映射关系按组分类、多级管理,按需初始化
支持用户指定全局降级与局部降级策略
页面、拦截器、服务等组件均自动注册到框架
支持多种方式配置转场动画
支持获取Fragment
完全支持Kotlin以及混编(配置见文末 其他#5)
支持第三方 App 加固(使用 arouter-register 实现自动注册)
支持生成路由文档
提供 IDE 插件便捷的关联路径和目标类
@Route(path = RoutePath.USER_HOME)
ARouter.getInstance().build(RoutePath.USER_HOME).navigation()
ARouter 的基本实现思路就是:
开发者自己维护特定 path 和特定的目标类之间的对应关系,ARouter 只要求开发者使用包含了 path 的 @Route 注解修饰目标类
ARouter 在编译阶段通过注解处理器来自动生成 path 和特定的目标类之间的对应关系,即将 path 作为 key,将目标类的 Class 对象作为 value 之一存到 Map 之中
在运行阶段,应用通过 path 来发起请求,ARouter 根据 path 从 Map 中取值,从而拿到目标类
初始化
ARouter.init(this)
LogisticsCenter.init(mContext, executor)
loadRouterMap()
loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("account", ARouter$$Group$$account.class);
loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/account/userHome", RouteMeta.build(RouteType.ACTIVITY, UserHomeActivity.class, "/account/userhome", "account", null, -1, -2147483648));
}
就会再来反射调用 ARouter$$Group$$account 的 loadInto 方法,即按需加载,等到需要的时候再来获取详细的路由对应信息
跳转到 Activity
ARouter.getInstance().build(RoutePath.USER_HOME).navigation()
build 返回 Postcard
Postcard 对象可以用于传入一些跳转配置参数,例如:携带参数 mBundle、开启绿色通道 greenChannel 、跳转动画 optionsCompat 等
_ARouter navigation LogisticsCenter.completion(postcard) 执行拦截器 _navigation 通过 intent 跳转
LogisticsCenter.completion 获取详细的路由信息, RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath())
ARouter.getInstance().build(RoutePath.USER_HOME)
.withLong(RoutePath.USER_HOME_PARAMETER_ID, 20)
.withString(RoutePath.USER_HOME_PARAMETER_NAME, "leavesC")
.navigation()
ARouter.getInstance().inject(this)
控制反转
ARouter 同时也支持控制反转:通过接口来获取其实现类实例
nterface ISayHelloService : IProvider {
@Route(path = RoutePath.SERVICE_SAY_HELLO)
ARouter.getInstance().navigation(ISayHelloService::class.java).sayHello()
拦截器
@Interceptor(priority = 200, name = "登陆拦截器")
class LoginInterceptor : IInterceptor {
注解处理器
APT(Annotation Processing Tool) 即注解处理器,是一种注解处理工具,用来在编译期扫描和处理注解,通过注解来生成 Java 文件。即以注解作为桥梁,通过预先规定好的代码生成规则来自动生成 Java 文件
arouter-annotation arouter-compiler
ARouter 是通过 JavaPoet 这个开源库来生成代码的。
LRU 缓存机制
实现 LinkedHashMap accessOrder = true accessOrder 决定内部的排序顺序
当序列达到设置的内存上限时, 丢弃序列中最近最少使用的元素.
LruCache android 自带类
ZXing 扫码优化
YUV是什么? 是一种颜色编码方法。常使用在各个视频处理组件中。
camera2 帧回调
mYuvReader = ImageReader.newInstance(prelargest.getWidth(), prelargest.getHeight(),
ImageFormat.YUV_420_888, /* maxImages */ 2)
mYuvReader.setOnImageAvailableListener(mOnYuvAvailableListener, null)
灰度优化
System.arraycopy(yuvData, inputOffset, matrix, 0, area)
只读取 yuv 中的 y 量,灰度图
byte[] cropNv21 = new byte[nv21.length / 2 * 3]
for (int i = 0
cropNv21[i] = nv21[i]
}
for (int i = 0
cropNv21[nv21.length + i] = (byte) 0b10000000
}
System.arraycopy(cropNv21, 0, cropNv21, 0, w * h)
YuvImage yuvImage = new YuvImage(cropNv21, ImageFormat.NV21, w, h, null)
try {
yuvImage.compressToJpeg(new Rect(0, 0, w, h), 100, new FileOutputStream(file))
} catch (FileNotFoundException e) {
e.printStackTrace()
}
接入python
id 'com.chaquo.python'
ndk {
abiFilters "arm64-v8a"
}
python {
buildPython "/usr/local/Cellar/python@3.9/3.9.19_1/bin/python3.9"
pip {
options "--index-url", "https://pypi.tuna.tsinghua.edu.cn/simple/"
options "--extra-index-url", "http://pypi.mirrors.ustc.edu.cn/simple/"
install "PyDocX"
install "doc-x-to-html"
}
}
implementation
implementation files
SurfaceView与TextureView
SurfaceView是一个有自己Surface的View,其缺点是不能做变形和动画
TextureView是一个可以把内容流作为外部纹理输出在上面的View。它本身需要是一个硬件加速层
TextureView是在View hierachy中做绘制,因此一般它是在主线程上做的(在Android 5.0引入渲染线程后,它是在渲染线程中做的)。而SurfaceView+SurfaceTexture在单独的Surface上做绘制,可以是用户提供的线程,而不是系统的主线程或是渲染线程。
SurfaceView的核心在于提供了两个线程:UI线程和渲染线程,两个线程通过“双缓冲”机制来达到高效的界面适时更新。
Activity
启动模式
任务栈Task,是一种用来放置Activity实例的容器,他是以栈的形式进行盛放,也就是所谓的先进后出,主要有2个基本操作:压栈和出栈,其所存放的Activity是不支持重新排序的,只能根据压栈和出栈操作更改Activity的顺序。
Stack<Activity> activitys = new Stack()
设置Intent的Flag
FLAG_ACTIVITY_NEW_TASK
使用一个新的Task来启动一个Activity
FLAG_ACTIVITY_SINGLE_TOP
使用singletop模式启动一个Activity singleTop”效果相同。
FLAG_ACTIVITY_CLEAR_TOP
使用SingleTask模式来启动一个Activity singleTask”效果相
FLAG_ACTIVITY_NO_HISTORY
Activity使用这种模式启动Activity,当该Activity启动其他Activity后,该Activity就消失了,不会保留在Activity栈中。
生命周期
进入Activity A :
A :onCreate->
A :onStart->
A :onResume
ActivityA 跳转到Activity B :
A: onPause
B: onCreate
B: onStart
B:onResume
A: onStop
Activity B 返回 Activity A
B:onPause
A:onRestart
A:onStart
A: onResume
B:onStop
B:onDestory
ContentProvider
进程间 进行数据交互 & 共享,即跨进程通信
ContentProvider的底层原理 = Android中的Binder机制
ContentProvider主要以 表格的形式 组织数据
Uri insert
int delete
int update
Cursor query
ContentResolver类
统一管理不同 ContentProvider间的操作
ContentResolver 类提供了与ContentProvider类相同名字 & 作用的4个方法
ContentObserver类
内容观察者
观察 Uri引起 ContentProvider 中的数据变化 & 通知外界(即访问该数据访问者)
getContentResolver().registerContentObserver(uri);
getContext().getContentResolver().notifyChange(uri, null);
OkHttp
OkHttp有一个库的依赖Okio
implementation ('com.squareup.okhttp3:okhttp:4.10.0')
implementation ('com.squareup.okhttp3:logging-interceptor:4.10.0')
client = new OkHttpClient.Builder()
.cache(new Cache(new File(Utils.cacheRoot), 10*1024*1024))
.callTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.connectionPool(kdbPool)
.retryOnConnectionFailure(true)
.addNetworkInterceptor(new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(@NonNull String s) {
Utils.getDefault().showLog("接口请求=="+s);
}
}).setLevel(HttpLoggingInterceptor.Level.BODY))
.build();
HttpUrl.Builder urlRequest = HttpUrl.get(rootUrl+apiName)
.newBuilder();
if(null!=params && !params.isEmpty()){
for(Map.Entry<String, String> entry: params.entrySet()){
urlRequest.addQueryParameter(entry.getKey(), entry.getValue());
}
}
Request request = new Request.Builder()
.url(urlRequest.build())
.build();
getClient().newCall(request).enqueue
RequestBody requestJsonBody = RequestBody.create(
params.toString(),
MediaType.parse("application/json")
);
Request request = new Request.Builder()
.url(rootUrl + apiName)
.post(requestJsonBody)
.build();
getClient().newCall(request).enqueue(
//上传文件
MultipartBody.Builder builder = new MultipartBody.Builder();
for(Map.Entry<File, MediaType> entry : fileMap.entrySet()){
builder.addFormDataPart("reqFiles", entry.getKey().getName(),
RequestBody.create(entry.getKey(), entry.getValue()));
}
RequestBody requestBody = builder
.setType(MultipartBody.FORM)
.build();
Request request = new Request.Builder()
.url(rootUrl + apiName)
.post(requestBody)
.build();
getClient().newCall(request).enqueue(
//文件下载
Request request = new Request.Builder().get().url(fileUrl).build();
getClient().newCall(request).enqueue(
//文件创建
ResponseBody responseBody = response.body();
Sink sink = null;
BufferedSink bufferedSink = null;
try{
sink = Okio.sink(file);
bufferedSink = Okio.buffer(sink);
bufferedSink.writeAll(responseBody.source());
if(null!=fileDownloadCallback) fileDownloadCallback.downloadSuccess(file.getAbsolutePath());
}catch (Exception e){
e.printStackTrace();
}finally {
try {
if (bufferedSink != null){
bufferedSink.close();
bufferedSink = null;
}
if (sink != null){
sink.close();
sink = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
创建 OkHttpClient 采用 build模式设置相关参数
dns = Dns.SYSTEM 默认的系统 dns
newCall(Request request) {
return new RealCall(this, request);
生成一个 RealCall
RealCall:真正的请求执行者
new RealCall(
1、设置 okhttp 客户端和请求体
2、创建责任链模式拦截器中的第一个拦截器 RetryAndFollowUpInterceptor
execute()
1、call是否已经被执行了,每个 call 只能被执行一次
2、client.dispatcher().executed(this) 来进行实际执行
3、getResponseWithInterceptorChain()
getResponseWithInterceptorChain 责任链模式
自定义的拦截器
RetryAndFollowUpInterceptor 重试以及重定向
BridgeInterceptor 桥接 把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应
CacheInterceptor 负责读取缓存直接返回、更新缓存
ConnectInterceptor 负责和服务器建立连接的。创建了一个HttpCodec
networkInterceptors 配置 OkHttpClient 时设置的 networkInterceptors
CallServerInterceptor 负责向服务器发送请求数据、从服务器读取响应数据
RealInterceptorChain proceed
实例化下一个拦截器对应的RealIterceptorChain对象,这个对象会在传递给当前的拦截器
得到当前的拦截器:interceptors是存放拦截器的ArryList
调用当前拦截器的intercept()方法,并将下一个拦截器的RealIterceptorChain对象传递下去
**除了在client中自己设置的interceptor,第一个调用的就是retryAndFollowUpInterceptor **
缓存,缓存拦截器
首先,根据request来判断cache中是否有缓存的response,如果有,得到这个response,然后进行判断当前response是否有效,没有将cacheCandate赋值为空。
根据request判断缓存的策略,是否要使用了网络,缓存 或两者都使用
调用下一个拦截器,决定从网络上来得到response
如果本地已经存在cacheResponse,那么让它和网络得到的networkResponse做比较,决定是否来更新缓存的cacheResponse
缓存未经缓存过的response
Retrofit2
网络请求的工作本质是okhttp完成,而Retrofit仅负责网络请求接口的封装。
Retrofit提供了注解可以表示该接口请求的请求方式、参数、url等。
interface RetrofitApi {
@GET("weather_mini")
Call<WeatherInfo> getWeatherInfo(@Query("city") String city);
@GET("HPImageArchive.aspx")
Observable<ImageBean> getImage(@Query("format") String format, @Query("idx") int idx, @Query("n") int n);
}
new Retrofit.Builder()
.baseUrl("https://cn.bing.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
retrofit.create(RetrofitApi.class)
Call<WeatherInfo> call = request.getWeatherInfo("北京");
call.enqueue(
Retrofit创建
build模式
Retrofit.create(xx.class) 来创建的
Proxy.newProxyInstance 动态代理
在动态代理回调里面,创建相关请求实例
ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
ServiceMethod loadServiceMethod(Method method)
尝试程缓存中获取,拿不到再重新创建,、通过反射
初始化 callAdapter
初始化 responseType
初始化 responseConverter
初始化 parameterHandlers
初始化 OkHttpCall 对 okhttp 方法的封装
返回 serviceMethod.callAdapter.adapt(okHttpCall)
a.使用Retrofit流程如下:
1、定义请求接口;
2、通过建造者模式创建一个Retrofit实例,对并Retrofit进行相关参数配置;
3、通过Retrofit对象的creat()方法创建一个代理对象;
4、当调用接口方法时,都会调用动态代理的invoke()方法;invoke()内部做了3件事情:
a. 对method进行解析,生成ServiceMethod对象并缓存起来,下次调用就不需要解析了;
b. 将原始的okhttp3.call[okhttp3]封装成OkHttpCall[retrofit2];
c. 通过CallAdapter转换成Call对象或者Observable对象;
5、如果返回Call对象,调用execute或者enqueue方法去做请求,最终在onResponse()里面进行请求结果逻辑处理;如果返回Observable对象,则通过Rxjava2最终在onNext()里面进行请求结果逻辑处理。
b.Retrofit封装的点:
1.Build模式创建网络请求基本配置;
2.用注解来排列组合成网络请求,以不变应万变;
3.统一提供Gson解析,提供了可复用、易扩展的数据解析方案;
4.自定义Executor(Handler)完成线程的切换;
c.Retrofit用到的设计模式
1.Retrofit实例使用建造者模式通过Builder类构建,当构造函数的参数大于4个,且存在可选参数的时候就可以使用建造者设计模式;
2.Retrofit创建的CallFactory,使用工厂方法设计模式,此处只支持OkHttp;
3.整个Retrofit采用的是外观模式,统一的调用创建网络请求接口实例和网络请求参数配置的方法;
4.Retrofit里面使用了动态代理模式来创建网络请求接口实例,这个是retrofit对用户使用来说最大的复用,其他的代码都是为了支撑这个动态代理给用户带来便捷性的;
5.使用了策略模式对serviceMethod对象进行网络请求参数配置,即通过解析网络请求接口方法的参数、返回值和注解类型,从Retrofit对象中获取对应的网络的url地址,网络请求执行器、网络请求适配器和数据转换器;
6.ExecutorCallbackCall使用装饰者模式来封装callbackExecutor,用于完成线程的切换;
7.ExecutorCallbackCall使用静态代理模式代理了call进行网络请求,真正的网络请求是由okhttpcall执行,然而okhttpcall不是自己执行,它是okhttp提供call给外界(retrofit)使用的唯一门户,此处是门面模式;
8.ExecutorCallbackCall的初始化是在ExecutorCallAdapterFactory里面通过适配器模式创建的,CallAdapter采用了适配器模式为创建访问call接口提供服务,默认ExecutorCallAdapterFactory将okhttp3.call转换为Retrofit中的Call,如果使用Rxjava,则将okhttp3.call转换为Observable;
9.当使用了Rxjava时,app层注册observer,即观察者模式。
service
startService
bindService
区别
Intent service = new Intent(this, MyService.class);
startService(service);
stopService(service);
stopSelf()
开启服务时,调用一次startService,生命周期执行的方法依次是:
onCreate() ==> onStartCommand();
调用多次startService,onCreate只有第一次会被执行,而onStartCommand会执行多次。
结束服务时,调用stopService,生命周期执行onDestroy方法,并且多次调用stopService时,onDestroy只有第一次会被执行。
bindService开启服务就多了一些内容。
Intent service = new Intent(this, MyService.class);
MyConnection conn = new MyConnection();
bindService(service, conn, BIND_AUTO_CREATE);
unbindService(conn);
bingService开启服务时,根据生命周期里onBind方法的返回值是否为空,有两种情况。
1、onBind返回值是null;
调用bindService开启服务,生命周期执行的方法依次是:
onCreate() ==> onBind();
调用多次bindService,onCreate和onBind也只在第一次会被执行。
调用unbindService结束服务,生命周期执行onDestroy方法,并且unbindService方法只能调用一次,多次调用应用会抛出异常。使用时也要注意调用unbindService一定要确保服务已经开启,否则应用会抛出异常。
2、onBind返回值不为null;
看一下android对于onBind方法的返回类型IBinder的介绍,字面上理解是IBinder是android提供的进程间和跨进程调用机制的接口。而且返回的对象不要直接实现这个接口,应该继承
Broadcast 广播
实现只有特定的组件(比如组件 A)能够接收某个广播
静态注册广播接收器时指定权限
1、在 AndroidManifest.xml 中注册广播接收器时,可以为其指定一个特定的权限。
<permission android:name="com.example.mypermission" android:protectionLevel="normal" />
然后在要接收特定广播的组件 A 的广播接收器声明中添加权限要求:
<uses-permission android:name="com.example.mypermission" />
发送广播时,也需要添加相同的权限:
Intent intent = new Intent("com.example.myaction");
sendBroadcast(intent, "com.example.mypermission");
2、动态注册广播接收器时进行判断
在组件 A 中动态注册广播接收器时,可以在onReceive方法中进行判断,确保只有组件 A 能够处理该广播。
onReceive(Context context, Intent intent) {
if (context instanceof ComponentAContext) {
}
3、使用本地广播管理器(LocalBroadcastManager)
使用LocalBroadcastManager发送和接收广播时,广播只会在当前应用内传播,其他应用无法接收到。然后可以在组件 A 中注册和接收特定的广播。
LocalBroadcastManager不能跨进程。
LocalBroadcastManager管理的广播只在当前应用程序的进程内有效,它是专门为了在单一应用内安全、高效地发送和接收广播而设计的。
如果需要跨进程通信,可以考虑使用其他方式,如ContentProvider、AIDL(Android Interface Definition Language)或者Messenger等。
标准广播(Normal broadcasts)是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接受到这条广播消息,因此它们之间没有任何先后顺序可言,这种广播的效率会比较高,但同时也意味着它是无法被拦截的。
有序广播(Ordered broadcasts) 则是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。所以此时的广播接受器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播消息了。
intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
netWorkChangeReceiver = new NetWorkChangeReceiver();
registerReceiver(netWorkChangeReceiver,intentFilter);
NetWorkChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
unregisterReceiver(netWorkChangeReceiver);
<receiver
android:name=".service.MyReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent intent = new Intent("com.function.luo.MyBroadcastReceiver");
sendOrderedBroadcast(intent,null);
<receiver
android:name=".service.MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter
android:priority="100"
>
<action android:name="com.function.luo.MyBroadcastReceiver" />
</intent-filter>
</receiver>
MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"发送标准广播",Toast.LENGTH_LONG).show();
abortBroadcast();
}
在onReceive()方法中调用了 abortBroadcast()方法,就表示这条广播截断,后面的广播接收器无法再接受到这条广播。
使用本地广播
localBroadcastManager = LocalBroadcastManager.getInstance(this);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("om.function.luo.LOCAL_BROADCAST");
LocalReceiver localReceiver = new LocalReceiver();
localBroadcastManager.registerReceiver(localReceiver, intentFilter);
Intent intent = new Intent("om.function.luo.LOCAL_BROADCAST");
localBroadcastManager.sendBroadcast(intent);
private class LocalReceiver extends BroadcastReceiver {
@Override
public void onReceive(
本地广播是无法通过静态注册的方式来接收的
本地广播的优势
可以明确知道正在发送的广播不会离开我们的程序,因此不必担心机密数据泄露。
其它的程序无法将广播发送到我们程序内部,因此不需要担心会有安全漏洞问题。
发送本地广播比发送系统全局广播更加高效。