- 浏览: 23771 次
- 性别:
- 来自: 北京
最新评论
ThreadLocal提供了一种线程操作数据的思想。以前我以为ThreadLocal的实现就是有一个以线程的name为关键字的HashMap,每次请求数据的时候在这个HashMap中查找数据。今天读完代码,我才明白原来是用一种更巧妙的方法实现的。
下面我们就分析一下ThreadLocal的源码实现,
首先我们先看一下ThreadLocal的相关成员变量以及构造函数:
/** Weak reference to this thread local instance. */
private final Reference<ThreadLocal<T>> reference = new WeakReference<ThreadLocal<T>>(this);
在这里可以将reference理解为关键字,下面我们可以看到reference的用处
private static AtomicInteger hashCounter = new AtomicInteger(0);
private final int hash = hashCounter.getAndAdd(0x61c88647 * 2);
hash用于计算hash值,hash值就是为了计算索引地址
构造函数如下:
public ThreadLocal() {}
ThreadLocal的构造函数时一个空函数。
在ThreadLocal类中有一个static的内部类Values. Values类提供了存储和索引具体的值的逻辑
我们也来看一下Values的成员变量以及构造函数:
private static final int INITIAL_SIZE = 16;
初始的存放数据的数组的大小
private static final Object TOMBSTONE = new Object();
TOMBSTONE是一个占位符,表示一个墓碑
private Object[] table;
保存数据的数组
private int mask;
用于计算索引值的掩码
private int size;
size表示有效的数据的个数
private int tombstones;
tombstones译为墓碑,表示索引值的数据无效,但是曾经被使用过的数据的个数
private int maximumLoad;
maximumLoad表示最大的装载数,也就是size+tombstones的最大值
private int clean;
clean记录了下次执行cleanup操作的起始位置
Values只提供了默认构造函数,实现如下:
Values() {
initializeTable(INITIAL_SIZE);
this.size = 0;
this.tombstones = 0;
}
private void initializeTable(int capacity) {
this.table = new Object[capacity * 2];
this.mask = table.length - 1;
this.clean = 0;
this.maximumLoad = capacity * 2 / 3; // 2/3
}
构造函数只是提供了成员变量的初始化。在实现中我们看到,将mask掩码初始化为table的长度-1, 这样在计算索引值时,保证索引值落在table里面。将maximumLoad初始化为table的长度的2/3.
(1)set函数
接下来我们看一下ThreadLocal的set函数的实现,set函数将值设置到线程中去
public void set(T value) {
//得到当前的运行线程
Thread currentThread = Thread.currentThread();
//获得运行线程的Values值
Values values = values(currentThread);
//如果values值为空,则说明还没有为该线程设置过值,这时需要初始化一个空的values对象
if (values == null) {
values = initializeValues(currentThread);
}
//设置值
values.put(this, value);
}
values函数的实现如下:
Values values(Thread current) {
return current.localValues;
}
我们可以看到,values函数就是返回了线程的localValues成员变量。
在Thread类中,存在ThreadLocal.Values localValues的成员变量,有着包访问权限。从这里我们可以看出,ThreadLocal的值是绑定到各个线程中去的,而不是在ThreadLocal里统一管理。
initializeValues函数的实现也很简单,如下:
Values initializeValues(Thread current) {
return current.localValues = new Values();
}
只是简单的初始化Thread的localValues成员变量new一个新Values对象。
接下来我们分析一下Values类的put函数的实现:
void put(ThreadLocal<?> key, Object value){
//一上来就执行cleanUp操作
cleanUp();
//记录第一个墓碑的位置
int firstTombstone = -1;
//遍历表
for (int index = key.hash & mask;; index = next(index)) {
//得到关键字
Object k = table[index];
//查找到关键字,则替换值
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
//如果当前位置的关键字是空,说明查找到一个没有使用过的位置,这时候就必须将该值保存
if (k == null) {
//firstTombstone == -1, 说明在查找过程中没有遇到墓碑,则将该值保存在这个null位置
if (firstTombstone == -1) {
// Fill in null slot.
//保存key和value
table[index] = key.reference;
table[index + 1] = value;
//有效的数据个数加1
size++;
return;
}
//firstTombstone != -1,说明查找过程中遇到过墓碑,则要将该值替换遇到的第一个墓碑
//保存key和value
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
//墓碑的个数减1
tombstones--;
//有效的个数加1
size++;
return;
}
//保存第一个墓碑的位置
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
从put的实现逻辑中,我们看到Values是想一个位置保存key,在这个位置的下一个位置处保存value。这就要求table的长度必须为2的整数倍,并且计算索引的步长为2。
我们从next函数的视线中验证了这一点:
private int next(int index) {
return (index + 2) & mask;
}
我们接下来看一下cleanUp函数的实现逻辑:
private void cleanUp() {
if (rehash()) {
//如果重新hash了table,则没有必要在进行cleanUp操作
return;
}
//size = 0, 表示还没有保存有效的数据
if (size == 0) {
// No live entries == nothing to clean.
return;
}
// 从上次执行cleanUp结束时的索引值开始
int index = clean;
Object[] table = this.table;
//遍历table
for (int counter = table.length; counter > 0; counter >>= 1, index = next(index)) {
//得到key
Object k = table[index];
if (k == TOMBSTONE || k == null) {
continue; // on to next entry
}
@SuppressWarnings("unchecked")
Reference<ThreadLocal<?>> reference = (Reference<ThreadLocal<?>>) k;
//如果为null,表示此对象已经被回收
if (reference.get() == null) {
//将该位置设置成墓碑,并将值设置为null
table[index] = TOMBSTONE;
table[index + 1] = null;
//增加墓碑的数目和减少有效的对象数目
tombstones++;
size--;
}
}
//保存下次cleanUp的其实索引值
clean = index;
}
从源码中可以看出,cleanUp主要清理了被垃圾回收器回收的对象。这样能保证每次取数据和设置数据时索引位置的有效性。
我们看一下rehash的函数实现:
private boolean rehash() {
//如果没有达到可装载的最大值,则不用重新hash,直接返回false
if (tombstones + size < maximumLoad) {
return false;
}
int capacity = table.length >> 1;
int newCapacity = capacity;
//如果一半的容量都被有效的值所占据,则将容量扩大一倍。因为table是将关键字和值保存在相邻的位置上,所以这里其实比较的是table数组的长度的1/4
if (size > (capacity >> 1)) {
newCapacity = capacity * 2;
}
Object[] oldTable = this.table;
//重新分配一个新的table数组
initializeTable(newCapacity);
this.tombstones = 0;
//如果有效的值的个数为0, 则没必要进行新旧table的赋值
if (size == 0) {
return true;
}
//进行新旧table的赋值
for (int i = oldTable.length - 2; i >= 0; i -= 2) {
Object k = oldTable[i];
if (k == null || k == TOMBSTONE) {
// Skip this entry.
continue;
}
// The table can only contain null, tombstones and references.
@SuppressWarnings("unchecked")
Reference<ThreadLocal<?>> reference = (Reference<ThreadLocal<?>>) k;
ThreadLocal<?> key = reference.get();
if (key != null) {
//该值是有效的,则加入到新的table中
add(key, oldTable[i + 1]);
} else {
//该关键字对象已经被回收
size--;
}
}
return true;
}
add函数是将值赋值到table数组中。在遍历table的过程中,忽略掉当前索引位置的关键字是墓碑的情况,保证插入到null的位置
void add(ThreadLocal<?> key, Object value) {
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == null) {
table[index] = key.reference;
table[index + 1] = value;
return;
}
}
}
(2)get()函数
get()函数负责取出已经保存的值。实现如下:
public T get() {
//得到当前线程的Values对象
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
//计算索引值
int index = hash & values.mask;
//如果还没有被垃圾回收
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
//为当前线程初始化values成员
values = initializeValues(currentThread);
}
//在table里没有查找到
return (T) values.getAfterMiss(this);
}
我们看一下getAfterMiss的实现:
Object getAfterMiss(ThreadLocal<?> key) {
//保存当前的数据table
Object[] table = this.table;
//计算索引值
int index = key.hash & mask;
//如果当前关键字为null, 则说明没有保存过该值,则没有继续查找下去的必要
if (table[index] == null) {
//初始化一个值
Object value = key.initialValue();
//如果在initialValue过程中,没有改变table
if (this.table == table && table[index] == null) {
//将关键字和值 保存
table[index] = key.reference;
table[index + 1] = value;
size++;
//执行一次cleanUp操作
cleanUp();
return value;
}
//在initialValue的过程中,改变了table
//put到table中去
put(key, value);
return value;
}
int firstTombstone = -1;
//继续查找
for (index = next(index);; index = next(index)) {
Object reference = table[index];
//如果关键字相等
if (reference == key.reference) {
return table[index + 1];
}
//如果当前索引的关键字为null,则表明当前位置还没有被使用过,则没有必要在查找下去
if (reference == null) {
//初始化一个值
Object value = key.initialValue();
//如果在initialValue过程中,没有改变table
if (this.table == table) {
//如果我们曾经遇到过墓碑,并且现在那个位置还是个墓碑,则将值赋值到第一个墓碑的位置
if (firstTombstone > -1 && table[firstTombstone] == TOMBSTONE) {
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
// No need to clean up here. We aren't filling
// in a null slot.
return value;
}
//当前索引的位置还是null,则赋值到当前的位置
if (table[index] == null) {
table[index] = key.reference;
table[index + 1] = value;
size++;
cleanUp();
return value;
}
}
//在initialValue的过程中,改变了table
put(key, value);
return value;
}
//记录第一个墓碑的位置
if (firstTombstone == -1 && reference == TOMBSTONE) {
firstTombstone = index;
}
}
}
在ThreadLocal的实现中,initialValue只是简单的返回一个null,等待子类去实现。
protected T initialValue() {
return null;
}
至此,我们分析了set和get函数的实现。
ThreadLocal还实现了remove的逻辑,我们接下来来分析一下remove的实现
(3)remove()函数
remove函数的源码如下:
public void remove() {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
values.remove(this);
}
}
得到当前线程对象的values成员之后,调用Values的remove函数即可。Values的remove函数实现如下:
void remove(ThreadLocal<?> key) {
cleanUp();
for (int index = key.hash & mask;; index = next(index)) {
Object reference = table[index];
if (reference == key.reference) {
//如果关键字相等,则将该位置标记为墓碑。
table[index] = TOMBSTONE;
table[index + 1] = null;
tombstones++;
size--;
return;
}
if (reference == null) {
// No entry found.
return;
}
}
}
至此,我们完整的分析了ThreadLocal的实现。我们思考一下问题:
为什么要使用一个叫墓碑的占位符呢?
我们知道在table数组中,每一个位置可以存放的对象要么等于null,要么是一个要保存的值。我们只要在按顺序遍历table,总会找到一个位置可以保存数据。如果要删除一个数据,只要将该位置设置为null。这样当查找到一个位置为null的时候,就可以知道这个位置肯定是没有被使用的位置,因此也就没有必要再查找下去了。那为什么还有多引入一个墓碑的对象呢?看似这个墓碑是多余的,其实不然!因为计算每个线程的索引值的时候,每个线程的索引值其实是固定的,每次都会映射到相同的位置。这样有了墓碑这个占位符对象之后,能保证别的对象不会使用这个位置,相当于将这个位置保护起来,以便留给特定的线程去使用。
从ThreadLocal的实现中,我们总结到,通过ThreadLocal.set()到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。
下面我们就分析一下ThreadLocal的源码实现,
首先我们先看一下ThreadLocal的相关成员变量以及构造函数:
/** Weak reference to this thread local instance. */
private final Reference<ThreadLocal<T>> reference = new WeakReference<ThreadLocal<T>>(this);
在这里可以将reference理解为关键字,下面我们可以看到reference的用处
private static AtomicInteger hashCounter = new AtomicInteger(0);
private final int hash = hashCounter.getAndAdd(0x61c88647 * 2);
hash用于计算hash值,hash值就是为了计算索引地址
构造函数如下:
public ThreadLocal() {}
ThreadLocal的构造函数时一个空函数。
在ThreadLocal类中有一个static的内部类Values. Values类提供了存储和索引具体的值的逻辑
我们也来看一下Values的成员变量以及构造函数:
private static final int INITIAL_SIZE = 16;
初始的存放数据的数组的大小
private static final Object TOMBSTONE = new Object();
TOMBSTONE是一个占位符,表示一个墓碑
private Object[] table;
保存数据的数组
private int mask;
用于计算索引值的掩码
private int size;
size表示有效的数据的个数
private int tombstones;
tombstones译为墓碑,表示索引值的数据无效,但是曾经被使用过的数据的个数
private int maximumLoad;
maximumLoad表示最大的装载数,也就是size+tombstones的最大值
private int clean;
clean记录了下次执行cleanup操作的起始位置
Values只提供了默认构造函数,实现如下:
Values() {
initializeTable(INITIAL_SIZE);
this.size = 0;
this.tombstones = 0;
}
private void initializeTable(int capacity) {
this.table = new Object[capacity * 2];
this.mask = table.length - 1;
this.clean = 0;
this.maximumLoad = capacity * 2 / 3; // 2/3
}
构造函数只是提供了成员变量的初始化。在实现中我们看到,将mask掩码初始化为table的长度-1, 这样在计算索引值时,保证索引值落在table里面。将maximumLoad初始化为table的长度的2/3.
(1)set函数
接下来我们看一下ThreadLocal的set函数的实现,set函数将值设置到线程中去
public void set(T value) {
//得到当前的运行线程
Thread currentThread = Thread.currentThread();
//获得运行线程的Values值
Values values = values(currentThread);
//如果values值为空,则说明还没有为该线程设置过值,这时需要初始化一个空的values对象
if (values == null) {
values = initializeValues(currentThread);
}
//设置值
values.put(this, value);
}
values函数的实现如下:
Values values(Thread current) {
return current.localValues;
}
我们可以看到,values函数就是返回了线程的localValues成员变量。
在Thread类中,存在ThreadLocal.Values localValues的成员变量,有着包访问权限。从这里我们可以看出,ThreadLocal的值是绑定到各个线程中去的,而不是在ThreadLocal里统一管理。
initializeValues函数的实现也很简单,如下:
Values initializeValues(Thread current) {
return current.localValues = new Values();
}
只是简单的初始化Thread的localValues成员变量new一个新Values对象。
接下来我们分析一下Values类的put函数的实现:
void put(ThreadLocal<?> key, Object value){
//一上来就执行cleanUp操作
cleanUp();
//记录第一个墓碑的位置
int firstTombstone = -1;
//遍历表
for (int index = key.hash & mask;; index = next(index)) {
//得到关键字
Object k = table[index];
//查找到关键字,则替换值
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
//如果当前位置的关键字是空,说明查找到一个没有使用过的位置,这时候就必须将该值保存
if (k == null) {
//firstTombstone == -1, 说明在查找过程中没有遇到墓碑,则将该值保存在这个null位置
if (firstTombstone == -1) {
// Fill in null slot.
//保存key和value
table[index] = key.reference;
table[index + 1] = value;
//有效的数据个数加1
size++;
return;
}
//firstTombstone != -1,说明查找过程中遇到过墓碑,则要将该值替换遇到的第一个墓碑
//保存key和value
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
//墓碑的个数减1
tombstones--;
//有效的个数加1
size++;
return;
}
//保存第一个墓碑的位置
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
从put的实现逻辑中,我们看到Values是想一个位置保存key,在这个位置的下一个位置处保存value。这就要求table的长度必须为2的整数倍,并且计算索引的步长为2。
我们从next函数的视线中验证了这一点:
private int next(int index) {
return (index + 2) & mask;
}
我们接下来看一下cleanUp函数的实现逻辑:
private void cleanUp() {
if (rehash()) {
//如果重新hash了table,则没有必要在进行cleanUp操作
return;
}
//size = 0, 表示还没有保存有效的数据
if (size == 0) {
// No live entries == nothing to clean.
return;
}
// 从上次执行cleanUp结束时的索引值开始
int index = clean;
Object[] table = this.table;
//遍历table
for (int counter = table.length; counter > 0; counter >>= 1, index = next(index)) {
//得到key
Object k = table[index];
if (k == TOMBSTONE || k == null) {
continue; // on to next entry
}
@SuppressWarnings("unchecked")
Reference<ThreadLocal<?>> reference = (Reference<ThreadLocal<?>>) k;
//如果为null,表示此对象已经被回收
if (reference.get() == null) {
//将该位置设置成墓碑,并将值设置为null
table[index] = TOMBSTONE;
table[index + 1] = null;
//增加墓碑的数目和减少有效的对象数目
tombstones++;
size--;
}
}
//保存下次cleanUp的其实索引值
clean = index;
}
从源码中可以看出,cleanUp主要清理了被垃圾回收器回收的对象。这样能保证每次取数据和设置数据时索引位置的有效性。
我们看一下rehash的函数实现:
private boolean rehash() {
//如果没有达到可装载的最大值,则不用重新hash,直接返回false
if (tombstones + size < maximumLoad) {
return false;
}
int capacity = table.length >> 1;
int newCapacity = capacity;
//如果一半的容量都被有效的值所占据,则将容量扩大一倍。因为table是将关键字和值保存在相邻的位置上,所以这里其实比较的是table数组的长度的1/4
if (size > (capacity >> 1)) {
newCapacity = capacity * 2;
}
Object[] oldTable = this.table;
//重新分配一个新的table数组
initializeTable(newCapacity);
this.tombstones = 0;
//如果有效的值的个数为0, 则没必要进行新旧table的赋值
if (size == 0) {
return true;
}
//进行新旧table的赋值
for (int i = oldTable.length - 2; i >= 0; i -= 2) {
Object k = oldTable[i];
if (k == null || k == TOMBSTONE) {
// Skip this entry.
continue;
}
// The table can only contain null, tombstones and references.
@SuppressWarnings("unchecked")
Reference<ThreadLocal<?>> reference = (Reference<ThreadLocal<?>>) k;
ThreadLocal<?> key = reference.get();
if (key != null) {
//该值是有效的,则加入到新的table中
add(key, oldTable[i + 1]);
} else {
//该关键字对象已经被回收
size--;
}
}
return true;
}
add函数是将值赋值到table数组中。在遍历table的过程中,忽略掉当前索引位置的关键字是墓碑的情况,保证插入到null的位置
void add(ThreadLocal<?> key, Object value) {
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == null) {
table[index] = key.reference;
table[index + 1] = value;
return;
}
}
}
(2)get()函数
get()函数负责取出已经保存的值。实现如下:
public T get() {
//得到当前线程的Values对象
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
//计算索引值
int index = hash & values.mask;
//如果还没有被垃圾回收
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
//为当前线程初始化values成员
values = initializeValues(currentThread);
}
//在table里没有查找到
return (T) values.getAfterMiss(this);
}
我们看一下getAfterMiss的实现:
Object getAfterMiss(ThreadLocal<?> key) {
//保存当前的数据table
Object[] table = this.table;
//计算索引值
int index = key.hash & mask;
//如果当前关键字为null, 则说明没有保存过该值,则没有继续查找下去的必要
if (table[index] == null) {
//初始化一个值
Object value = key.initialValue();
//如果在initialValue过程中,没有改变table
if (this.table == table && table[index] == null) {
//将关键字和值 保存
table[index] = key.reference;
table[index + 1] = value;
size++;
//执行一次cleanUp操作
cleanUp();
return value;
}
//在initialValue的过程中,改变了table
//put到table中去
put(key, value);
return value;
}
int firstTombstone = -1;
//继续查找
for (index = next(index);; index = next(index)) {
Object reference = table[index];
//如果关键字相等
if (reference == key.reference) {
return table[index + 1];
}
//如果当前索引的关键字为null,则表明当前位置还没有被使用过,则没有必要在查找下去
if (reference == null) {
//初始化一个值
Object value = key.initialValue();
//如果在initialValue过程中,没有改变table
if (this.table == table) {
//如果我们曾经遇到过墓碑,并且现在那个位置还是个墓碑,则将值赋值到第一个墓碑的位置
if (firstTombstone > -1 && table[firstTombstone] == TOMBSTONE) {
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
// No need to clean up here. We aren't filling
// in a null slot.
return value;
}
//当前索引的位置还是null,则赋值到当前的位置
if (table[index] == null) {
table[index] = key.reference;
table[index + 1] = value;
size++;
cleanUp();
return value;
}
}
//在initialValue的过程中,改变了table
put(key, value);
return value;
}
//记录第一个墓碑的位置
if (firstTombstone == -1 && reference == TOMBSTONE) {
firstTombstone = index;
}
}
}
在ThreadLocal的实现中,initialValue只是简单的返回一个null,等待子类去实现。
protected T initialValue() {
return null;
}
至此,我们分析了set和get函数的实现。
ThreadLocal还实现了remove的逻辑,我们接下来来分析一下remove的实现
(3)remove()函数
remove函数的源码如下:
public void remove() {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
values.remove(this);
}
}
得到当前线程对象的values成员之后,调用Values的remove函数即可。Values的remove函数实现如下:
void remove(ThreadLocal<?> key) {
cleanUp();
for (int index = key.hash & mask;; index = next(index)) {
Object reference = table[index];
if (reference == key.reference) {
//如果关键字相等,则将该位置标记为墓碑。
table[index] = TOMBSTONE;
table[index + 1] = null;
tombstones++;
size--;
return;
}
if (reference == null) {
// No entry found.
return;
}
}
}
至此,我们完整的分析了ThreadLocal的实现。我们思考一下问题:
为什么要使用一个叫墓碑的占位符呢?
我们知道在table数组中,每一个位置可以存放的对象要么等于null,要么是一个要保存的值。我们只要在按顺序遍历table,总会找到一个位置可以保存数据。如果要删除一个数据,只要将该位置设置为null。这样当查找到一个位置为null的时候,就可以知道这个位置肯定是没有被使用的位置,因此也就没有必要再查找下去了。那为什么还有多引入一个墓碑的对象呢?看似这个墓碑是多余的,其实不然!因为计算每个线程的索引值的时候,每个线程的索引值其实是固定的,每次都会映射到相同的位置。这样有了墓碑这个占位符对象之后,能保证别的对象不会使用这个位置,相当于将这个位置保护起来,以便留给特定的线程去使用。
从ThreadLocal的实现中,我们总结到,通过ThreadLocal.set()到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。
发表评论
-
Activity与WindowManagerService连接的过程(三)
2018-04-16 16:27 622page11 WindowManagerService ... -
Activity与WindowManagerService连接的过程(二)
2018-04-16 16:36 763page6 WindowManagerGlobal的getW ... -
Activity与WindowManagerService连接的过程(一)
2018-04-16 16:21 984page1 Activity组件在 ... -
Activity的ViewRoot的创建过程(三)
2017-11-06 14:25 739page7 在这篇文章里, 我们分析一下W类的构造过程. W ... -
Activity的ViewRoot的创建过程(二)
2017-11-06 14:29 937page4 我们看一下ViewRootImpl对象的创 ... -
Activity的ViewRoot的创建过程(一)
2017-11-06 14:27 1078page1 当一个Activity第一次激活的时候会为该Ac ... -
Activity的Window和WindowManager的创建过程(三)
2017-07-05 11:49 1334page9 在这里我们分析一下DisplayManager的 ... -
Activity的Window和WindowManager的创建过程(二)
2017-07-05 11:31 543page5 在这篇文章中, 我们分析一下ContextImp ... -
Activity的Window和WindowManager的创建过程(一)
2017-07-05 11:27 605page1 我们开始分析一下Activity的Window和 ... -
Acitivy创建Context的过程(二)
2017-06-21 14:11 512page4 在这里我们分析一下ContextImpl的ini ... -
Acitivy创建Context的过程(一)
2017-06-21 14:15 635page1 从本篇文章开始,我们分析一下Activity创建 ... -
应用程序进程与SurfaceFlinger的连接过程
2017-06-21 11:49 1056我们从SurfaceComposerClient对象的创建开始 ... -
Android源码之SurfaceFlinger的启动(三)
2017-04-20 11:09 1041page11 我们来看一下SurfaceFlinger ... -
Android源码之SurfaceFlinger的启动(二)
2017-04-18 15:15 870page6 我们看一下Thread的run函数的实现: ... -
Android源码之SurfaceFlinger的启动(一)
2017-04-17 10:07 994page1 在Android系统中, 显示系统在底层是通过S ... -
Android源码之Zygote
2015-12-15 11:45 517当ActivityManagerService启动一个应用程序 ... -
Android源码之Binder(五)
2015-12-04 09:19 1508Service组件在启动时,需要将自己注册到Service M ... -
Android源码之Binder(四)
2015-12-04 09:18 1923case BINDER_SET_MAX_THREADS: ... -
Android源码之Binder(三)
2015-12-04 09:17 909{ int ret; struct binder_pr ... -
Android源码之Binder(二)
2015-12-04 09:15 546分析完Binder驱动程序的打开和内存分配的过程之后,我们看一 ...
相关推荐
Android应用源码Android平台下通过HTTP协议实现断点续传下载.rar Android应用源码Hibernate4Android.rar Android应用源码http、udp、tcp网络交互组件.rar Android应用源码ListView实现的目录树结构.rar Android应用...
Android应用源码11套安卓源码合集: Android Gps日志记录程序源码.rar Android listview 滑动删除(具体效果360手机卫士后台通知).rar Android MP3播放器,带卡拉OK字幕.rar Android “遇见”android应用源码.rar ...
android应用源码QQ_UI之分类菜单.rar android应用源码QQ的登录界面.rar android应用源码Socket的客户端通信.rar android应用源码tooxin图信美图.rar android应用源码yannihui(音乐播放器).rar android应用源码Zirco-...
源码下载对于深入理解Android系统的工作原理、进行定制化开发或优化应用程序至关重要。在本文中,我们将探讨如何下载Android 9.0(28 r6)的源码,以及这个过程涉及的关键知识点。 首先,要下载Android源码,你需要...
源码编译对于开发者来说是一项基础且至关重要的工作,它涉及到理解系统内部运作,自定义修改以及构建自己的Android ROM。`.ipr`和`.iml`文件是IntelliJ IDEA(Android Studio是其专为Android开发的分支)项目配置...
Android源码设计模式解析与实战.PDF(完整版),文件中有百度网盘下载链接。
1 Android PDF 阅读器源码 2 Android Txt文本阅读器源码 3 Android SqliteManager 源码 4 Android 个人记账程序源码 5 android 多点触控实例源码 ...40 android源码分享之带手势划动功能的日历源码
Android 源码源码在线访问,覆盖各版本,包括最新的Android L. 无需setup git, 无需费时下载。
在不同操作系统下编译Android源码需要更改一些Android源码的配置项,脚本用于自动化更改配置项.zip 在不同操作系统下编译Android源码需要更改一些Android源码的配置项,脚本用于自动化更改配置项.zip 在不同操作系统...
《深入剖析Android Chromium浏览器源码》 Android Chromium是一款开源的浏览器项目,它是Google Chrome浏览器在Android平台上的核心组件。此源码提供了深入了解Android应用程序开发、Web浏览技术以及Chrome浏览器...
Android 源码 android.iml文件,移去了无用的模块方便Android studio快速导入,欢迎下载
在分析Android源码的过程中,一些有效的工具和方法可以极大地提高我们的工作效率,确保我们能够快速准确地定位到我们感兴趣的部分。这里,我们主要介绍两种工具:Eclipse和Android Studio IDE,以及Android SDK ...
WordPress for Android源码基于Android 4.0(Ice Cream Sandwich)进行开发,这表明项目在设计之初就考虑到了兼容性和性能优化。源码主要分为以下几个关键模块: 1. 数据访问层:这部分代码处理与WordPress REST ...
《深入剖析Android开源中国社区应用源码》 在IT领域,源码分析是提升技能、理解软件设计原理的重要途径。开源中国社区的Android版源码为我们提供了一个宝贵的实践平台,让我们能够深入研究一个完整的社交应用是如何...
Android源码 炫酷的3D音乐播放器 各种特效OpenGLAndroid源码 炫酷的3D音乐播放器 各种特效OpenGLAndroid源码 炫酷的3D音乐播放器 各种特效OpenGLAndroid源码 炫酷的3D音乐播放器 各种特效OpenGLAndroid源码 炫酷的3D...
《Android Studio项目源码解析与学习指南》 在Android应用开发的世界中,Android Studio作为官方推荐的集成开发环境(IDE),已经成为开发者们的首选工具。本文将深入探讨"50款Android studio项目源码.zip"这一资源...
在Android开发领域,深入理解Android源码是提升技术能力的关键步骤。"android源码大全"这个资源包无疑为开发者提供了一个宝贵的自学平台,涵盖了几乎所有的主流Android资源文件和源码。接下来,我们将深入探讨这个...
android源码下载步骤