关于Ubuntu有多个Python版本的解决方案

执行:which 软件名,会输出软件路径:可以看我我这有好几个python版本。

1
2
3
4
5
6
7
yu@librity:~$ which python
/usr/bin/python
yu@librity:~$ which python3
/usr/bin/python3
yu@librity:~$ which python2
/usr/bin/python2
yu@librity:~$

现在看下这些文件的具体信息:

cd /usr/bin

ls -l | grep python

1
2
3
4
5
6
yu@librity:~$ cd /usr/bin
yu@librity:/usr/bin$ ls -l | grep python
lrwxrwxrwx 1 root root 24 9月 14 17:54 python -> /etc/alternatives/python
lrwxrwxrwx 1 root root 9 7月 28 2021 python2 -> python2.7
lrwxrwxrwx 1 root root 10 3月 25 20:41 python3 -> python3.10
yu@librity:/usr/bin$

-> 这个标记的意思就是软连接的意思。可以看到上面的都设置了软连接,python指向了/etc/alternatives/python文件。

注意:我在这里曾经栽了很大的跟头,我在执行python的时候报错:

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
yu@librity:python
Could not find platform independent libraries <prefix>
Could not find platform dependent libraries <exec_prefix>
Consider setting $PYTHONHOME to <prefix>[:<exec_prefix>]
Python path configuration:
PYTHONHOME = (not set)
PYTHONPATH = (not set)
program name = 'python'
isolated = 0
environment = 1
user site = 1
import site = 1
sys._base_executable = '/usr/bin/python'
sys.base_prefix = '/usr'
sys.base_exec_prefix = '/usr'
sys.executable = '/usr/bin/python'
sys.prefix = '/usr'
sys.exec_prefix = '/usr'
sys.path = [
'/usr/lib/python38.zip',
'/usr/lib/python3.8',
'/usr/lib/lib-dynload',
]
Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding
Python runtime state: core initialized
ModuleNotFoundError: No module named 'encodings'

Current thread 0x00007fc19c386740 (most recent call first):
<no Python frame>

报了一堆什么 Could not find 的问题。然后我又无意间执行了这个代码
update-alternatives --install /usr/bin/python python /usr/bin/python2.7 1

1
2
3
yu@librity:~$ update-alternatives --install /usr/bin/python python /usr/bin/python2.7 1
Can not upgrade
Your python install is corrupted. Please fix the '/usr/bin/python' symlink.

明确告诉我我的python软连接损坏了。然后我执行ls -l | grep python 后发现python没有指向任何文件,果然损坏了。
所以解决办法就是我给他添加一个软连接:

1
sudo ln -sf /usr/bin/python2.7 /usr/bin/python

所有的问题都解决了,现在就可以手动切换我们的python版本了:

  1. 执行:查看所有的可以替代的版本,你没配置过肯定没任何输出。
1
update-alternatives --list python 
  1. 添加替代信息
    1
    2
    update-alternatives --install /usr/bin/python python /usr/bin/python2.7 1  
    update-alternatives --install /usr/bin/python python /usr/bin/python3.6 2
  2. 在查看下:
1
2
3
4
5
yu@librity:/usr/bin$ update-alternatives --list python 
/usr/bin/python2.7
/usr/bin/python3
/usr/bin/python3.10
yu@librity:/usr/bin$
  1. 随意替换顺序
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    root@librity:/home/yu# update-alternatives --config python 
    有 3 个候选项可用于替换 python (提供 /usr/bin/python)。

    选择 路径 优先级 状态
    ------------------------------------------------------------
    * 0 /usr/bin/python3.10 2 自动模式
    1 /usr/bin/python2.7 1 手动模式
    2 /usr/bin/python3 2 手动模式
    3 /usr/bin/python3.10 2 手动模式

    要维持当前值[*]请按<回车键>,或者键入选择的编号:0
    输入你要最有优先的序号,比如这里我输入0。那么我在控制台输入python得到的版本将是python3.10的。

全部完成!
博客

Android秘技之JobService的使用详解:

Android秘技之JobService的使用详解:

JobService的作用是在特定条件下才执行后台任务的情景。比如你有个需求,需要监听用户连接上wifi后就自动开启下载任务。JobService是由系统统一管理和调度的,在特定场景下使用他更加灵活和省心。

关于他的api详解:博客

captrue.png

captrue.png

captrue.png

高效加载Bitmap

高效加载Bitmap:

为了防止加载bitmap出现oom的情况,我们一般需要缩小一个bitmap的大小。缩小内存大小有个办法,首先你可以降低图片的采样率。在BitmapFactory.Options里面可以设置参数imSampleSize的值(采样率)。当imSampleSize = 1,表示原图;当imSampleSize = 2,表示宽高都缩小2倍,整体像素为原来的1/4。

现在结合一个实际情况来讲:比如ImageView的大小是100 * 100。而图片原始大小为200 * 200,那么采样率inSampleSize设为2最合适,那代码怎么写呢?

  1. 讲BitmapFactory.Options的inJustDecodeBounds参数设置为true。并加载图片。
  2. 讲BitmapFactory.Options中取出图片的原始宽高,他们对应的是outwidth和outHeight.
  3. 根据采样率的规则结合目标View的所需大小计算出采样率inSampleSize
  4. 将BitmapFactory.Options的inJustDecodeBounds参数设置为fasle,然后重新加载图片。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static Bitmap decodeSampleBitMapFromResource(Resource res,int resId,int reqWidth,int reqHeight){
final Bitmapfactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res,resId,options);
options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResources(res,resId,options);
}
public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if(height > reqHeight || width > reqWidth){
final int halfHeight = height/2;
final int halfWidth = width/2;
while((halfHeight/inSampleSize)>=reqHieght && (halfWidth/inSampleSize)>=reqWidth){
inSampleSize *=2;
}
}
return inSampleSize;
}

有了上面这个方法就很简单了,比图ImageView所期望的图片大小为100*100,那么我们就可以高效的加载了:

1
mImageView.setImageBitmap(decodeSampleBitmapFromResource(getResource(),R.id.bg,100,100));

Ipc

IPC:

进行进程间的通信,对象数据是需要进行序列化的。序列化方法有:

  1. Serializable接口(java提供的):使用简单,但是序列化的开销大,序列化和反序列化需要大量的I/O操作。
  2. Parcelable接口(android提供的):使用麻烦点,但是效率高。

Binder

Binder就是一个类,一个实现接口IBinder接口的类。Binder是ServiceManager连接各种Manager(ActivityManager,WindowManager)和相应ManagerSerivce的桥梁。从Android应用层来说,Binder是客户端和服务端进行通信的媒介。

目前已知,在Android里面Binder主要用在Service中,包括AIDL和Messenger。

所有可以在Binder中传输的接口都需要继承接口IInterface接口。比如在使用AIDL的时候,我们新建如下:

1
2
3
4
5
// IBookManager.aidl
interface IBookManager {
List<String> getBookList();
void addBook(String book);
}

然后你再build一下,会在build文件里面生成类IBookManager.java类:乍一看很复杂其实很简单。

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
108
109
110
111
112
113
114
115
116
117
118
119
120
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.example.android.findme.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.example.android.findme.IBookManager";

/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}

/**
* Cast an IBinder object into an com.example.android.findme.IBookManager interface,
* generating a proxy if needed.
*/
public static com.example.android.findme.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.example.android.findme.IBookManager))) {
return ((com.example.android.findme.IBookManager) iin);
}
return new com.example.android.findme.IBookManager.Stub.Proxy(obj);
}

@Override
public android.os.IBinder asBinder() {
return this;
}

@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(descriptor);
java.util.List<java.lang.String> _result = this.getBookList();
reply.writeNoException();
reply.writeStringList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(descriptor);
java.lang.String _arg0;
_arg0 = data.readString();
this.addBook(_arg0);
reply.writeNoException();
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}

private static class Proxy implements com.example.android.findme.IBookManager {
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;
}

@Override
public java.util.List<java.lang.String> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<java.lang.String> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createStringArrayList();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}

@Override
public void addBook(java.lang.String book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(book);
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}

static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}

public java.util.List<java.lang.String> getBookList() throws android.os.RemoteException;

public void addBook(java.lang.String book) throws android.os.RemoteException;
}

首先他声明了两个方法,getBookList和addBook,很显然就是我们在IBookManager.aidl中声明的方法,然后还声明了两个id,分别对应这两个方法。然后就是申明了一个内部类Stub继承Binder。

从上面的分析看来。我们其实可以不用生命adil文件就可以实现Binder。之所以android提供这个aidl文件,其实是帮助我们生成代码。

**asInterface(IBinder obj)**:
用于将服务端的Binder对象转换成客户端所需要的aidl接口类的对象。如果客户端和服务端属于同一个进程,那么此方法返回的就是服务端的Stub对象本身,否则返回是系统封装后的Stub.proxy对象。

asBinder
此方法返回当前Binder对象。

Binder里面还有两个重要方法:

  1. linkToDeath
  2. unlinkToDeath

在Binder发生断裂的时候死亡的时候,linkToDeth方法会收到回调。让客户端知道服务端是否死亡了。

IPC

使用Bundle

优点:简单易用

缺点:只能传输Bundle支持的数据类型

使用场景:四大组件进行进程间的通信

使用文件共享

缺点:不适合高并发场景,并且无法做到进程间的即时通信

场景:无并发访问情景,交换简单的数据,实时性不高的场景

使用Messenger

优点:功能一般,支持一对多串行通信,支持实时通信

缺点:不能很好的处理高并发,不支持rpc,数据通过message进行传输

是对AIDL的封装,主要用来传递Message。不能直接在服务端调用客户端的方法。Messenger是通过Handler来传递消息的。

使用AIDL

功能强大,支持一对多并发通信,支持实时性

缺点:使用复杂,需要处理好线程同步

  1. Messenger对消息的处理是串行的,不支持并行。这里使用还有个知识点就是使用RemoteCallbackList用于删除进程listener的接口。
  2. 权限验证功能:我们的远程服务并不希望任何人都可以连接。
    实现方式:

1.1 在onBind中进行验证:
首先在AndroidMenifest里面申明权限:

1
2
3
<permission 
android:name="com.tgf.ds.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal"/>

然后在obBind里面进行验证:

1
2
3
4
5
6
7
public onBind(Intent intent){
int chek = checkCallingOrSelfPermission("com.tgf.ds.permission.ACCESS_BOOK_SERVICE");
if(chek == PackageManager.PERMISSION_DENIED){
return null;
}
return mBinder;
}

1.2 在服务端的onTranscat方法进行验证。如果验证失败就直接返回false。

使用ContentProvider

优点:在数据访问方面功能强大,支持一对多并发数据共享,可通过call进行方法扩展操作

缺点:受约束的aidl

他是专门用于不用应用间数据共享的方式。

使用socket

Binder连接池

当有10个aidl需要进行进程间的通信,那应该怎么处理,总不能新建10个服务一次进行绑定吧。我们需要将所有的aidl放在一个service里面去处理。这是实现好的demo:

1
2
3
4
// ICompute.aidl
interface ICompute {
int add(int a,int b);
}
1
2
3
4
5
//ISecurityCenter.aidl
interface ISecurityCenter {
String encrypt(String content);
String decrypy(String password);
}
1
2
3
4
//IBinderPool.aidl
interface IBinderPool {
IBinder queryBinder(int binderCode);
}
1
2
3
4
5
6
7
//ComputeImpl.java
public class ComputeImpl extends ICompute.Stub{
@Override
public int add(int a, int b) throws RemoteException {
return a+b;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//SecurityCenterImpl.java
public class SecurityCenterImpl extends ISecurityCenter.Stub{
private static final char SECREPT_CODE = '^';
@Override
public String encrypt(String content) throws RemoteException {
char[] chars = content.toCharArray();
for(int i=0;i<chars.length;i++){
chars[i]^=SECREPT_CODE;
}
return new String(chars);
}

@Override
public String decrypy(String password) throws RemoteException {
return encrypt(password);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// BinderPoolImpl.java
public class BinderPoolImpl extends IBinderPool.Stub {
@Override
public IBinder queryBinder(int binderCode) throws RemoteException {
IBinder binder = null;
switch (binderCode) {
case BinderPool.BINDER_SECURITY_CENTER:
binder = new SecurityCenterImpl();
break;
case BinderPool.BINDER_COMPUTER:
binder = new ComputeImpl();
break;
default:
break;
}
return binder;
}
}
1
2
3
4
5
6
7
8
9
10
//BinderPoolService.java
public class BinderPoolService extends Service {
private static final String TAG = "BinderPoolService";
private Binder mBinderPool = new BinderPoolImpl();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinderPool;
}
}
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
//BinderPool.java
public class BinderPool {
private static final String TAG = "BinderPool";
public static final int BINDER_NONE = -1;
public static final int BINDER_COMPUTER = 0;
public static final int BINDER_SECURITY_CENTER = 1;

private Context mContext;
private IBinderPool mIBinderPool;
private static volatile BinderPool sInstance;
private CountDownLatch mConnectBinderPoolCountDownLatch;

private BinderPool(Context context) {
mContext = context.getApplicationContext();
connectBinderPoolService();
}

public static BinderPool getInstance(Context context) {
if (sInstance == null) {
synchronized (BinderPool.class) {
if (sInstance == null) {
sInstance = new BinderPool(context);
}
}
}
return sInstance;
}

private synchronized void connectBinderPoolService() {
mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
Intent service = new Intent(mContext, BinderPoolService.class);
mContext.bindService(service, mBinderPoolConnection, Context.BIND_EXTERNAL_SERVICE);
try {
mConnectBinderPoolCountDownLatch.await();
} catch (Exception e) {

}
}

public IBinder queryBinder(int binderCode) {
IBinder iBinder = null;
if (mIBinderPool != null) {
try {
iBinder = mIBinderPool.queryBinder(binderCode);
} catch (RemoteException e) {
e.printStackTrace();
}
}
return iBinder;
}

private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mIBinderPool = IBinderPool.Stub.asInterface(service);
try {
mIBinderPool.asBinder().linkToDeath(mBinderDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
mConnectBinderPoolCountDownLatch.countDown();
}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};

private IBinder.DeathRecipient mBinderDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
mIBinderPool.asBinder().unlinkToDeath(mBinderDeathRecipient, 0);
mIBinderPool = null;
connectBinderPoolService();
}
};
}

在MainActivity.java进行使用:

1
2
3
4
5
6
7
8
9
10
11
fun dowork(){
var binderPool = BinderPool.getInstance(this)
var securityBinder = binderPool.queryBinder(BinderPool.BINDER_SECURITY_CENTER)
var mSecurityCenter = SecurityCenterImpl.asInterface(securityBinder)
var password = mSecurityCenter.encrypt("Hellp=Android")
println("encrty:${password}")
println("dectypt:${mSecurityCenter.decrypy(password)}")
var computeBinder = binderPool.queryBinder(BinderPool.BINDER_COMPUTER)
var mComoute = ComputeImpl.asInterface(computeBinder)
println("3+5=${mComoute.add(3,5)}")
}

Glide中内存缓存分类

Glide内存缓存:
首先你要知道什么时候用到了内存缓存:答案是在:engine.load的时候:当从内存缓存里面找到了可以使用的engineSource时候,就会把当前的engineSource添加到活动缓存里面。

其实内存缓存分为两个:

1. 活动缓存activityReources
2. 内存缓存LruResourceCache。

他们两个缓存的数据不会重复。当系统发生gc的时候,所有的数据会从activityResources里面移除放进LreResourceCache里面。然后当从LruResourceCache里面查到数据的时候会从LruResourceCache里面移除然后添加到activityResources里面。

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
class Engine{
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
}
// 看活动缓存能拿到东西不
EngineResource<?> active = loadFromActiveResources(key);
if (active != null) {
return active;
}
//看内存缓存能拿到数据不
EngineResource<?> cached = loadFromCache(key);
if (cached != null) {
return cached;
}
return null;
}

// 可以看到如果在内存缓存里面找到了engineSource,那么就把engineSource添加到活动缓存里面。
private EngineResource<?> loadFromCache(Key key) {
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}

private EngineResource<?> getEngineResourceFromCache(Key key) {
// 找到了就会从内存缓存里面进行移除
Resource<?> cached = cache.remove(key);

final EngineResource<?> result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
result = (EngineResource<?>) cached;
} else {
result =
new EngineResource<>(
cached, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
}
return result;
}
}

内存缓存也就是LruResourceCache,需要我们进行深度学习一下:LruResourceCahce里面用到了LinkedHashMap。为什么不用hashMap呢。因为hashMap是无序的,在Glide的内存缓存里面,需要在数据达到限制的时候去删除最近最少使用的数据,那我们既然要知道数据是不是最近最少使用的话,就要使用到Linkedhashmap的特性。他通过牺牲空间和时间维护了一个双向链表来保证迭代顺序。

来看下LruResourceCached的put操作:

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
// LruCache.java
public synchronized Y put(@NonNull T key, @Nullable Y item) {
final int itemSize = getSize(item);
if (itemSize >= maxSize) {
onItemEvicted(key, item);
return null;
}

if (item != null) {
currentSize += itemSize;
}
@Nullable Entry<Y> old = cache.put(key, item == null ? null : new Entry<>(item, itemSize));
if (old != null) {
currentSize -= old.size;
if (!old.value.equals(item)) {
onItemEvicted(key, old.value);
}
}
evict();

return old != null ? old.value : null;
}
private void evict() {
trimToSize(maxSize);
}
// 超过缓存限制的时候删除最近最少使用的节点。
protected synchronized void trimToSize(long size) {
Map.Entry<T, Entry<Y>> last;
Iterator<Map.Entry<T, Entry<Y>>> cacheIterator;
while (currentSize > size) {
cacheIterator = cache.entrySet().iterator();
last = cacheIterator.next();
final Entry<Y> toRemove = last.getValue();
currentSize -= toRemove.size;
final T key = last.getKey();
cacheIterator.remove();
onItemEvicted(key, toRemove.value);
}
}
  1. 过大的图片不被允许进行缓存。
  2. 每一个EngineResource会被包装在Entry里面

总结:
活动缓存他是通过弱引用的方式将EngeineResource保存在了HashMap里面。这个容量是没有限制的。然后从活动缓存里面移除的数据会被保存在内存缓存也就是LruCachedResource里面。这个里面可以动态设置一个maxSize值用来限制他的大小,当超过这个值的时候他是采用删除最近最少使用的方式来维持缓存大小。

Glide中的活跃活动缓存理解

Glide活跃活动缓存:activeResources:ActiveResources
活跃缓存他是没有大小限制的,有本事的话你可以无限往里面塞数据。来看下他的类:

1
2
3
4
final class ActiveResources {
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();
}

数据是被保存到activeEngineResources这个HashMap里面。这个map是没容量限制的。我们可以看到他的key是一个key对象,他的键值是一个弱引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {

final Key key;
final boolean isCacheable;
Resource<?> resource;

ResourceWeakReference(
@NonNull Key key,
@NonNull EngineResource<?> referent,
@NonNull ReferenceQueue<? super EngineResource<?>> queue,
boolean isActiveResourceRetentionAllowed) {
super(referent, queue);
this.key = Preconditions.checkNotNull(key);
this.resource =
referent.isMemoryCacheable() && isActiveResourceRetentionAllowed
? Preconditions.checkNotNull(referent.getResource())
: null;
isCacheable = referent.isMemoryCacheable();
}
}
}

为什么要用弱引用呢。我们知道弱引用有一个特性。在jvm触发垃圾回收的时候会回收掉弱引用。这也就是为什么glide在活动缓存里面没有设置容量大小限制的原因,因为他很容易就被全部干掉。

现在来看下活动缓存是怎么塞数据的:

1
2
3
4
5
6
7
8
9
10
11
//ActiveResouces.java

synchronized void activate(Key key, EngineResource<?> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);

ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) { removed.reset();
}
}

在塞数据的时候,如果存在同名的key,那么就会把上一个键值给重置掉。

这里还需要说一点,Glide监听了gc操作,那他是怎么监听的呢,就是通过弱引用里的ReferenceQueue来监听的:

1
2
3
4
5
6
7
8
9
10
11
// ActiveResources.java
void cleanReferenceQueue() {
while (!isShutdown) {
try {
ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
cleanupActiveReference(ref);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}

开启了个线程无限循环,用来监听ResourceQueue的移除操作。当触发了gc就会,我们的ResourceQueue.remove就会收到反馈,然后删除了hasmap里面的值,然后如果我们开启了内存缓存,这个时候从活动缓存里面移除的数据会被添加到内存缓存里面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//ActiveResources.java
void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
synchronized (this) {
activeEngineResources.remove(ref.key);

if (!ref.isCacheable || ref.resource == null) {
return;
}
}
EngineResource<?> newResource =
new EngineResource<>(
ref.resource, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ false, ref.key, listener);
listener.onResourceReleased(ref.key, newResource);
}

// Engine.java
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
activeResources.deactivate(cacheKey);
if (resource.isMemoryCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource, /*forceNextFrame=*/ false);
}
}

内存缓存的知识后面再讲,现在需要知道的是,活动缓存里面的数据会被添加到内存缓存里面。

Recycleview的onMeasure过程重看

今天我又打开了Recycleview源码进行阅读。这次我深度分析了RecycleviewonMeasure方法。RecycleviewonMeasure方法里面干了很多事情,如果你的Recycleview是自动测量(LinearLayout默认就开启了自动测量),如果宽高是确定的那么他不会在onMeasure阶段进行预布局,如果是wrap_content的话,那他还要进行预布局在onMeasure阶段就要去测量孩子的大小然后在设置Recycleview的具体宽高值,这样会导致在onMeasure阶段花费更多的时间。

想要读懂这个方法我们需要深度了解void onMeasure(int widthSpec,int heightSpec)里面widthSpec和heightSpec参数值的由来。

**widthSpec 和 heightSpec**值的由来:
他是ViewGroup根据自己的长宽值然后再根据孩子的长宽值,父视图在来设定孩子你最终的长宽是多少。这里讲的有点抽像,现在根据案例还讲解:
这里我拿Linearlayout来举例:
1
2
3
4
5
6
7
8
9
10
<LinearLayout
android:layout_width="100dp"
android:layout_height="100dp"
android:orientation="vertical">

<View
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff5566" />
</LinearLayout>
![截图 2022-10-12 10-47-26.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a373f225da4d4b008ad6e0fd12bcfba7~tplv-k3u1fbpfcp-watermark.image?)

这一看是不是有点纳闷,我的View长宽都设置的wrap_content,那怎么在LinearLayout里面却铺满了整个LinearLayout。只有一个实现办法:LinearLayout发现孩子的宽高是wrap_content的时候,LinearLayout就强制将孩子的宽高设定成了自己的宽高值。也就是上面提到的widthSpec和heightSpec值。现在我们来看下LinearLayout的源码:

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
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);

int size = Math.max(0, specSize - padding);

int resultSize = 0;
int resultMode = 0;

switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

源码里面明确写了,当孩子是wrap_content的时候,就将孩子的宽高设置为自己的宽高值。

现在继续看回RecycleviewonMeasure方法。

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
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
// LinearLayoutManager默认是开启的
if (mLayout.isAutoMeasureEnabled()) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
// 先根据默认的设置recycleview的宽高
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

final boolean measureSpecModeIsExactly =
widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
// 判断当前的recycleview的宽高设置的是不是具体值,是的话就不用进行预加载的过程
// 预加载过程就是提前执行layout过程,然后计算所需要的宽高,然后在设置给recycleiew
if (measureSpecModeIsExactly || mAdapter == null) {
return;
}

if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
// 这里就是默认设置父视图规定在没有具体设置宽高值的情况下的值
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
// 进行预备加载 加载完就能知道全部孩子需要的宽高值
dispatchLayoutStep2();

// 这里就已经拿到了孩子全部的具体宽高值,然后在设置给recycleview
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

// if RecyclerView has non-exact width and height and if there is at least one child
// which also has non-exact width & height, we have to re-measure.
if (mLayout.shouldMeasureTwice()) {
mLayout.setMeasureSpecs(
MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
mState.mIsMeasuring = true;
dispatchLayoutStep2();
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
}
}

看完Recycleview的测量过程,我认为你能够设置具体值的就最好设置具体值,不然在onMeasure厘面还要在走一比那layout过程。

这里再稍微讲下layout过程,为什么要讲呢,因为有一部分设计到了上面onMeasure的过程,当时困扰了我挺久:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
return;
}
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {// 3
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}

前面都很容易看懂,问题就在我标记的3处。

1
mLayout.getHeight() != getHeight()

现在看下mLayout.getHeight()

1
2
3
4
//LayoutManager.java
public int getHeight() {
return mHeight;
}

那这个mHeight是什么时候赋值的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// LayoutManager.java
void setMeasureSpecs(int wSpec, int hSpec) {
mWidth = MeasureSpec.getSize(wSpec);
mWidthMode = MeasureSpec.getMode(wSpec);
if (mWidthMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) {
mWidth = 0;
}

mHeight = MeasureSpec.getSize(hSpec);
mHeightMode = MeasureSpec.getMode(hSpec);
if (mHeightMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) {
mHeight = 0;
}
}

可以看到在调用方法setMeasureSpecs方法时候赋值的,那这个方法又是什么时候调用的:答案在onMeasuere方法的时候,再问具体点就是在还没有进行预加载之前进行的:可以看到标记3的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
if (mLayout.isAutoMeasureEnabled()) {
//3
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
dispatchLayoutStep2();
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
}

现在mLayout.getHeight搞懂了。我们再来看下getHeight()方法:

1
2
3
public final int getHeight() {
return mBottom - mTop;
}

这个getHeight是通过mBottom - mTop来获取的。其实就是我们经常获取控件高度的方式。
那现在是不是有疑问了,mLayout.getHeight获取的值也是通过测量高度设置进行的,然后getHeight()获取的值就是控件的高度那为什么这两个值会存在不相等的情况呢?

不想等的问题就出现在onMeasure里面进行了预加载处理。在预加载前给recycleview设置了他的默认宽高。但是在预加载结束后,根据具体的孩子数量算出的高度值才是recycleview真实的宽高值。然后mBottom的值在layout阶段才设置进去的。所以在layout阶段获取到的recycleview的值和在onMeasure里面预加载阶段前获取到的宽高值会存在不同的情况。

为什么说scroll方法移动的是他的孩子?

在思考mScrollX,mScrollY这两个参数对View的绘制会产生什么样的影响的时候,我看了这么个代码:

1
2
3
4
5
6
7
class  View{
private void drawBackground(Canvas canvas) {
canvas.translate(mScrollX, mScrollX);
background.draw(canvas);
canvas.translate(-mScrollX, -mScrollX);
}
}

看方法名字很容易知道这是绘制背景的。但是我们在实战中会发现调用TextView.scrollBy(10f,10f)的时候,只有TextView的内容移动了位置,但是我们给TextView设置的背景并没有产生移动。带着这个疑问你看源码,在调用background.draw(canvas)之前不是调用了canvas.translate(mScrollX, mScrollX)对画布进行了移动吗??????为啥我们的背景确并没有进行移动!!!!!!!!!

哈哈,你既然看到了这里,想必你也产生了和我同样的问题。现在就带着问题来看源码,为什么我们的背景没有进行移动。

首先对整个View的绘制流程进行源码解析:

ViewRootImpl触发draw(canvas)方法。

draw(canvas)分四部走。

    1. drawBackground(canvas) : 绘制背景
    1. onDraw(canvas) : 绘制自己
    1. dispatchDraw(canvas) : 绘制孩子
    1. 绘制bar(忽略不管)

这里我先模拟这么个层级:

1
2
3
4
<Framelayout>
<ImageView>
</ImageView>
</Framelayout>

我来分析下:首先ViewRootImpl调用了FrameLayoutdraw(canvas)方法。然后FrameLayout调用了drawBackground绘制了FrameLayout的背景。然后调用了FrameLayoutonDraw(canvas)方法。我们知道自定义ViewGroup的时候很少几乎不会重写onDraw(canvas)方法。只有自定义View的时候才会重写。然后接着调用了FrameLayoutdispatchDraw(canvas)方法。在这个里面会遍历所有的孩子调用child.draw(canvas, this, drawingTime)可以看到到了这里就走进了子孩,在这里也就是ImageViewdraw(canvas, this, drawingTime)方法。然后在这个draw(canvas, this, drawingTime)方法里面会调用孩子的draw(canvas)方法。


注意重点来了:我们知道绘制背景的代码是在draw(canvas)这个方法里调用的。现在你想下在绘制背景前会调用canvas.translate(mScrollX, mScrollX)但是最终的现象是调用了这个方法,我们的背景并没有偏移。既然没有偏移,我们是不是可以猜想我们的画布在调用偏移前已经提前偏移过了一次,这次绘制背景前的偏移其实和之之前的偏移只是进行了一个抵消操作,所以我们的背景没有移动。哈哈,其实就是这么个过程,我们刚才分析了ViewGroupdispatchDraw(canvas)方法。他调用了child.draw(canvas, this, drawingTime)也就是这个方法draw(canvas, this, drawingTime)先于背景的偏移前进行了一次偏移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
int sx = 0;
int sy = 0;
if (!drawingWithRenderNode) {
computeScroll();
sx = mScrollX;
sy = mScrollY;
}
final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
final boolean offsetForScroll = cache == null && !drawingWithRenderNode;
if (offsetForScroll) {
canvas.translate(mLeft - sx, mTop - sy);
}
draw(canvas);
return more;
}

看在倒数第四行调用了canvas.translate()对视图已经进行了偏移。

到此为什么我们的背景不会移动已经讲清楚了。

线程和线程池的理解

线程有个异常叫InterruptedException,这个异常是在调用方法interrupt方法触发的。不是每次调用这个方法都能触发这个异常,条件是:

  1. 线程处于阻塞态
  2. 线程处于限期等待
  3. 线程处于无限期等待

不能中断I/O阻塞和synchroized锁阻塞。还有就是一个线程你调用了无限循环,这个时候你调用interrupt方法也是没办法使线程提前结束的。但是你可以使用interrupt给线程设上的标记用来停止线程。比如这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class InterruptExample {

private static class MyThread2 extends Thread {
@Override
public void run() {
while (!interrupted()) {
// ..
}
System.out.println("Thread end");
}
}
}

线程池的中断操作:
调用shutDown()方法会阻止接收新的任务,但是会继续执行已经提交的任务。但是调用shutDownNow方法,则相当于调用线程的interrupt方法。


使用锁有synchronizedReentrantLock,两者区别是RenntrantLock可以中断,还是公平锁。

线程池

是否新建线程的流程:

graph TD
提交任务 ==> 核心线程池是否已经满 
核心线程池是否已经满 ==否==> 创建线程执行任务
核心线程池是否已经满 ==是==> 队列是否已经满了
队列是否已经满了 ==否==> 将任务存储在队列里面
队列是否已经满了 ==是==> 线程池是否已经满了
线程池是否已经满了 ==否==> 创建线程执行任务
线程池是否已经满了 ==是==> 按照策略处理无法执行的任务

线程池可以设定线程的空闲的存活时间keepAliveTime,当一个线程空闲下来后,超过keepAliveTime时间,线程池会判断当前运行的线程数量是否大于核心线程数量,如果超过了,那么空闲的线程就会被停掉。所以所有任务都完成后,线程池里面的线程数都会收缩到核心线程数大小。注意:当设置了allowCoreThreadTimeOut不关心线程存活时间,那么在线程空闲下来后,不会判读空闲线程是否超过核心线程数就直接被停掉。所以最终线程数=0

线程池里面的BlockingQueue有几种往队列加数据的方法:

抛出异常 特殊值 阻塞 超时
插入 add offer put offer(e,time.unit)
移除 remove poll take poll(time,unit)
检测 element peek 不可用 不可用
  • 抛出异常:当队列满了,插入的新值插不进去了就会立马抛出一个异常。
  • 特殊值:当插入数据失败,返回一个boolean值,true成功,false失败。然后拿SynchronousQueue调用这个方法的时候,必须有另一个线程执行了取值的操作,这个offer才会返回true。
  • 阻塞:一个线程插入一个数据的时候,必须等到另一个线程执行取数据的操作,不然就一直阻塞当前线程。
  • 超时:插入数据失败在阻塞一个规定的时间。

关于使用SynchronousQueue.offer演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SynchronousQueue<String> queue = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println("取值" + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("put 1");
System.out.println( "结果:"+queue.offer("hahah"));
System.out.println("hahaha");
}).start();

运行这个代码你可以发现,当没调用take这个取值的操作,我们offer返回的结果一直都是false(这个现象只针对SynchronousQueue阻塞队列)。


讲一下我对线程池实现的思路分析,线程池里面有个核心线程,非核心线程和任务存储的数据结构。为什么会出现核心线程这个东西,因为线程的开启和关闭开销是很大的,所以在有很多任务需要执行的时候,我们应该有一个办法来实现线程的重复使用。降低线程的频繁创建和关闭的次数。核心线程就是这个作用,核心线程是不会停止的,用阻塞的方式实现线程不会停止,然后当核心线程都在运行的时候,就把任务添加到我们的任务存储的数据结构里面,核心线程一直从任务存储的数据结构里面拿去新任务。当任务存储结构满了,就需要开辟临时线程了,临时线程我们可以给他设置过期时间,当处于空闲后超过过期时间那么这个临时线程就可以停止了。

现在看下基于上面的思路是怎么实现的:

现在来分析ThreadPoolExecutor线程池源码:

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
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//1 创建我们的核心线程数量
if (workerCountOf(c) < corePoolSize) {
// addWorker的返回值用来判断新的任务是否添加成功
if (addWorker(command, true))
return;
c = ctl.get();
}
/**
*2 调用offer他是不会阻塞线程的,这个方法的返回值要看具体的实现类。
* 比如拿SyncronousQueue来说,这个队列比较特殊,他不存储任何数据,所以如果你只
* 调用offer,他的返回值只会是false。只有当你在其他线程调用了workQueue.take或者poll。这里才会返回true。
* workQueue.offer返回值的含义只代表任务是否添加进队列。添加不进去就代表需要开辟非核心线程了。
**/
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//3 线程池第一次提交任务并且线程数小于核心线程数的时候走的是这里。
else if (!addWorker(command, false))
reject(command);
}

现在进入只要的addWorker方法:

// core:是核心线程的话就为true 否则为false
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
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
/**
*这里有个双循环,外层的循环是判断线程池是否是可运行状态
*第二层循环判断线程数量是否已经超过线程数量设定。
**/
for (int c = ctl.get();;) {
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false;

for (;;) {
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get();
if (runStateAtLeast(c, SHUTDOWN))
continue retry;
}
}

boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 这里创建了Worker对象,你查看worker的构造函数,
// 你会发现在构造函数里面创建了线程thread。
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int c = ctl.get();

if (isRunning(c) ||
(runStateLessThan(c, STOP) && firstTask == null)) {
if (t.getState() != Thread.State.NEW)
throw new IllegalThreadStateException();
workers.add(w);
workerAdded = true;
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
// 开启了线程 这里很关键,哈哈。你第一次看到这里的时候一定蒙蔽,
// 这里直接调用了thread.start.那核心线程是怎么一直运行下去的还不会停止?
// 下面我会带你看worker类
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}

这里很有必要看下我们的Worker类:

可以看Worker类继承了Runable,然后在构造函数里面创建了线程Thread并且将自己传了进去自己,一个worker就是一个任务。所以在调用Thread.start的时候,会调用到Workerrun方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
}

runWorker方法里面实现了核心线程的不停运行:

可以看到在runWorker方法里面有一个while循环,有了while循环我们就知道了,如果getTask方法返回的值如果不是null,那么这个while循环就会一直为true,那我们的线程不就一直运行下去了吗。哈哈

1
2
3
4
5
6
7
8
9
10
final void runWorker(Worker w) {
Runnable task = w.firstTask;
try {
while (task != null || (task = getTask()) != null) {

}
} finally {
processWorkerExit(w, completedAbruptly);
}
}

现在来看下getTask方法:

在下面我标记1和2的位置,1的位置用来判断当前线程数是否大于核心线程数:

  1. 小于的话代表当前线程是核心线程。那么就调用workQueue.take()方法。(在最上面我写了take方法是一个阻塞方法,他会阻塞当前的线程直到其他线程调用了put或者offer方法)。这也就实现了核心线程的永不死亡。
  2. 如果不是核心线程就调用workQueue.poll(timeOut,TIME_TYPE)。这个方法实现了延迟线程的死亡。timeOut的值是我们在创建线程池的时候自己设定的。所以非核心线程是会死亡的。
    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
    private Runnable getTask() {
    boolean timedOut = false;

    for (;;) {
    int c = ctl.get();
    if (runStateAtLeast(c, SHUTDOWN)
    && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
    decrementWorkerCount();
    return null;
    }

    int wc = workerCountOf(c);
    //1
    boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
    if ((wc > maximumPoolSize || (timed && timedOut))
    && (wc > 1 || workQueue.isEmpty())) {
    if (compareAndDecrementWorkerCount(c))
    return null;
    continue;
    }

    //2 这里是关键。实现了核心线程不死,非核心线程可以死亡。
    try {
    Runnable r = timed ?
    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
    workQueue.take();
    if (r != null)
    return r;
    timedOut = true;
    } catch (InterruptedException retry) {
    timedOut = false;
    }
    }
    }

至此已经分析完了,核心线程不会死亡的原因。

在看了整个任务添加流程(addWork)方法。我们可以看到这个方法返回的是一个boolean变量。用来判读任务添加失败和成功。那什么情况会失败呢?

线程池有五种状态:他们数值依次变大

int COUNT__BITS = Interger.SIZE - 3

  • RUNNING (int RUNNIT = -1 << COUNT_BITS)
  • SHUTDOWN (int SHUTDOWN = 0 << COUNT_BITS)
  • STOP (int STOP = 1 << COUNT_BITS)
  • TIDYING (int TIDYING = 2 << COUNT_BITS)
  • TERMINATED (int TERMINATED = 3 << COUNT_BITS)
  1. addWorker方法false的情况:
  • 线程池当前状态 大于等于SHUTDOWN状态 并且 线程池当前状态大于等于STOP状态 或者 是任务不等于null 或者 队列为空 就返回false。返回false就是拒绝的意思。
1
2
3
4
5
6
7
8
9
10
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (int c = ctl.get();;) {
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false;//拒绝新任务的添加
}
}
  1. 线程数超过设置的最大线程数,就拒绝。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (int c = ctl.get();;) {
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false;//拒绝新任务的添加
}
for (;;) {
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
// 超过了设置的最大线程数量就返回false
return false;
}
}
  1. 当线程池处于SHUTDOWN状态并且队列里面已经存在之前已经添加的但还没执行的相同的任务。
1
2
3
4
5
6
7
8
if (! isRunning(recheck) && remove(command))
reject(command);

public boolean remove(Runnable task) {
boolean removed = workQueue.remove(task);
tryTerminate(); // In case SHUTDOWN and now empty
return removed;
}
  1. 事物存储的数据结构存不下新的事物的时候,然后非核心线程也到了上限值。
1
2
3
4
5
6
7
8
9
10
// offer:往队列里面添加数据,添加数据失败则返回false
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}// 队列里面添加事物失败才会走这个判断
else if (!addWorker(command, false))
reject(command);

这里讲到了拒绝,那就在分析下线程池的拒绝策略:

java官方实现的拒绝策略有四种:

classDiagram
RejectedExecutionHandler <|-- abortpolicy rejectedexecutionhandler <|-- callerrunspolicy discardoldestpolicy discardpolicy rejectedexecutionhandler: +regectedexecution()< pre>
  1. AbortPolicy终止策略:直接抛出异常RejectedxecutionException
  2. CallerRunsPolicy调用者运行策略:除非线程池现在处于shutDown状态,那么被拒绝的任务将由调用者线程自己处理。
  3. DiscardOldestPolicy如果线程池没shutDown,就丢弃队列里面最久没执行的事物,然后将当前任务添加进线程池里面。
  4. DiscardPolicy默默丢弃任务无任何感知。

当然我们可以继承自RejectedExecutionHandler实现recectedExecution方法。


shutDown和shutDownNow的区别:

  • shutDown中断空前的workers。没有任何返回。
  • showDownNow中断所有的workders,并且返回所有的任务队列里面的任务

java提供了几个默认的线程池:

  1. newCachedThreadPool:该线程池可以无限扩展,当需求增加时会自动添加新的线程,当需求降低时,会自动回收空闲线程。通常用来执行许多短期异步任务的程序性能,
    1
    2
    3
    4
    5
    public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
    60L, TimeUnit.SECONDS,
    new SynchronousQueue<Runnable>());
    }
  2. newScheduledThreadPool创建一个以延迟或者定时来执行任务的线程池,工作队列为DelayedWorkQueue.适用于需要多个后台线程执行的周期性任务。
  3. newSingleThreadExecutor只有一个线程的线程池,用来执行需要要保证顺序的任务场景。

一篇挺好的分析线程池源码的文章:文章1


创建线程的几种方式:

  1. 继承thread
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class MyThread extends Thread{//继承Thread类
      public void run(){
      //重写run方法
      }
    }

    public class Main {
      public static void main(String[] args){
        new MyThread().start();//创建并启动线程
      }

    }
  2. 继承Runable
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class MyRunable implements Runnable {//实现Runnable接口
      public void run(){
      //重写run方法
      }
    }

    public class Main {
      public static void main(String[] args){
        //创建并启动线程
        MyRunable myRunable=new MyRunable();
        Thread thread=new Thread(myRunable);
        thread().start();
      }
  3. 使用Callable和FutureTask来创建
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    FutureTask futureTask = new FutureTask(new Callable() {
    @Override
    public Object call() throws Exception {
    Thread.sleep(1000);
    System.out.println("执行任务");
    return "线程里的返回值";
    }
    });
    Thread thread = new Thread(futureTask);
    thread.start();
    System.out.println("==:" + futureTask.get());
    System.out.println("结束了");
    futureTask.get方法会阻塞当前正在运行的线程.
  4. 使用线程池:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    ExecutorService mFixedPool = Executors.newFixedThreadPool(4);
    Future future = mFixedPool.submit(new Callable<Object>() {
    @Override
    public Object call() throws Exception {
    System.out.println("在子线程执行任务");
    return "我是返回值";
    }
    });
    try {
    System.out.println("拿到返回值:"+future.get());
    } catch (InterruptedException e) {
    e.printStackTrace();
    } catch (ExecutionException e) {
    e.printStackTrace();
    }
    System.out.println("一切都结束了");
    mFixedPool.shutdown();

实现等待所有线程结束在继续运行的实现有如下几种方式:

  1. 使用CountDownLatch
  2. 使用join

比如main线程想等thread1线程结束了,main在结束,那么在main线程只需要调用thread1.join就行.

关于线程状态控制的方法的记忆要这么记忆:
第一组: wait / notify / notifyAll
第二组: join / sleep / yield

join / sleep / yield : 他们会自动到达某个条件就苏醒过来.而 wait 不会,他需要你触发 notify notifyAll 才可以. 那 sleep 和 wait 的区别是,sleep他会阻塞当前线程,并且不会释放对象锁,而wait让当前线程处于等待状态,并且释放对象锁.

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
public class SleepDemo {
private static final Object lock = new Object();

public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 1 acquired lock");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1 released lock");
}
});

Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 2 acquired lock");
}
});

t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
//上面代码的运行效果是,t2一直运行不起来,因为t1拿到了对象锁,并且调用了sleep导致锁不能被释放,所以t2也没办法运行下去.

Zygote进程为什么可以创建子进程,然后自己还一直存活着。

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

首先简单分析下Zygote进程的启动流程:

init.cpp会去读取init.rc内容,在init.rc可以看到这么一句话:

import /system/etc/init/hw/init.${ro.zygote}.rc 。这个就是通过系统版本加载对应的init.zygote64.rc和init.zygote32.rc两个文件。

然后就会加载类app_main.cpp,在app_main.cpp的main函数里面可以看到去创建了Zygote进程。并且进程名字就叫zygote。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static const char ZYGOTE_NICE_NAME[] = "zygote";
//....省略部分代码
if (!niceName.isEmpty()) {
runtime.setArgv0(niceName.string(), true /* setProcName */);//设置进程名字
}

if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
app_usage();
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
}

Zygote创建子进程的方式是通过fork函数来创建,这里我们需要搞懂父进程和子进程的关系。

父进程&子进程

子进程拥有父进程的全部资源包括代码。那你可能要问了,那子进程从代码哪个地方开始执行?答案是。从fork那个位置开始执行,fork之前的代码子进程不会去执行,他只会执行fork这行代码下面的代码。fork会返回一个pid。这个pid=0代表这是一个子进程,pid<0代表子进程创建失败,pid>0代表是父进程。我们就是通过这个pid的值来判断某些功能让谁来做。这里你可能又有问题了,那父进程在fork之前创建的对象比如Student,那在子进程里面,这个在fork之前创建的对象,他的指向地址和父进程是一样的吗?答案告诉你,是一样的。你在子进程打印出的Student对象地址和父进程打印的Student地址是一样的。那我在给你提个问题,那既然一样,我在子进程修改Student.name值,那父进程里面在获取这个name的值,是子进程修改的值吗?答案是:不是的。子进程修改值不会影响到父进程。因为在子进程那个地址是一个虚拟地址,真正指向的物理地址并不是那个。


在我第一次看ZygoteInit.java这个类的时候,我一直不明白为什么zygote进程能够一直存活,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] argv) {
if (startSystemServer) {
Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
if (r != null) {
r.run();
return;
}
}
caller = zygoteServer.runSelectLoop(abiList);
} catch (Throwable ex) {
Log.e(TAG, "System zygote died with fatal exception", ex);
throw ex;
} finally {
if (zygoteServer != null) {
zygoteServer.closeServerSocket();
}
}

if (caller != null) {
caller.run();
}
}

比如创建systemServer进程的时候,当创建完成后,这个runable一定是不为null的。所以就会执行r.run方法。然后执行return。这里都return了。那为什么zygote进程还是活着的,不是应该死掉了吗。

来看下forkSystemServer方法:

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
private static Runnable forkSystemServer(String abiList, String socketName,
ZygoteServer zygoteServer) {
ZygoteArguments parsedArgs;
int pid;
try {
pid = Zygote.forkSystemServer(
parsedArgs.mUid, parsedArgs.mGid,
parsedArgs.mGids,
parsedArgs.mRuntimeFlags,
null,
parsedArgs.mPermittedCapabilities,
parsedArgs.mEffectiveCapabilities);
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}

/* For child process */
if (pid == 0) {
if (hasSecondZygote(abiList)) {
waitForSecondaryZygote(socketName);
}

zygoteServer.closeServerSocket();
return handleSystemServerProcess(parsedArgs);
}

return null;
}

哈哈你看懂了吗。我们可以看到里面执行了Zygote.forkSystemServer方法去创建了子进程。然后当pid=0的时候去创建了Runbale对象,当pid不是0的时候,返回null。现在在回到ZygoteInit.java里面:

1
2
3
4
5
6
7
8
9
public static void main(String[] argv) {
if (startSystemServer) {
Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
if (r != null) {
r.run();
return;
}
}
}

可以看到当是主进程的时候,这个r是等于null的,所以主进程不会走进return。所以主进程这个时候也不会死。同理你可以去看runSelectLoop方法,他里面有个while的死循环,这就让这个zygote进程可以一直活着了。
想搞懂zygote进程不会死的原因,你一定要先搞懂父进程和子进程的原理和执行逻辑。