Omooo 6 years ago
parent 18c8cb70a0
commit de0b32e905
  1. 38
      blogs/Activity.md
  2. 0
      blogs/Android/Animator.md
  3. 25
      blogs/Android/AsyncTask.md
  4. 30
      blogs/Android/Bundle.md
  5. 376
      blogs/Android/ContextProvider.md
  6. 0
      blogs/Android/Dalvik \ ART.md
  7. 172
      blogs/Android/HandlerThread.md
  8. 194
      blogs/Android/IntentService.md
  9. 0
      blogs/Android/RecyclerView.md
  10. 0
      blogs/Android/SQLite.md
  11. 0
      blogs/Android/SharedPreferences.md
  12. 0
      blogs/Android/WebView.md
  13. 0
      blogs/Android/埋点/AOP_AspectJ.md
  14. 0
      blogs/Android/埋点/埋点.md
  15. 0
      blogs/Android/埋点/埋点之代理 View.OnClickListener.md
  16. 0
      blogs/Android/埋点/埋点之应用前后台判断.md
  17. 18
      blogs/Android/屏幕适配.md
  18. 0
      blogs/Android/性能优化/I\O 优化.md
  19. 40
      blogs/Android/热修复.md
  20. 0
      blogs/Java/String.md
  21. 0
      blogs/Java/final.md
  22. 0
      blogs/Java/反射.md
  23. 0
      blogs/Java/泛型.md
  24. 4
      blogs/Java/注解.md
  25. 0
      images/Android/AOP_AspectJ.png
  26. 0
      images/Android/Android 虚拟机.png
  27. 0
      images/Android/Animator.png
  28. BIN
      images/Android/ContentProvider.png
  29. 0
      images/Android/Context 类层次.png
  30. 0
      images/Android/Context.png
  31. 0
      images/Android/DeceAcceInterpolator.gif
  32. 0
      images/Android/I\O 流程.png
  33. 0
      images/Android/I\O 请求流程.png
  34. 0
      images/Android/Linux I\O 模型.png
  35. 0
      images/Android/RecyclerView.png
  36. 0
      images/Android/SharedPreferences.png
  37. 0
      images/Android/SnapView.png
  38. 0
      images/Android/TypeEvaluator.gif
  39. 0
      images/Android/WebView.png
  40. 0
      images/Java/String.png
  41. 0
      images/Java/compact_after.png
  42. 0
      images/Java/compact_before.png
  43. 0
      images/Java/反射.png
  44. 0
      images/Java/泛型.png

@ -1,38 +0,0 @@
---
Activity
---
#### 目录
1. 思维导图
2. 基本使用
- Activity 启动以及生命周期
- Activity 启动
- 显式启动
- 隐式启动
- 生命周期
- 正常生命周期
- 异常生命周期
- 横竖屏切换
- 异常退出
- 启动模式
- standard
- singleTop
- singleTask
- singleInstance
- 常用回调函数
- setResult / onActivityResult
- onNewIntent
- onConfigurationChanged
- onSaveInstanceState / onRestoreInstanceState
- onTrimMemory
- 通信交互
- 与 Activity 通信
- 与 Fragment 通信
- 与 Service 通信
3. 高级玩法
- 根 Activity 启动流程

@ -0,0 +1,25 @@
---
AsyncTask
---
#### 目录
1. 思维导图
2. 概述
3. 具体使用
4. 源码分析
5. 参考
#### 思维导图
#### 概述
AsyncTask 是一个轻量级的异步任务类、抽象泛型类。
#### 具体使用
#### 源码分析
#### 参考
[译文:Android中糟糕的AsyncTask](https://droidyue.com/blog/2014/11/08/bad-smell-of-asynctask-in-android/)

@ -7,6 +7,9 @@ Bundle、ArrayMap、SparseArray
1. 思维导图
2. Bundle
3. ArrayMap
- 概述
- 源码分析
- 注意事项
4. SparseArray
5. 参考
@ -25,18 +28,41 @@ Android 为什么要设计出 Bundle 而不是直接使用 HashMap 来进行数
#### ArrayMap
ArrayMap 的内部实现是两个数组,一个 int 数组存储 key 的哈希值,一个对象数组保存 key 和 value,内部使用二分法对 key 进行排序,所以在添加、删除、查找数据的时候,都会使用二分法查找,适合小数据量操作,如果在数据量比较大的情况下,那么它的性能将退化。而 HashMap 内部则是数组+链表结构,所以在数据量较小的时候,HashMap 的 Entry Array 比 ArrayMap 占用更多的内存。同时,ArrayMap 避免了自动装箱。
##### 概述
ArrayMap 的内部实现是两个数组,一个 int 数组存储 key 的哈希值,一个对象数组保存 key 和 value,内部使用二分法对 key 进行排序,所以在添加、删除、查找数据的时候,都会使用二分法查找,适合小数据量操作,如果在数据量比较大的情况下,那么它的性能将退化。而 HashMap 内部则是数组+链表结构,所以在数据量较小的时候,HashMap 的 Entry Array 比 ArrayMap 占用更多的内存。
在 Android 中,建议用 ArrayMap 来替换 HashMap。
##### 源码分析
```java
public class SimpleArrayMap<K, V> {
int[] mHashes; //由 key 的 hashCode 所组成的数组,从小到大排序
Object[] mArray; //由 key-value 对所组成的数据,是 mHashes 大小的两倍
int mSize; //
}
```
![](http://gityuan.com/images/arraymap/arrayMap.jpg)
其中 mSize 记录着该 ArrayMap 对象中有多少对数据,执行 put 或者 append 操作,则 mSize 会加一,执行 remove,mSize 则减一。mSize 往往小于 mHashes.length,如果大于或等于,则说明需要扩容。
更多源码分析可以参考:[深度解读ArrayMap优势与缺陷](http://gityuan.com/2019/01/13/arraymap/)
#### SparseArray
SparseArray 和 ArrayMap 类似,但是 SparseArray 只能存储 key 为 int 型的,它比 ArrayMap 少了计算 key 的哈希值,建议用 SparseArray\<V> 替换 HashMap\<Integer,V>。
SparseArray 和 ArrayMap 类似,但是 SparseArray 只能存储 key 为 int 型的,它比 ArrayMap 少了计算 key 的哈希值,同时对象数组只需要存 value 即可,这也就避免了 key 的装箱操作和分配空间,,建议用 SparseArray\<V> 替换 HashMap\<Integer,V>。
类似的还有 SparseIntArray 代替 HashMap\<Integer,Integer> 等。
![](http://gityuan.com/images/arraymap/SparseArray.jpg)
#### 参考
[Android为什么要设计出Bundle而不是直接使用HashMap来进行数据传递](https://github.com/ZhaoKaiQiang/AndroidDifficultAnalysis/blob/master/02.Android%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E8%AE%BE%E8%AE%A1%E5%87%BABundle%E8%80%8C%E4%B8%8D%E6%98%AF%E7%9B%B4%E6%8E%A5%E4%BD%BF%E7%94%A8HashMap%E6%9D%A5%E8%BF%9B%E8%A1%8C%E6%95%B0%E6%8D%AE%E4%BC%A0%E9%80%92.md)
[深度解读ArrayMap优势与缺陷](http://gityuan.com/2019/01/13/arraymap/)
[如何通过 ArrayMap 和 SparseArray 优化 Android App](https://github.com/xitu/gold-miner/blob/master/TODO/android-app-optimization-using-arraymap-and-sparsearray.md)

@ -0,0 +1,376 @@
---
ContentProvider
---
#### 目录
1. 思维导图
2. 概述
3. 统一资源标识符(URI)
4. MIME 数据类型
5. ContentProvider
- 组织数据方式
- 主要方法
6. 辅助工具类
- ContentResolver
- ContentUris
- UriMatcher
- ContentObserver
7. 实例
- 获取手机通讯录信息
- 结合 SQLite
8. 参考
#### 思维导图
![](https://i.loli.net/2019/02/05/5c597c4f25157.png)
#### 概述
ContextProvider 为存储和获取数据提供了统一的接口,可以在不同的应用程序之间安全的共享数据。它允许把自己的应用数据根据需求开放给其他应用进行增删改查。数据的存储方式还是之前的方式,它只是提供了一个统一的接口去访问数据。
#### 统一资源标识符
统一资源标识符即 URI,用来唯一标识 ContentProvider 其中的数据,外界进程通过 URI 找到对应的 ContentProvider 其中的数据,在进行数据操作。
URI 分为系统预置和自定义,分别对应系统内置的数据(如通讯录等)和自定义数据库。
##### 系统内置 URI
比如获取通讯录信息所需要的 URI:ContactsContract.CommonDataKinds.Phone.CONTENT_URI。
##### 自定义 URI
```java
格式:content://authority/path/id
authority:授权信息,用以区分不同的 ContentProvider
path:表名,用以区分 ContentProvider 中不同的数据表
id: ID号,用以区别表中的不同数据
示例:content://com.example.omooo.demoproject/User/1
上述 URI 指向的资源是:名为 com.example.omooo.demoproject 的 ContentProvider 中表名为 User 中 id 为 1 的数据。
```
注意,URI 也存在匹配通配符:* & #
#### MIME 数据类型
它是用来指定某个扩展名的文件用某种应用程序来打开。
可以通过 ContentProvider.getType(uri) 来获得。
每种 MIME 类型由两部分组成:类型 + 子类型。
示例:text/html、application/pdf
#### ContentProvider
##### 组织数据方式
ContentProvider 主要以表格的形式组织数据,同时也支持文件数据,只是表格形式用的比较多,每个表格中包含多张表,每张表包含行和列,分别对应数据。
##### 主要方法
```java
public class MyProvider extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}
```
#### 辅助工具类
##### ContentResolver
统一管理不同的 ContentProvider 间的操作。
1. 即通过 URI 即可操作不同的 ContentProvider 中的数据
2. 外部进程通过 ContentResolver 类从而与 ContentProvider 类进行交互
一般来说,一款应用要使用多个 ContentProvider,若需要了解每个 ContentProvider 的不同实现从而在完成数据交互,操作成本高且难度大,所以在 ContentProvider 类上多加一个 ContentResolver 类对所有的 ContentProvider 进行统一管理。
ContentResolver 类提供了与 ContentProvider 类相同名字和作用的四个方法:
```java
// 外部进程向 ContentProvider 中添加数据
public Uri insert(Uri uri, ContentValues values) 
// 外部进程 删除 ContentProvider 中的数据
public int delete(Uri uri, String selection, String[] selectionArgs)
// 外部进程更新 ContentProvider 中的数据
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) 
// 外部应用 获取 ContentProvider 中的数据
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
```
```java
// 使用ContentResolver前,需要先获取ContentResolver
// 可通过在所有继承Context的类中 通过调用getContentResolver()来获得ContentResolver
ContentResolver resolver = getContentResolver();
// 设置ContentProvider的URI
Uri uri = Uri.parse("content://cn.scu.myprovider/user");
// 根据URI 操作 ContentProvider中的数据
// 此处是获取ContentProvider中 user表的所有记录
Cursor cursor = resolver.query(uri, null, null, null, "userid desc");
```
##### ContentUris
用来操作 URI 的,常用有两个方法:
```java
// withAppendedId()作用:向URI追加一个id
Uri uri = Uri.parse("content://cn.scu.myprovider/user")
Uri resultUri = ContentUris.withAppendedId(uri, 7);
// 最终生成后的Uri为:content://cn.scu.myprovider/user/7
// parseId()作用:从URL中获取ID
Uri uri = Uri.parse("content://cn.scu.myprovider/user/7")
long personid = ContentUris.parseId(uri);
//获取的结果为:7
```
##### UriMatcher
在 ContentProvider 中注册 URI,根据 URI 匹配 ContentProvider 中对应的数据表。
```java
// 步骤1:初始化UriMatcher对象
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
//常量UriMatcher.NO_MATCH = 不匹配任何路径的返回码
// 即初始化时不匹配任何东西
// 步骤2:在ContentProvider 中注册URI(addURI())
int URI_CODE_a = 1;
int URI_CODE_b = 2;
matcher.addURI("cn.scu.myprovider", "user1", URI_CODE_a);
matcher.addURI("cn.scu.myprovider", "user2", URI_CODE_b);
// 若URI资源路径 = content://cn.scu.myprovider/user1 ,则返回注册码URI_CODE_a
// 若URI资源路径 = content://cn.scu.myprovider/user2 ,则返回注册码URI_CODE_b
// 步骤3:根据URI 匹配 URI_CODE,从而匹配ContentProvider中相应的资源(match())
@Override
public String getType(Uri uri) {
Uri uri = Uri.parse(" content://cn.scu.myprovider/user1");
switch(matcher.match(uri)){
// 根据URI匹配的返回码是URI_CODE_a
// 即matcher.match(uri) == URI_CODE_a
case URI_CODE_a:
return tableNameUser1;
// 如果根据URI匹配的返回码是URI_CODE_a,则返回ContentProvider中的名为tableNameUser1的表
case URI_CODE_b:
return tableNameUser2;
// 如果根据URI匹配的返回码是URI_CODE_b,则返回ContentProvider中的名为tableNameUser2的表
}
}
```
##### ContentObserver
内容观察者,当 ContentProvider 中的数据发生变化时,就会触发 ContentObserver 类。
```java
// 步骤1:注册内容观察者ContentObserver
getContentResolver().registerContentObserver(uri);
// 通过ContentResolver类进行注册,并指定需要观察的URI
// 步骤2:当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
public class UserContentProvider extends ContentProvider {
public Uri insert(Uri uri, ContentValues values) {
db.insert("user", "userid", values);
getContext().getContentResolver().notifyChange(uri, null);
// 通知访问者
}
}
// 步骤3:解除观察者
getContentResolver().unregisterContentObserver(uri);
// 同样需要通过ContentResolver类进行解除
```
#### 实例
##### 获取通讯录信息
这里就不需要自己写 ContentProvider 的实现了,用系统已经给的 URI。
```java
/**
* 获取通讯录信息
*/
private void getContactsInfo() {
Cursor cursor = getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null
);
if (cursor != null) {
while (cursor.moveToNext()) {
//联系人姓名
String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
//联系人手机号
String phoneNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
Log.i(TAG, "getContactsInfo: name: " + name + " phone: " + phoneNumber);
}
cursor.close();
}
}
```
##### 结合 SQLite
1. 创建数据库
2. 自定义 ContentProvider 并注册
3. 进程内访问数据
创建数据库:
```java
public class MySQLiteOpenHelper extends SQLiteOpenHelper {
public MySQLiteOpenHelper(Context context) {
super(context, "user.info", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.setPageSize(1024 * 4);
// db.enableWriteAheadLogging();
db.execSQL("CREATE TABLE if not exists user (name text, age string)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
```
自定义 ContentProvider 并注册:
```java
public class MyProvider extends ContentProvider {
private static UriMatcher mUriMatcher;
static {
mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mUriMatcher.addURI("com.example.omooo.demoproject.provider", "user", 1);
}
private MySQLiteOpenHelper mMySQLiteOpenHelper;
private SQLiteDatabase mSQLiteDatabase;
private Context mContext;
@Override
public boolean onCreate() {
mContext = getContext();
mMySQLiteOpenHelper = new MySQLiteOpenHelper(mContext);
mSQLiteDatabase = mMySQLiteOpenHelper.getWritableDatabase();
mSQLiteDatabase.execSQL("insert into user values('Omooo','18');");
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
return mSQLiteDatabase.query("user", projection, selection, selectionArgs, null, null, sortOrder, null);
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
mSQLiteDatabase.insert("user", null, values);
mContext.getContentResolver().notifyChange(uri, null);
return uri;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
```
```xml
<provider
android:exported="false"
android:authorities="com.example.omooo.demoproject.provider"
android:name=".provider.MyProvider"/>
```
进程内访问数据:
```java
private void insertTable() {
Uri uri = Uri.parse("content://com.example.omooo.demoproject.provider/user");
ContentValues values = new ContentValues();
values.put("name", "Test");
values.put("age", "21");
ContentResolver resolver = getContentResolver();
resolver.insert(uri, values);
Cursor cursor = resolver.query(uri, new String[]{"name", "age"}, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex("name"));
String age = cursor.getString(cursor.getColumnIndex("age"));
Log.i(TAG, "insertTable: name: " + name + " age: " + age);
}
cursor.close();
}
}
```
#### 参考
[Android:关于ContentProvider的知识都在这里了!](https://www.jianshu.com/p/ea8bc4aaf057)
[ContentProvider 从入门到精通](https://juejin.im/entry/5726d30e49830c0053562f1a)
[系统自带ContentProvider的常用Uri地址](https://www.cnblogs.com/tangs/articles/5756320.html)

@ -0,0 +1,172 @@
---
HandlerThread
---
#### 目录
1. 思维导图
2. 概述
3. 具体使用
4. 源码分析
5. 参考
#### 思维导图
#### 概述
HandlerThread 继承于 Thread,所以它本质就是个 Thread。与普通 Thread 的区别在于,它不仅建立了一个线程,并且创建了消息队列,有自己的 Looper,可以让我们在自己的线程中分发和处理消息,并对外提供自己的 Looper 的 get 方法。
HandlerThread 自带 Looper 使它可以通过消息队列来重复使用当前线程,节省系统资源开销。这是它的优点也是缺点,每个任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。
#### 具体使用
```java
public class HandlerThreadActivity extends AppCompatActivity {
private Button mButton;
private HandlerThread mHandlerThread;
private Handler mUiHandler;
private Handler mChildHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
initView();
mHandlerThread = new HandlerThread("HandlerThread");
mHandlerThread.start();
mUiHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 2) {
mButton.setText("子线程更新");
}
return false;
}
});
mChildHandler = new Handler(mHandlerThread.getLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 1) {
try {
//子线程模拟延迟处理
Thread.sleep(2000);
mUiHandler.sendEmptyMessage(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return false;
}
});
}
public void initView() {
mButton = findViewById(R.id.btn_show);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mChildHandler.sendEmptyMessage(1);
}
});
}
}
```
#### 源码分析
```java
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
protected void onLooperPrepared() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
public Looper getLooper() {
if (!isAlive()) {
return null;
}
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
@NonNull
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
public int getThreadId() {
return mTid;
}
}
```
源码很简单,就是在 run 方法中执行 Looper.prepare()、Looper.loop() 构造消息循环系统。外界可以通过 getLooper() 这个方法拿到这个 Looper。
总结为下:
1. HandlerThread 是一个自带 Looper 的线程,因此只能作为子线程使用
2. HandlerThread 必须配合 Handler 使用,HandlerThread 线程中具体做什么事,需要在 Handler 的 callback 中进行,因为它自己的 run 方法被写死了
3. 子线程的 Handler 与 HandlerThread 关系建立是通过构造子线程的Handler 传入 HandlerThread 的 Looper 。所以在此之前,必须先调用 mHandlerThread.start 让 run 方法跑起来 Looper 才能创建。
#### 参考
[Android中的线程形态(二)(HandlerThread/IntentService)](https://www.jianshu.com/p/4ca760e5040b)

@ -0,0 +1,194 @@
---
IntentService
---
#### 目录
1. 思维导图
2. 概述
3. 具体使用
4. 源码分析
5. 参考
#### 思维导图
#### 概述
IntentService 是继承于 Service 并处理异步请求的一个类,内部实现是 HandlerThread,处理完子线程的事后自动 stopService。
#### 具体使用
首先创建:
```java
public class MyIntentService extends IntentService {
private static final String TAG = "MyIntentService";
public MyIntentService() {
//IntentService 工作线程的名字
super("MyIntentService");
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate: ");
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy: ");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
if (intent != null) {
String taskName = intent.getStringExtra("taskName");
Log.i(TAG, "onHandleIntent: " + taskName);
switch (taskName) {
case "task1":
//任务一
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
case "task2":
//任务二
break;
default:
break;
}
}
}
}
```
Activity 中:
```java
public void onClick(View v) {
Intent intent = new Intent(this, MyIntentService.class);
switch (v.getId()) {
case R.id.btn1:
intent.putExtra("taskName", "task1");
startService(intent);
break;
case R.id.btn2:
intent.putExtra("taskName", "task2");
startService(intent);
break;
default:
break;
}
}
```
这里如果点击按钮一后立马点击按钮二,日志打印如下:
```
onCreate、onStartCommand、onHandleIntent: task1、
onStartCommand、onHandleIntent: task2、onDestroy
```
如果是等三秒后再点击按钮二,就是:
```
onCreate、onStartCommand、onHandleIntent: task1、onDestroy、
onCreate、onStartCommand、onHandleIntent: task2、onDestroy
```
这下就清楚了吧。
#### 源码分析
```java
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private String mName;
private boolean mRedelivery;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
//重写的方法,子线程需要做的事情
onHandleIntent((Intent)msg.obj);
//做完事,自动停止
stopSelf(msg.arg1);
}
}
public IntentService(String name) {
super();
//IntentService 的线程名
mName = name;
}
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
//构造子线程 Handler
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
//在 Service 启动的时候发送消息,子线程开始工作
mServiceHandler.sendMessage(msg);
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
//调用上面的那个方法,促使子线程开始工作
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onDestroy() {
mServiceLooper.quit();
}
@Override
@Nullable
public IBinder onBind(Intent intent) {
return null;
}
@WorkerThread
protected abstract void onHandleIntent(@Nullable Intent intent);
}
```
总结如下:
1. Service onCreate 的时候通过 HandlerThread 构建子线程的 Handler
2. Service onStartCommand 中通过子线程 Handler 发送消息
3. 子线程 handlerMessage 中调用我们重写的 onHandlerIntent 执行异步任务,执行完之后 Service 销毁
#### 参考

@ -0,0 +1,18 @@
---
屏幕适配
---
#### 目录
1. 思维导图
2. 适配方案
3. 参考
#### 思维导图
#### 适配方案
#### 参考
[一种极低成本的Android屏幕适配方式](https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA)

@ -6,13 +6,19 @@
1. 思维导图
2. 概述
3. 原理浅析
3. ClassLoader
- BaseDexClassLoader
- PathClassLoader
- DexClassLoader
- SecureClassLoader
- URLClassLoader
4. 原理浅析
- BaseDexClassLoader
- PathClassLoader
- DexClassLoader
- DexPathList
4. 实战
5. 参考
5. 实战
6. 参考
#### 思维导图
@ -24,6 +30,22 @@
在加载 dex 文件的时候,是放在一个 Element[] 数组里的,我们只要把修复好的 classes.dex 文件放在数组最前面被优先加载,那在加载有 bug 的 dex 文件里的 class 类的时候已经被加载过了,系统就不会加载,这时候带有 bug 的 class 就算被 “修复” 了。
#### ClassLoader
ClassLoader 是个抽象类,其具体的实现的子类有 BaseDexClassLoader 和 SecureClassLoader。
##### BaseDexClassLoader
它也有两个子类,分别是 PathClassLoader 和 DexClassLoader。
其中 PathClassLoader 只能加载安装在 Android 系统内 APK 文件(/data/app 目录下),其他位置的文件加载时都会报 ClassNotFoundException。因为 PathClassLoader 会读取 /data/dalvik-cache 目录下的经过 Dalvik 优化过的 dex 文件,这个目录的 dex 文件是在安装 apk 包的时候由 Dalvik 生成的,没有安装的时候,自然没有生成这个文件。
DexClassLoader 可从任意目录加载 jar/apk/dex。
##### URLClassLoader
只能用来加载 jar 文件,这在 Android 的 Dalvik/ART 上没法使用。
#### 原理浅析
热修复涉及的就是 dex 的加载流程。涉及的类有 BaseDexClassLoader、DexClassLoader、PathClassLoader、DexPathList 等等。
@ -73,8 +95,6 @@ public class BaseDexClassLoader extends ClassLoader {
其实 BaseDexClassLoader 的 findClass 是调用 DexPathList 的 findClass 方法,如果为 null 就抛出 ClassNotFoundException。
##### DexClassLoader
##### PathClassLoader
```java
@ -104,9 +124,11 @@ public class DexClassLoader extends BaseDexClassLoader {
可以看到,其实 DexClassLoader 和 PathClassLoader 都没有做什么,都是调用父类方法,只不过 DexClassLoader 多传了一个optimizedDirectory,它们的使用场景不一样:
PathClassLoader:只能加载已经安装到 Android 系统中的 apk 文件,是 Android 默认使用的类加载器。
PathClassLoader:用于加载 Android 系统类和开发编写应用的类,只能加载已经安装应用的 dex 或 apk 文件,是 Android 默认使用的类加载器,也是 getSystemClassLoader 的返回对象。
DexClassLoader:可以加载任意目录下的 dex/jar/apk/zip 文件,是实现热修复的重点。
DexClassLoader:可以加载任意目录下的 dex/jar/apk/zip 文件,比 PathClassLoader,是实现热修复的重点。
BootClassLoader:主要用于加载系统的类,包括 java 和 android 系统的类库,和 JVM 中不同,BootClassLoader 是 ClassLoader 内部类,是由 Java 实现,它也是所有系统 ClassLoader 的 父 ClassLoader
##### DexPathList
@ -265,4 +287,6 @@ final class DexPathList {
[网易云课堂](
https://study.163.com/course/courseLearn.htm?courseId=1208968811#/learn/live?lessonId=1278455246&courseId=1208968811)
[热修复——Tinker 的集成与使用](https://mp.weixin.qq.com/s/xpB_ipYv9cN8k8fdr_7wCw)
[热修复——Tinker 的集成与使用](https://mp.weixin.qq.com/s/xpB_ipYv9cN8k8fdr_7wCw)
[剖析ClassLoader深入热修复原理](https://www.jianshu.com/p/95387cc07e3c)

@ -241,8 +241,6 @@ Java 源代码的编译流程分为三个步骤:
[浅谈Android下的注解](https://juejin.im/post/5b292b736fb9a00e8c4435f7)
[Android Annotation扫盲笔记](https://juejin.im/post/5a771b8b6fb9a0633c65e947)
[注解处理器](https://time.geekbang.org/column/article/40189)
[注解浅析](https://www.jianshu.com/p/937a15e18eac)
[Java 注解及其在 Android 中的应用](https://juejin.im/post/5b824b8751882542f105447d)

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 153 KiB

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Before

Width:  |  Height:  |  Size: 285 KiB

After

Width:  |  Height:  |  Size: 285 KiB

Before

Width:  |  Height:  |  Size: 191 KiB

After

Width:  |  Height:  |  Size: 191 KiB

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 155 KiB

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Loading…
Cancel
Save