Android底层知识Window和WindowManager深入分析

问题:

What is WindowManager in android?

The developer website’s definition is not clear either:

The interface that apps use to talk to the window manager. Use Context.getSystemService(Context.WINDOW_SERVICE) to get one of these.

Can someone with plain 6th grade English explain what it is?

摘自:What is WindowManager in android?

前言:

Window表示一个窗口的概念,正常开发中直接接触Window的机会并不是很多,但是如果我们要在桌面上显示一个类似悬浮窗(微信视频通话最小化的时候)就需要用到。Window是一个抽象类。它的具体实现是PhoneWindow

1
2
3
4
5
6
7
8
9
10
11
12
//window源码
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window {
1
2
3
4
5
6
7
8
9
10
//PhoneWindow源码
/**
* Android-specific Window.
* <p>
* todo: need to pull the generic functionality out into a base class
* in android.widget.
*
* @hide
*/
public class PhoneWindow extends Window implements MenuBuilder.Callback {

创建一个Window很简单,只需要通过WindowManager就可以完成,Window的具体实现是通过WindowManagerService完成,他们之间的交互是一个IPC过程。Andriod中所有视图都是通过Window来实现的,不管是Activity、Toast、Dialog,他们的视图都是附加在Window上的,因此Window实际上是View的直接管理者。

补充知识点:WindowManagerService

WindowManagerService 就是位于 Framework 层的窗口管理服务,它的职责就是管理系统中的所有窗口。窗口的本质是什么呢?其实就是一块显示区域,在 Android 中就是绘制的画布:Surface,当一块 Surface 显示在屏幕上时,就是用户所看到的窗口了。WindowManagerService 添加一个窗口的过程,其实就是 WindowManagerService 为其分配一块 Surface 的过程,一块块的 Surface 在 WindowManagerService 的管理下有序的排列在屏幕上,Android 才得以呈现出多姿多彩的界面。于是根据对 Surface 的操作类型可以将 Android 的显示系统分为三个层次,如下图:


一般的开发过程中,我们操作的是 UI 框架层,对 Window 的操作通过 WindowManager 即可完成,而 WindowManagerService 作为系统级服务运行在一个单独的进程,所以 WindowManager 和 WindowManagerService 的交互是一个 IPC 过程。

Window和WindowManager

通过WindowManager添加View的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
val btn_bug = Button(activity.getApplicationContext())
//btn_bug.setBackgroundColor(Color.RED);
btn_bug.setBackgroundResource(R.drawable.environment_setting)
val wm = activity.getApplicationContext().getSystemService(Context.WINDOW_SERVICE) as WindowManager
val wmParams = WindowManager.LayoutParams()
wmParams.type = android.view.WindowManager.LayoutParams.TYPE_PHONE //2002;
wmParams.format = PixelFormat.RGBA_8888
wmParams.flags = android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
wmParams.width = 100
wmParams.height = 100
//wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE;
wmParams.gravity = Gravity.LEFT or Gravity.TOP
wmParams.x = UiUtil.dip2px(activity, 50f) //不要挡住返回键
wmParams.y = 0
wm.addView(btn_bug, wmParams)
var x: Float = 0.toFloat()
var y: Float = 0.toFloat()
var org_x: Float = 0.toFloat()
var org_y: Float = 0.toFloat()
btn_bug.setOnTouchListener({ v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN // 捕获手指触摸按下动作
-> {
// 获取相对View的坐标,即以此View左上角为原点
x = event.x
y = event.y
org_x = event.rawX
org_y = event.rawY
}
MotionEvent.ACTION_MOVE // 捕获手指触摸移动动作
-> {
wmParams.x = (event.rawX - x).toInt()
wmParams.y = (event.rawY - getStatusBarHeight(activity).toFloat() - y).toInt()
wm.updateViewLayout(v, wmParams)
}
MotionEvent.ACTION_UP // 捕获手指触摸离开动作
-> {
val threshold = ViewConfiguration.get(activity).scaledTouchSlop
if (Math.abs(event.rawX - org_x) < threshold && Math.abs(event.rawY - org_y) < threshold) {
val intent = Intent("yh.action.EnvironmentSettingActivity")
activity.startActivity(intent)
}
}
}
return@setOnTouchListener true
})
flags参数解析:
  • FLAG_NOT_FOCUSABLE:表示window不需要获取焦点,也不需要接收各种输入事件。此标记会同时启用FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的window;

  • FLAG_NOT_TOUCH_MODAL:在此模式下,系统会将window区域外的单击事件传递给底层的window,当前window区域内的单击事件则自己处理,一般都需要开启这个标记;

  • FLAG_SHOW_WHEN_LOCKED:开启此模式可以让Window显示在锁屏的界面上。 [奇怪的是我删除这个标记还是在锁屏看到了添加的组件orz]

type参数表示window的类型,window共有三种类型:应用window,子window和系统window。
  • 应用window对应着一个Activity。
  • 子window不能独立存在,需要附属在特定的父window之上,比如Dialog就是子window。
  • 系统window是需要声明权限才能创建的window,比如Toast和系统状态栏这些都是系统window,需要声明的权限是

Window的分层

window是分层的,每个window都对应着z-ordered,层级大的会覆盖在层级小的上面,应用window的层级范围是1~99,子window的层级范围是1000~1999,系统window的层级范围是2000~2999。

ViewManager源码

常用的只有三个方法:addView、updateView和removeView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/** Interface to let you add and remove child views to an Activity. To get an instance
* of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
*/
public interface ViewManager
{
/**
* Assign the passed LayoutParams to the passed View and add the view to the window.
* <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
* errors, such as adding a second view to a window without removing the first view.
* <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
* secondary {@link Display} and the specified display can't be found
* (see {@link android.app.Presentation}).
* @param view The view to be added to this window.
* @param params The LayoutParams to assign to view.
*/
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}

Window的内部机制

  • Window是一个抽象的概念,不是实际存在的,它也是以View的形式存在。在实际使用中无法直接访问Window,只能通过WindowManager才能访问Window。每个Window都对应着一个View和一个ViewRootImplWindow和View通过ViewRootImpl来建立联系
  • Window的添加、删除和更新过程都是IPC过程,以Window的添加为例,WindowManager的实现类对于addView、updateView和removeView方法都是委托给WindowManagerGlobal类,该类保存了很多数据列表,例如所有window对应的view集合mViews、所有window对应的ViewRootImpl的集合mRoots等,之后添加操作交给了ViewRootImpl来处理,接着会通过WindowSession来完成Window的添加过程,这个过程是一个IPC调用,因为最终是通过WindowManagerService来完成window的添加的。

以添加过程为例,其他两种情况类似:

1
2
3
4
5
6
7
//WindowManager 是一个接口,它的真正实现是 WindowManagerImpl 类
//indowManagerImpl 并没有直接实现 Window 的三大操作,而是交给了 WindowManagerGlobal 来处理
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

下面以 addView 为例,分析一下
WindowManagerGlobal 中的实现过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//检查参数合法性,如果是子 Window 做适当调整
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
..
..//省略部分源码
..//addView 操作时会将相关对象添加到对应集合中:
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);

这里补充一下

1
2
3
4
5
6
//在 WindowManagerGlobal 内部有如下几个集合比较重要:
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();

其中 mViews 存储的是所有 Window 所对应的 View,mRoots 存储的是所有 Window 所对应的 ViewRootImpl,mParams 存储的是所有 Window 所对应的布局参数,mDyingViews 存储了那些正在被删除的 View 对象,或者说是那些已经调用了 removeView 方法但是操作删除还未完成的 Window 对象,可以通过表格直观的表示:

集合 存储内容
mViews Window 所对应的 View
mRoots Window 所对应的 ViewRootImpl
mParams Window 所对应的布局参数
mDyingViews 正在被删除的 View 对象
通过 ViewRootImpl 来更新界面并完成 Window 的添加过程

在了解View 的工作原理时,我们知道 View 的绘制过程是由 ViewRootImpl 来完成的,这里当然也不例外,具体是通过 ViewRootImpl 的 setView 方法来实现的。在 setView 内部会通过 requestLayout 来完成异步刷新请求,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
mForceDecorViewVisibility = (mWindowAttributes.privateFlags
& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);

可以看到 scheduleTraversals 方法是 View 绘制的入口,继续查看它的实现

1
2
3
4
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);

mWindowSession 的类型是 IWindowSession,它是一个 Binder 对象,真正的实现类是 Session,这也就是之前提到的 IPC 调用的位置。在 Session 内部会通过 WindowManagerService 来实现 Window 的添加,代码如下:

1
2
3
4
5
//终于,Window 的添加请求移交给 WindowManagerService 手上了,在 WindowManagerService 内部会为每一个应用保留一个单独的 Session
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams, attrs, int viewVisibility,
int displayId, Rect outContentInsets, InputChannel outInputChannel){
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outInputChannel);
}

总结:

  • Window用于显示View和接收各种事件,Window有三种类型:应用Window(每个Activity对应一个Window)、子Window(不能单独存在,附属于特定Window)、系统window(Toast和状态栏)
  • Window分层级,应用Window在1-99、子Window在1000-1999、系统Window在2000-2999.WindowManager提供了增删改View三个功能。
  • Window是个抽象概念:每一个Window对应着一个View和ViewRootImpl,Window通过ViewRootImpl来和View建立联系,View是Window存在的实体,只能通过WindowManager来访问Window。
  • WindowManager的实现是WindowManagerImpl其再委托给WindowManagerGlobal来对Window进行操作,其中有四个List分别储存对应的View、ViewRootImpl、WindowManger.LayoutParams和正在被删除的View
  • Window的实体是存在于远端的WindowMangerService中,所以增删改Window在本端是修改上面的几个List然后通过ViewRootImpl重绘View,通过WindowSession(每个应用一个)在远端修改Window。
  • Activity创建Window:Activity会在attach()中创建Window并设置其回调(onAttachedToWindow()、dispatchTouchEvent()),Activity的Window是由Policy类创建PhoneWindow实现的。然后通过Activity#setContentView()调用PhoneWindow的setContentView。

参考:

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg

当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

接口和抽象类的理解,Java8做了啥修改?

问题:

谈谈接口和抽象类有什么全部?

知识点补充

  1. Java 8 新增了函数式编程的支持,所以又增加了一个类定义,即所谓的functional interface,简单来说只有一个方法的接口,用FunctionnalInterface annotation来标记。如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}

Java8以后,接口中方法也是可以实现的。如Collections中代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* Returns a sequential {@code Stream} with this collection as its source.
*
* <p>This method should be overridden when the {@link #spliterator()}
* method cannot return a spliterator that is {@code IMMUTABLE},
* {@code CONCURRENT}, or <em>late-binding</em>. (See {@link #spliterator()}
* for details.)
*
* @implSpec
* The default implementation creates a sequential {@code Stream} from the
* collection's {@code Spliterator}.
*
* @return a sequential {@code Stream} over the elements in this collection
* @since 1.8
*/
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
/**
* Returns a possibly parallel {@code Stream} with this collection as its
* source. It is allowable for this method to return a sequential stream.
*
* <p>This method should be overridden when the {@link #spliterator()}
* method cannot return a spliterator that is {@code IMMUTABLE},
* {@code CONCURRENT}, or <em>late-binding</em>. (See {@link #spliterator()}
* for details.)
*
* @implSpec
* The default implementation creates a parallel {@code Stream} from the
* collection's {@code Spliterator}.
*
* @return a possibly parallel {@code Stream} over the elements in this
* collection
* @since 1.8
*/
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
  1. 面向对象通用设计原则(S.0.L.I.D原则)
  • 单一职责,类或者对象只有单一职责
  • 开闭原则,设计要对扩展开发,对修改关闭
  • 里氏替换,凡是可以用父类或者基类的地方,都可以用子类替换。可参考:面向对象设计 里氏替换原则(LSP)
  • 接口分离,对行为进行解耦
  • 依赖反转,实体应该依赖于抽象而不是实现。

    回答问题:

    接口和抽象类是Java面向对象设计的两个基础机制。
    接口是对行为的抽象,它是抽象方法的集合,利用接口可以达到API定义和实现分离的目的。接口、不能实例化;不能包含任何非常量成员,任何field都是隐含着public static final的意义;同时,没有非静态方法的实现,也就是说要么是抽象方法,要么是静态方法。Java标准类库中,定义了非常多的接口,比如java.util.List。
    抽象类是不能实例化的类,用abstract关键字修饰class,其目的是代码重用,除了不能实例化,形式上和一般的Java类并没有太大区别,可以有一个或者多个抽象方法,也可以没有抽象方法。抽象类大多用户抽取相关Java类的共用方法或者是共同成员变量,然后通过继承的方式达到代码重用的目的。Java标准类库中,比如collection框架,很多通用部分被抽取成为抽象类,例如java.util.AbstractList。
    Java类实现interface使用implements关键词,继承abstract class是使用extends关键词,例如ArrayList:
1
2
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable

参考:

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg

当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

Activity底层知识之PMS及App安装过程透彻分析.md

前言:

PMS,全称PackageManagerService,是用来获取Apk包的信息的。它负责系统中Package的管理,应用程序的安装、卸载、信息查询等。

PackageManagerService及客户端的类家族如图:

在下载并安装App的过程,会把Apk存放在data/app目录下。

Apk是一个zip压缩包,在文件头会记录压缩包的大小,所以后续在文件尾巴就算是追加一部小电影,也不会对解压造成影响——木马其实就是这个思路,在可执行文件exe尾巴上挂一个木马病毒,执行exe的同时也会执行这个木马,然后你就中招了。

我们可以把木马思想运用在Android多渠道打包上。在比较老的Android 4.4版本中,我们会在Apk尾巴上追加几个字节,来标记Apk的渠道。Apk启动的时候,从apk中的尾巴上读取这个渠道值。

后来Google也发现这个安全漏洞了,在新版本的系统中,就会在Apk安装的时候,检查Apk的实际大小,看这个值与Apk的头部记录的压缩包大小,是否相等,不相等就会报错说安装失败。

每次从apk中读取资源,并不是先解压再找图片资源,而是解析apk中的resources.arsc文件,这个文件中存储资源的所有信息,包括资源在apk中地址、大小等。不解压apk的好处自然是节省空间。

App的安装流程

Android系统使用PMS解析这个Apk中的manifest文件,包括:

  • 四大组件的信息,比如说,前面讲过的静态Receiver。比如说默认启动的Activity。

  • 分配用户Id和用户组Id。用户Id是唯一的,因为Android是一个Linux系统。用户组Id指的是各种权限,每个权限都在一个用户组中,比如读写SD卡,比如网络访问,分配了哪些用户组Id,就拥有了哪些权限。

  • 在Launcher生成一个icon,icon中保存着默认启动的Activity的信息。

  • App安装过程的最后,是把上面这些信息记录在一个xml文件中,以备下次安装时再次使用。

在Android手机系统每次启动的时候,都会使用PMS,把Android系统的所有apk都安装一遍,一共四个步骤。
image

  • 第1步,因为结束安装的时候,都会把安装信息保存在xml文件中,所以Android系统再次启动时,再次重新安装所有的Apk,就可以直接读取之前保存的xml文件了。

  • 第2步,从5个目录中读取并安装所有的apk。

  • 第3步和第4步,与单独安装一个App的步骤是一样的。

应用程序的完整安装过程图:
image

PackageParser

Android 安装一个APK的时候首先会解析APK,而解析APK则需要用到一个工具类,这个工具类就是PackageParser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Parser for package files (APKs) on disk. This supports apps packaged either
* as a single "monolithic" APK, or apps packaged as a "cluster" of multiple
* APKs in a single directory.
* <p>
* Apps packaged as multiple APKs always consist of a single "base" APK (with a
* {@code null} split name) and zero or more "split" APKs (with unique split
* names). Any subset of those split APKs are a valid install, as long as the
* following constraints are met:
* <ul>
* <li>All APKs must have the exact same package name, version code, and signing
* certificates.
* <li>All APKs must have unique split names.
* <li>All installations must contain a single base APK.
* </ul>
*
* @hide
*/
public class PackageParser {

大体意思如下:

解析磁盘上的APK安装包文件。它既能解析一个”单一”APK文件,也能解析一个”集群”APK文件(即一个APK文件里面包含多个APK文件)。
一个”集群”APK有一个”基准”APK(base APK)组成和其他一些”分割”APK(“split” APKs)构成,其中这些”分割”APK用一些数字来分割。这些”分割”APK的必须都是有效的安装,同时必须满足下面的几个条件:

  • 所有的APK必须具有完全相同的软件包名称,版本代码和签名证书
  • 所有的APK必须具有唯一的拆分名称
  • 所有安装必须包含一个单一的APK。

所以我们知道PackageParse类,它主要用来解析手机上的APK文件(支持Single APK和MultipleAPK),解析一个APK主要是分为两个步骤:

  • 将APK解析成Package:即解析APK文件为Package对象的过程。
  • 将Package转化为PackageInfo:即由Package对象生成Package对象生成PackageInfo的过程。

在插件化编程中,我们反射PackageParser,一般用来获取AndroidManifest中的四大组件信息。

参考:

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg

当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

如何保证集合是线程安全的?ConcurrentHashMap如何实现高效的线程安全?

问题:

  • 如何保证集合是线程安全的?
  • ConcurrentHashMap如何实现高效的线程安全?

    知识点:

  1. 为什么需要ConcurrentHashMap?
    Hashtable比较低效,所有线程公用同一把锁,效率低下。Collections工具类提供的Map方法,大同小异,源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private static class SynchronizedMap<K, V> implements Map<K, V>, Serializable {
private static final long serialVersionUID = 1978198479659022715L;
private final Map<K, V> m;
//划重点了,利用this作为互斥的mutex
final Object mutex;
private transient Set<K> keySet;
private transient Set<Entry<K, V>> entrySet;
private transient Collection<V> values;
SynchronizedMap(Map<K, V> var1) {
this.m = (Map)Objects.requireNonNull(var1);
this.mutex = this;
}
SynchronizedMap(Map<K, V> var1, Object var2) {
this.m = var1;
this.mutex = var2;
}
public int size() {
Object var1 = this.mutex;
synchronized(this.mutex) {
return this.m.size();
}
}
public boolean isEmpty() {
Object var1 = this.mutex;
//方法不再申明为synchronized方法,但是利用this作为互斥的mutex,大同小异 synchronized(this.mutex) {
return this.m.isEmpty();
}
}

高并发时候,容易始HashMap死循环导致CPU占用100%。可参考不正当使用HashMap导致cpu 100%的问题追究
HashMap 死循环的探究

  1. ConcurrentHashMap,篇幅太长,后续有专门文章分析。

回答问题

Java提供了不同层面的线程安全支持,在传统的框架内部,除了Hashtable等同步容器,还提供了所谓的同步包装器,我们可以调用Collecions工具的同步方法,来获取一个同步的包装容器(如:Collections.synchronizedMap),但是他们都是利用非常粗粒度的处理方式,在高并发的时候,性能比较低下。

另外,更加普遍的选择是利用并发包提供的线程安全容器类,它提供了:

具体保证线程安全的方式,包括从有简单的synchronize方式,到基于更加精细化的,比如基于分离锁实现的ConcurrentHashMap等并发实现等。具体选择要看开发的场景需求。总得来说,并发包提供的容器通用场景,远优于早期的简单的同步实现。
自己项目中使用代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private Map<Promise, ReadableMap> mPictureTakenOptions = new ConcurrentHashMap<>();
private Map<Promise, File> mPictureTakenDirectories = new ConcurrentHashMap<>();
public void takePicture(ReadableMap options, final Promise promise, File cacheDirectory) {
mPictureTakenPromises.add(promise);
mPictureTakenOptions.put(promise, options);
mPictureTakenDirectories.put(promise, cacheDirectory);
if (mPlaySoundOnCapture) {
MediaActionSound sound = new MediaActionSound();
sound.play(MediaActionSound.SHUTTER_CLICK);
}
try {
super.takePicture();
} catch (Exception e) {
mPictureTakenPromises.remove(promise);
mPictureTakenOptions.remove(promise);
mPictureTakenDirectories.remove(promise);
throw e;
}
}

参考:

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg

当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

Activity底层知识之Binder透彻分析

前言:

apk是怎么安装的?资源是怎么加载的?AIDL究竟是什么?Binder又是啥?等等这样的疑问,今天起就开始一一揭开。
先来一张图(网络图)感受一下:

今天重点先分析Binder。

知识准备

在讲解Binder前,我们先了解一些Linux的基础知识。

进程空间划分

  • 一个进程空间分为 用户空间 & 内核空间(Kernel),即把进程内 用户 & 内核 隔离开来。
  • 二者区别:进程间,用户空间的数据不可共享,所以用户空间 = 不可共享空间;进程间,内核空间的数据可共享,所以内核空间 = 可共享空间 。

    所有进程共用1个内核空间

  • 进程内 用户空间 & 内核空间 进行交互 需通过 系统调用,主要通过函数:
    • [x] copy_from_user():将用户空间的数据拷贝到内核空间
    • [x] copy_to_user():将内核空间的数据拷贝到用户空间

image

进程隔离 & 跨进程通信( IPC )

  • 进程隔离
    为了保证 安全性 & 独立性,一个进程 不能直接操作或者访问另一个进程,即Android的进程是相互独立、隔离的
  • 跨进程通信( IPC )
    即进程间需进行数据交互、通信
  • 跨进程通信的基本原理

image

a. 而Binder的作用则是:连接 两个进程,实现了mmap()系统调用,主要负责 创建数据接收的缓存空间 & 管理数据接收缓存

b. 注:传统的跨进程通信需拷贝数据2次,但Binder机制只需1次,主要是使用到了内存映射。

Binder 跨进程通信机制

image

模型原理步骤说明

image

以上内容参考:Android跨进程通信:图文详解 Binder机制 原理,如若侵权,请联系作者删除

Binder组成

(划重点了,作为APP开发人员👆上面的内容不是非必须的,下面的知识点是必须掌握的)

  • Binder分为Client和Server两个进程
    • [x] Client和Server是相对的,谁发消息,谁就是Client,谁接受消息,谁就是Server。
  • Binder组成。

整个通信过程过程可以看做一个拨打电话进行通信的过程,ServericeManager相当于一个电话局,张三(Client)给李四(Server)打电话,电话局先解析电话号码的地址。有的话就可以拨通,没有的话,就报错。

  • Binder通信过程

AIDL原理

AIDL是Binder的延伸。
Android系统中很多系统服务都是AIDL,比如剪切板,双进程守护。这里举例一下双进程守护:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /Users/huangchen/workspace/web/yhparter/yh-partner-app/android/app/src/main/aidl/com/yonghuivip/partner/Service_1.aidl
*/
package com.yonghuivip.partner;
// Declare any non-default types here with import statements
public interface Service_1 extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.yonghuivip.partner.Service_1 {
private static final java.lang.String DESCRIPTOR = "com.yonghuivip.partner.Service_1";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.yonghuivip.partner.Service_1 interface,
* generating a proxy if needed.
*/
public static com.yonghuivip.partner.Service_1 asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.yonghuivip.partner.Service_1))) {
return ((com.yonghuivip.partner.Service_1) iin);
}
return new com.yonghuivip.partner.Service_1.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
//收到Binder驱动通知后,Server 进程通过回调Binder对象onTransact()进行数据解包 & 调用目标方法
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
// code即在transact()中约定的目标方法的标识符
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getName: {
// a. 解包Parcel中的数据
// a1. 解析目标方法对象的标识符
data.enforceInterface(DESCRIPTOR);
// a2. 获得目标方法的参数
java.lang.String _result = this.getName();
reply.writeNoException();
// c. 将计算结果写入到reply
reply.writeString(_result);
return true;
}
}
// 2. 将结算结果返回 到Binder驱动
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.yonghuivip.partner.Service_1 {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
// 注:在发送数据后,Client进程的该线程会暂时被挂起
// 所以,若Server进程执行的耗时操作,请不要使用主线程,以防止ANR
@Override
public java.lang.String getName() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
//通过 调用代理对象的transact() 将 上述数据发送到Binder驱动
mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public java.lang.String getName() throws android.os.RemoteException;
}

AIDL重要的几个类:

  • IBinder
  • IInterface
  • Stub
  • Binder
  • Proxy

AIDL中涉及到的类图:

  • 1
    2
    3
    4
    MyService.asInterface(service).getName()
    ```
    asInteface作用是判断当前进程是否和自己再同一个进程。Stub调用自己的asInterface方法发现不在同一个进程之内,就调用Proxy的getName()
    - Proxy在自己的getName方法中,会使用Parceable来准备数据,把函数名称、函数参数都写入_data,让_reply接受函数返回值,最后调用IBinder的transact,就可以把数据传递给Binder的Server端。

    //通过 调用代理对象的transact() 将 上述数据发送到Binder驱动
    mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);

    1
    2
    - Server通过onTransact方法接受Client传递过来的数据,包括函数名称、函数参数,找到对应的函数,这里是getName(),把参数传递过去。所以onTransact方法经历了:读数据、执行要调用的函数、把执行结果再写入数据。

    //收到Binder驱动通知后,Server 进程通过回调Binder对象onTransact()进行数据解包 & 调用目标方法

    @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        // code即在transact()中约定的目标方法的标识符
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_getName: {
                // a. 解包Parcel中的数据
                // a1. 解析目标方法对象的标识符
                data.enforceInterface(DESCRIPTOR);
                // a2. 获得目标方法的参数
                java.lang.String _result = this.getName();
                reply.writeNoException();
                // c. 将计算结果写入到reply
                reply.writeString(_result);
                return true;
            }
        }
        // 2. 将结算结果返回 到Binder驱动
        return super.onTransact(code, data, reply, flags);
    }
    

    ```

四大组件的启动和后续流程,都是在和AMS打交道,四大组件给AMS发消息,四大组件就是Client Binder , AMS就是Server Binder,反之,角色替换。

参考:

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg

当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

Activity底层知识之Activity启动透彻分析

前言

对于我们做电商APP的开发人员来说,Activity是四大组件中使用最多的,当然也是最复杂的。那Activity是如何启动的?点击手机上APP图标又发生了什么呢?今天我们就一一解析。

APP是怎么启动的?

当我们点击某个APP图标的时候,这里假设是电商APP-永辉生活。这个APP的首页(或启动页)就展示在我们面前了。看似简单的操作,其实里面是Activity和AMS反反复复的通信过程。

这里补充一下,我们点击的应用图标的界面其实是一个Activity,官方用语Launcher。launcher其实就是一个app,它的作用用来显示和管理手机上其他App。目前市场上有很多第三方的launcher应用,比如“小米桌面”、“91桌面”等等(因此我们可以对桌面图标进行分组,调用网络请求等)。

首先回顾一下我们是怎么定义默认启动的Activity的呢?

1
2
3
4
5
6
7
8
9
10
<activity
android:name=".guide.NullDefaultActivity"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="landscape"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

这些配置信息在APP安装的时候(或者Android系统启动的时候),通过PMS(PackageManagerService)从永辉生活APK的配置文件(AndroidManifest)文件中读取到的。所以点击永辉生活应用图标就启动了永辉生活。

启动APP就这么简单吗?

并非如此。上述只是一个最简单描述。我们通过Launcher启动一个新的Activity是启动一个新的进程,Launcher和永辉生活 APP属于不同的进程。它们之间的通信是通过Binder完成通信的。我们的AMS登场了。
我们这里就以永辉生活APP为例,整体流程分为7个阶段。

  1. Launcher通知AMS,要启动永辉生活APP,并告诉要启动哪个页面(这里我们是引导页)
  2. AMS通知Launcher,“好了,我知道了,谢谢你了,你可以休息了”。同时把要启动的引导页信息记录下来。
  3. Launcher进入paused状态,同时通知AMS,“那我休息了,接下来我就不管了”。

    1-3阶段上面是Launcher和AMS之间的通信。

  4. AMS检查永辉生活APP是否已经启动了。是的话,就唤起永辉生活APP,如果不是的话,就开启一个新的进程。AMS在新进程创建一个ActivityThread对象,启动其中的main函数。
  5. 永辉生活APP启动后,通知AMS,“我启动好了”。
  6. AMS翻出第二阶段的保存的值,告诉永辉生活APP,启动哪个页面。
  7. 永辉生活APP启动引导页,创建Context并与引导页Activity相关联。然后调用引导页onCreate相关联。

    4-7阶段,永辉生活APP与AMS相互通信。

这边涉及到一堆类。

  • ActivityThread: 应用的启动入口类,当应用启动,会首先执行其main方法,开启主线程消息循环机制。
  • ApplicationThread: ActivityThread的内部类,主要与系统进程AMS通信,从而对应用进程的具体Activity操作进行管理。
  • Instrumentation: ActivityThread的属性变量,主要辅助ActivityThread类调用Activity的生命周期相关方法。
  • ActivityManagerService(AMS): Activity管理系统服务类,主要是对所有的Activity进行管理。
  • ActivityStack,Activity在AMS的栈管理,用来记录已经启动的Activity的先后关系,状态信息等。通过ActivityStack决定是否需要启动新的进程。
  • ActivityRecord,ActivityStack的管理对象,每个Activity在AMS对应一个ActivityRecord,来记录Activity的状态以及其他的管理信息。其实就是服务器端的Activity对象的映像。
  • TaskRecord,AMS抽象出来的一个“任务”的概念,是记录ActivityRecord的栈,一个“Task”包含若干个ActivityRecord。AMS用TaskRecord确保Activity启动和退出的顺序。如果你清楚Activity的4种launchMode,那么对这个概念应该不陌生。

第1阶段:Launcher通知AMS

Launcher通知AMS的流程图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* Launches the intent referred by the clicked shortcut.
*
* @param v The view representing the clicked shortcut.
*/
public void onClick(View v) {
Object tag = v.getTag();
if (tag instanceof ApplicationInfo) {
// Open shortcut
final Intent intent = ((ApplicationInfo) tag).intent;
startActivitySafely(intent);
} else if (tag instanceof FolderInfo) {
handleFolderClick((FolderInfo) tag);
}
}
void startActivitySafely(Intent intent) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
} catch (SecurityException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
e(LOG_TAG, "Launcher does not have the permission to launch " + intent +
". Make sure to create a MAIN intent-filter for the corresponding activity " +
"or use the exported attribute for this activity.", e);
}
}

继续跟进:

1
2
3
4
5
6
7
8
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
startActivityForResult(intent, -1);
}
}

后调用的还是这个重载方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
if (mParent == null) {
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
if (requestCode >= 0) {
mStartedActivity = true;
}
cancelInputsAndStartExitTransition(options);
} else {
if (options != null) {
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}

可以发现这里调用了mInstrumentation.execStartActivity方法,这里先简单介绍一下Instrumentation对象,他是Android系统中应用程序端操作Activity的具体操作类,这里的操作段是相对于ActivityManagerService服务端来说的。也就是说当我们在执行对Activity的具体操作时,比如回调生命周期的各个方法都是借助于Instrumentation类来实现的。

我们发现有个mMainThread变量,这是一个ActivityThread变量,就是主线程,也就是UI线程,它是APP启动时候创建的,它代表了一个应用程序。

它里面有个main函数,这个是由Android系统底层提供的。源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(String[] args) {
//省略部分代码
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}

上面代码传递了两个很重要的参数:

  • 通过ActivityThread的getApplicationThread方法取到一个Binder对象,它的类型为ApplicationThread,它代表着Launcher所在的App进程。
  • mToken,这也是个Binder对象,它代表了Launcher这个Activity,这里也通过Instrumentation传给AMS,AMS一查电话簿,就知道是谁向AMS发起请求了。

这两个参数是伏笔,传递给AMS,以后AMS想反过来通知Launcher,就能通过这两个参数,找到Launcher。

Instrumentation的execStartActivity方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
....................................................
try {
....................................
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}

AMN的getDefault方法

  • ServiceManager是一个容器类。
  • AMN的getDefault方法返回类型为IActivityManager,而不是AMP。IActivityManager是一个实现了IInterface的接口,里面定义了四大组件所有的生命周期。

image

AMP的startActivity方法

看到这里,你会发现AMP的startActivity方法,和AIDL的Proxy方法,是一模一样的,写入数据到另一个进程,也就是AMS,然后等待AMS返回结果。

第2阶段 AMS处理Laucner传递过来的信息

  • 首先Binder,也就是AMN/AMP,和AMS通信,肯定每次是做不同的事情,就比如说这次Launcher要启动永辉生活App,那么会发送类型为START_ACTIVITY——TRANSACTION的请求给AMS,同时会告诉AMS要启动哪个Activity。
  • AMS说,好,我知道了,然后它会干一件很有趣的事情,就是检查永辉生活App中的Manifest文件,是否存在要启动的Activity。如果不存在,就抛出Activity not found的错误,各位做App的同学对这个异常应该再熟悉不过了,经常写了个Activity而忘记在Manifest中声明了,就报这个错,就是因为AMS在这里做检查。不管是新启动一个App的首页,还是在App内部跳转到另一个Activity,都会做这个检查。
  • 接下来AMS会通知Launcher

    Binder的双方进行通信是平等的,谁发消息,谁就是Client,接收的一方就是Server。Client这边会调用Server的代理对象。

那么当AMS想给Launcher发消息,又该怎么办呢?前面不是把Launcher以及它所在的进程给传过来了吗?它在AMS这边保存为一个ActivityRecord对象,这个对象里面有一个ApplicationThreadProxy,单单从名字看就出卖了它,这就是一个Binder代理对象。它的Binder真身,也就是ApplicationThread。

站在AIDL的角度,来画这张图,是这样的:

image

第3阶段:Launcher开始休息,然后通知AMS,“我休息了”

先来感受一下:

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// we use token to identify this activity without having to send the
// activity itself back to the activity manager. (matters more with ipc)
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
ActivityClientRecord r = new ActivityClientRecord();
r.token = token;
r.ident = ident;
r.intent = intent;
r.referrer = referrer;
r.voiceInteractor = voiceInteractor;
r.activityInfo = info;
r.compatInfo = compatInfo;
r.state = state;
r.persistentState = persistentState;
r.pendingResults = pendingResults;
r.pendingIntents = pendingNewIntents;
r.startsNotResumed = notResumed;
r.isForward = isForward;
r.profilerInfo = profilerInfo;
r.overrideConfig = overrideConfig;
updatePendingConfiguration(curConfig);
sendMessage(H.LAUNCH_ACTIVITY, r);
}
1
2
3
4
5
6
7
8
9
10
11
12
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;

AMS给Activity发送的所有消息,以及给其它三大组件发送的所有消息,都从H这里经过。插件化就可以在这里动手脚。
H对于PAUSE_ACTIVITY消息的处理,如上面的代码,是调用ActivityThread的handlePauseActivity方法。这个方法干两件事:

  • ActivityThread里面有一个mActivities集合,保存当前App也就是Launcher中所有打开的Activity,把它找出来,让它休眠。
  • 通过AMP通知AMS,我真的休眠了。
    你可能会找不到H和APT这两个类文件,那是因为它们都是ActivityThread的内嵌类。

第4阶段:AMS启动新的进程

AMS接下来要启动永辉生活App的首页,因为永辉生活App不在后台进程中,所以要启动一个新的进程。这里调用的是Process.start方法,并且指定了ActivityThread的main函数为入口函数。

1
2
3
4
Process.ProcessStartResult startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, debugFlags, mountExternal,
app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
app.info.dataDir, entryPointArgs);

第5阶段:新的进程启动,以ActivityThread的main函数为入口

启动新进程,其实就是启动一个新的APP。

image

在启动新进程的时候,为这个进程创建ActivityThread对象,这就是我们耳熟能详的主线程(UI线程)。

创建好UI线程后,立刻进入ActivityThread的main函数,接下来要做2件具有重大意义的事情:

1)创建一个主线程Looper,也就是MainLooper。看见没,MainLooper就是在这里创建的。
2)创建Application。记住,Application是在这里生成的。

第6阶段 AMS告诉新App启动哪个Activity

还记得第1阶段,Launcher发送给AMS要启动永辉生活App的哪个Activity吗?这个信息被AMS存下来了。

那么在第6阶段,AMS从过去的记录中翻出来要启动哪个Activity,然后通过ActivityThreadProxy告诉App。

第7阶段 启动永辉生活APP首页Activity

万事俱备只欠东风,收场的来了

image

handleLaunchActivity方法都做哪些事呢?

  • 通过Instrumentation的newActivity方法,创建出来要启动的Activity实例。
  • 为这个Activity创建一个上下文Context对象,并与Activity进行关联。
  • 通过Instrumentation的callActivityOnCreate方法,执行Activity的onCreate方法,从而启动Activity。看到这里是不是很熟悉很亲切?

至此,App启动完毕。这个流程是经过了很多次握手, App和ASM,频繁的向对方发送消息,而发送消息的机制,是建立在Binder的基础之上的。

参考:

  • Android系统源代码情景分析(第三版) 罗升阳著
  • Android插件化开发指能 包建强著
  • 项目源码代码分析
  • launcher 启动流程解析

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg

当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

深入理解并发类库中提供线程安全队列

问题:

并发包中ConcurrentLinkedQueue和LinkedBlockingQueue有什么区别?

知识点

  • Java并发类库中提供的各种各样的线程安全队列

image

  • BlockingQueue提供了特定的等待性操作,获取(take)等待元素进队,或者插入(put)等待队列出现空位。

源码:

1
2
3
4
5
6
7
8
/**
* Retrieves and removes the head of this queue, waiting if necessary
* until an element becomes available.
*
* @return the head of this queue
* @throws InterruptedException if interrupted while waiting
*/
E take() throws InterruptedException;
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Inserts the specified element into this queue, waiting if necessary
* for space to become available.
*
* @param e the element to add
* @throws InterruptedException if interrupted while waiting
* @throws ClassCastException if the class of the specified element
* prevents it from being added to this queue
* @throws NullPointerException if the specified element is null
* @throws IllegalArgumentException if some property of the specified
* element prevents it from being added to this queue
*/
void put(E e) throws InterruptedException;
  • ArrayBlockQueue是典型的有界队列,其内部是以final的数组保存数据,数组的大小就决定了队列的边界,所以我们在创建ArrayBlockingQueue时,都指定容量,如:
1
2
3
4
5
6
7
8
9
10
/**
* Creates an {@code ArrayBlockingQueue} with the given (fixed)
* capacity and default access policy.
*
* @param capacity the capacity of this queue
* @throws IllegalArgumentException if {@code capacity < 1}
*/
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
  • LinkedBlockingQueue,容易被误解为无边界,但其行为和内部代码都是基于有界的逻辑实现的,只不过如果我们没有在创建队列时就指定容量,那么其容量限制就自动设置Integer.MAX_VALUE,称为了无界队列。
1
2
3
4
5
6
7
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
  • SynchronousQueue,这是一个非常奇葩的队列实现,每个删除操作都要等待插入操作,反之每个插入操作都要等待删除动作。那么这个队列的容量是多少呢?是1吗?其实不是的,其内部容量是0。
  • PriorityBlockingQueue是无边界的优先队列,虽然严格意义上讲,其大小总归是手系统资源影响。
  • DelayQueue和LinkedTransferQueue同样是无边界的队列。对于无边界的队列,有一个自然的结果,就是put操作永远也不会发生其他BlockingQueue的那种等待情况。

    回答问题:

    有时候我们把并发包下面的所有容器都习惯叫做并发容器,但是严格来讲,类似ConcurrentLinkedQueue这种“Concurrent”容器,才是真正代表并发。
    关于问题中它们的区别:
  • Concurrent类型基于lock-free,在常见的多线程访问场景,一般可以提供较高吞吐量。
  • 而LinkedBlockingQueue内部则是基于锁,并提供了BlockingQueue的等待性方法。
    不知道你有没有注意到,java.util.concurrent包提供的容器(Queue、List、Set)、Map,从命名上可以大概区分为Concurrent*、CopyOnWrite和Blocking等三类,同样是线程安全容器,可以简单认为:
  • Concurrent类型没有类似CopyOnWrite之类容器较重的修改开销。
  • 但是,凡是都是有代价的,Concurrent往往提供了较低的遍历一致性,你可以这样理解所谓的弱一致性,例如,当李勇迭代器遍历时,如果容器发生修改,迭代器仍然可以继续进行遍历。
  • 与弱一致性对应的,就是我介绍过的同步容器常见的行为“fail-fast”,也就是检测到容器在遍历过程中发生了修改,则抛出ConcurrentModificationException,不再继续遍历。
  • 弱一致性的另外一个体现是,size等操作准确性是有线的,未必是100%准确。
  • 与此同时,读取的性能具有一定的不确定性。

参考:

  • 队列中部分源码
  • 极客时间APP核心技术第20讲| 并发包中ConcurrentLinkedQueue和LinkedBlockingQueue有什么区别?

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg

当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

对Android类加载器最全面的分析

心得体会:学习不仅仅只是看教程,最好能够想出代码实例去验证自己对某个方面的理解和判断,这样不仅能加深理解,还能够在未来的应用开发中使用到。

前言

本篇文章针对Java 8 之前的类加载结构,Java 9做了不少改变,有兴趣可以查看相关资料。

虚拟机类加载机制

类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括:

image

1. 加载

  • 通过一个类的全限定名来获取定义此类的二进制流。

  • 将这个字节流所代表的静态存储结构转化为运行时数据结构

  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种访问入口。

注意,这里第1条中的二进制字节流并不只是单纯地从Class文件中获取,比如它还可以从Jar包中获取、从网络中获取(最典型的应用便是Applet)、由其他文件生成(JSP应用)等。

2. 链接:

  • 验证:确保被加载类的正确性;

  • 准备:为类的静态变量分配内存,并将其初始化为默认值;

  • 解析:把类中的符号引用转换为直接引用;(解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始)

3. 初始化

jvm有严格的规定(五种情况):

  1. 遇到new,getstatic,putstatic,invokestatic这4条字节码指令时,假如类还没进行初始化,
    则马上对其进行初始化工作。
    其实就是3种情况:用new实例化一个类时、读取或者设置类的静态字段时(不包括被final修饰的静态字段,
    因为他们已经被塞进常量池了)、以及执行静态方法的时候。

  2. 使用java.lang.reflect.*的方法对类进行反射调用的时候,
    如果类还没有进行过初始化,马上对其进行。

  3. 初始化一个类的时候,如果他的父亲还没有被初始化,则先去初始化其父亲。

  4. 当jvm启动时,用户需要指定一个要执行的主类(包含static void main(String[] args)的那个类),
    则jvm会先去初始化这个类。

  5. 用Class.forName(String className);来加载类的时候,也会执行初始化动作。
    注意:ClassLoader的loadClass(String className);方法只会加载并编译某类,并不会对其执行初始化。

对于这5种会触发类初始化的场景,虚拟机使用了一个很强烈的限定语,“有且只有”,这5种场景的行为称为对一个类进行主动引用。除此之外,所有引用类的方法都不会触发初始化。

  • 如:通过子类引用父类的静态字段,不会导致子类初始化;
  • 通过数组定义引用类,不会触发此类的初始化;
  • 常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。

初始化阶段是执行类构造器()方法的过程。()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的 。

这里借网络上一张图片总结一下,有兴趣可参考:Java面试相关(一)– Java类加载全过程(觉得图画的很细致,如若侵权,请联系作者)

image

双亲委派模型

image

看结构图(组合关系,非继承关系)

image

从图中我们发现除启动类加载器外,每个加载器都有父的类加载器。
双亲委派机制:如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

优势:Java类随着它的类加载器一起具备了一种带有优先级的层次关系,避免了重复加载类,保障了Java类型体系的安全。

当然你也可以破坏双亲委派模型,尤其在Android插件化中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);//判断类是否已经加载过
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);//父类加载器优先加载
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);//调用当前类加载器的findClass方法进行加载
// this is the defining class loader; record the stats
}
}
return c;
}

源码分析:

简单来说,java的双亲委派机制分为三个过程,在ClassLoader的loadClass方法中会先判断该类是否已经加载,若加载了直接返回,若没加载过则先调用父类加载器的loadClass方法进行类加载,若父类加载器没有找到,则会调用当前正在查找的类加载器的findClass方法进行加载。

如果想保证自定义的类加载器符合双亲委派机制,则覆写findClass方法;如果想打破双亲委派机制,则覆写loadClass方法。

破坏双亲委派机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyClassLoader extends DexClassLoader{
public MyClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super(dexPath, optimizedDirectory, librarySearchPath, parent);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if(xxx){//条件判断是否自己加载
return this.loadClass(name);
}else{//双亲委派机制加载
return super.loadClass(name, resolve);
}
}
}

父类,子类加载顺序

父类代码:

1
2
3
4
5
6
7
8
9
10
11
public class A{
static{
System.out.println("父类-静态代码块");
}
{
System.out.println("父类-非静态代码块");
}
public A(){
System.out.println("父类-构造方法");
}
}

子类代码:

1
2
3
4
5
6
7
8
9
10
11
public class B extends A{
static{
System.out.println("子类-静态代码块");
}
{
System.out.println("子类-非静态代码块");
}
public B(){
System.out.println("子类-构造方法");
}
}

测试一下:

1
2
3
4
5
public class Test{
public static void main(String[] args) {
B b = new B();
}
}

看看效果:

1
2
3
4
5
6
父类-静态代码块
子类-静态代码块
父类-非静态代码块
父类-构造方法
子类-非静态代码块
子类-构造方法

看到这,就知道初始化子类会先初始化父类。顺序为 父类静态——》子类静态——》父类非静态代码块——》父类构造方法——》子类非静态代码块——》子类构造方法。

以上根本原因:初始化一个类的时候,如果他的父亲还没有被初始化,则先去初始化其父亲。

Android中类加载器介绍

android中的类加载器中主要包括三类BootClassLoader,PathClassLoader和DexClassLoader。

BootClassLoader主要用于加载系统的类,包括java和android系统的类库。

PathClassLoader主要用于加载应用内中的类。路径是固定的,只能加载
/data/app中的apk,无法指定解压释放dex的路径。所以PathClassLoader是无法实现动态加载的。

DexClassLoader可以用于加载任意路径的zip,jar或者apk文件。可以实现动态加载。下面来具体看看应用程序中的类加载器。

Android的BootClassLoader和Java的BootStrapClassLoader区别:
  • Android虚拟机中BootClassLoader是ClassLoader内部类,由java代码实现而不是c++实现,是Android平台上所有ClassLoader的最终parent,这个内部类是包内可见,所以我们没法使用。

  • Java虚拟机中BootStrapClassLoader是由原生代码(C++)编写的,负责加载java核心类库(例如rt.jar等) .

补充知识点:

1
2
3
4
5
Log.i("ljj", "Context的类加载器:"+ Context.class.getClassLoader());
Log.i("ljj", "TextView的类加载器: "+ TextView.class.getClassLoader());
//打印结果
02-14 12:37:49.161 22341-22341/com.ljj.host I/ljj: Context的类加载器:java.lang.BootClassLoader@a645091
02-14 12:37:49.162 22341-22341/com.ljj.host I/ljj: TextView的类加载器: java.lang.BootClassLoader@a645091

可见系统的类都是由BootClassLoader加载完成。

1
2
Log.i("ljj", "classLoader:"+getClassLoader());
02-14 13:19:23.730 20518-20518/com.ljj.host I/ljj: classLoader:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.ljj.host-2/base.apk"],nativeLibraryDirectories=[/data/app/com.ljj.host-2/lib/arm64, /vendor/lib64, /system/lib64]]]

DexClassLoader:

1
2
3
4
5
6
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}

DexClassLoader的源码很简单,只包含一个构造函数,看来所有的工作都是在BaseDexClassLoader中完成的。这里再看BaseDexClassLoader前,先说一下DexClassLoader构造函数的四个参数。

  • dexPath:是加载apk/dex/jar的路径
  • optimizedDirectory:是dex的输出路径(因为加载apk/jar的时候会解压除dex文件,这个路径就是保存dex文件的)
  • librarySearchPath:是加载的时候需要用到的lib库,这个一般不用,可以传入Null
  • parent:给DexClassLoader指定父加载器
    下面继续分析BaseClassLoader。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}

可以看出,DexClassLoader会通过传入的路径构造出一个DexPathList对象,作为pathList。从findClass方法可以看出来加载的类都是从pathList中查找。至于DexPathList对象的源码就不往下具体分析了,简单的理解就是将每个dex都构建成Element元素,放入到dexElements数组中,多说一句,这个dexElements数组的用处很大,MultiDex方案以及由此衍生出的QQ空间热更新方案都是通过改变dexElements数组的元素位置来实现的。感兴趣可以参考:Android类加载器分析

Android类加载器和Java的类加载器工作机制是类似的,使用双亲委托机制。

参考:

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg

当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

红黑树的理解(硬骨头也会变成软骨头)

简介:

百度百科:红黑树(英语:Red–black tree)是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。它是在1972年由鲁道夫·贝尔发明的,他称之为”对称二叉B树”,它现代的名字是在Leo J. Guibas和Robert Sedgewick于1978年写的一篇论文中获得的。它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的:它可以在O(logn时间内做查找,插入和删除,这里的 n是树中元素的数目。

性质:

红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

  • 节点是红色或黑色。
  • 根是黑色。
  • 所有叶子都是黑色(叶子是NIL节点)。
  • 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
  • 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

下面是一个具体的红黑树的图例:

image

这些约束确保了红黑树的关键特性:从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。

注意:
  • 性质 3 中指定红黑树的每个叶子节点都是空节点,而且并叶子节点都是黑色。但 Java 实现的红黑树将使用 null 来代表空节点,因此遍历红黑树时将看不到黑色的叶子节点,反而看到每个叶子节点都是红色的。
  • 性质 4 的意思是:从每个根到节点的路径上不会有两个连续的红色节点,但黑色节点是可以连续的。
  • 性质 5 是成为红黑树最主要的条件,后序的插入、删除操作都是为了遵守这个规定。红黑树并不是标准平衡二叉树,它以性质 5 作为一种平衡方法,使自己的性能得到了提升。

红黑树的左旋右旋:

image

  • X 左旋(右图转成左图)的结果,是让在 Y 左子树的黑色节点跑到 X 右子树去。
  • 右旋把左子树里的一个节点(上图 β)移动到了右子树。

    红黑树插入删除在线调式网站:

    在线调试红黑树 插入删除规律有兴趣可自行搜索,重点是通过变色+旋转满足上述5条约定。

总结

红黑树并不是真正的平衡二叉树,但在实际应用中,红黑树的统计性能要高于平衡二叉树,但极端性能略差。

红黑树的插入、删除调整逻辑比较复杂,但最终目的是满足红黑树的 5 个特性,尤其是 4 和 5。

在插入调整时为了简化操作我们直接把插入的节点涂成红色,这样只要保证插入节点的父节点不是红色就可以了。

而在删除后的调整中,针对删除黑色节点,所在子树缺少一个节点,需要进行弥补或者对别人造成一个黑色节点的伤害。具体调整方法取决于兄弟节点所在子树的情况。

红黑树的插入、删除在树形数据结构中算比较复杂的,理解起来比较难,但只要记住,红黑树有其特殊的平衡规则,而我们为了维持平衡,根据邻树的状况进行旋转或者涂色。

红黑树这么难理解,必定有其过人之处。它的有序、快速特性在很多场景下都有用到,比如 Java 集合框架的 TreeMap, TreeSet 等。

参考:

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg

当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

强引用、软引用、弱引用、幻象引用再不理解就晚了

问题:

强引用、软引用、弱引用、幻象引用有什么区别?具体使用场景是什么?

知识点:

  1. 对象可达性
    对象可达性对于我们理解JVM 可达性分析有重要作用,具体后续文章会谈到。
  2. 引用队列(ReferenceQueue)使用。
    利用引用队列,我们可以在对象处于相应状态时,执行后期处理逻辑。例如:LeakCanary监控内存泄漏的源码中:
1
2
3
4
5
6
7
8
9
10
11
12
//源码地址 LeakCanary
com.squareup.leakcanary.RefWatcher
private void removeWeaklyReachableReferences() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
//do something
retainedKeys.remove(ref.key);
}
}

removeWeaklyReachableReferences 把已被回收的对象的 key 从 retainedKeys 移除,剩下的 key 都是未被回收的对象;LeakCanary分析可以参考文章:带你学开源项目:LeakCanary- 如何检测 Activity 是否泄漏

  1. Reachablity Fence

除了这四种基本引用类型,我们也可以通过底层API来达到引用的效果,这就是所谓的设置reachability fence。这里不做详细介绍,有兴趣的自己查看相关资料。

回答问题:

在Java语言中,除了基本数据类型以外,其他都是指向各类对象的对象引用,根据生命周期长短,通常分为四类:

强引用:我们正常new出来对象就是强引用,当内存不够的时候,JVM宁可抛出异常,也不会回收强引用对象。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显示的将相应(强)引用赋值为null,就是可以被垃圾收集了,当然具体回收时机还是要看垃圾收集策略。

软引用(SoftReference):软引用生命周期比强引用低,在内存不够的时候,会进行回收软引用对象。软引用对象经常和引用队列(ReferenceQueue)一起使用,在软引用所引用的对象被GC回收后,会把该引用加入到引用队列中。通常用来实现内存敏感的缓存。例如:Android图片框架Fresco中就使用到了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class OOMSoftReference<T> {
SoftReference<T> softRef1;
SoftReference<T> softRef2;
SoftReference<T> softRef3;
public OOMSoftReference() {
softRef1 = null;
softRef2 = null;
softRef3 = null;
}
public void set(@Nonnull T hardReference) {
softRef1 = new SoftReference<T>(hardReference);
softRef2 = new SoftReference<T>(hardReference);
softRef3 = new SoftReference<T>(hardReference);
}
@Nullable
public T get() {
return (softRef1 == null ? null : softRef1.get());
}
public void clear() {
if (softRef1 != null) {
softRef1.clear();
softRef1 = null;
}
if (softRef2 != null) {
softRef2.clear();
softRef2 = null;
}
if (softRef3 != null) {
softRef3.clear();
softRef3 = null;
}
}
}

有兴趣可以自己去查看源码。

弱引用(WeakReference):弱引用生命周期比软引用要短,在下一次GC的时候,扫描到它所管辖的区域存在弱引用的话,不管当前内存是否够,都会进行回收。(由于垃圾回收器是一个优先级很低的线程,因此不一定会很快回收弱引用的对象)。弱引用和软引用一样,也会经常和引用队列(ReferenceQuene)一起使用,在弱引用所引用的对象被GC回收后,会把该引用加入到引用队列中。经常用在图片缓存中。例如:ThreadLocal中所持用的静态类ThreadLocalMap的Key值就用到了弱引用,防止内存泄露(value可能存在内存泄露,调用remove方法)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

有兴趣的话,可以查看源码,也可以参考文章:理解Java中的ThreadLocal

幻象引用(PhantomReference):又叫虚引用,幻想引用仅仅是提供了一种确保对象被finalize后会处理某些事情,必须和引用队列一起使用(ReferenceQuene)。比如上面一篇文章所说的Cleaner机制中就使用到了幻象引用,也可以用来跟踪对象被垃圾回收器回收的活动,当一个幻象引用关联的对象被垃圾回收器回收之前收到一条系统通知。

参考:

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg
当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

|