diff --git a/blogs/Activity.md b/blogs/Activity.md deleted file mode 100644 index 64e6405..0000000 --- a/blogs/Activity.md +++ /dev/null @@ -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 启动流程 \ No newline at end of file diff --git a/blogs/Animator.md b/blogs/Android/Animator.md similarity index 100% rename from blogs/Animator.md rename to blogs/Android/Animator.md diff --git a/blogs/Android/AsyncTask.md b/blogs/Android/AsyncTask.md new file mode 100644 index 0000000..0484e16 --- /dev/null +++ b/blogs/Android/AsyncTask.md @@ -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/) \ No newline at end of file diff --git a/blogs/Android/Bundle.md b/blogs/Android/Bundle.md index a51b87f..956c98f 100644 --- a/blogs/Android/Bundle.md +++ b/blogs/Android/Bundle.md @@ -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 { + + 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\ 替换 HashMap\。 +SparseArray 和 ArrayMap 类似,但是 SparseArray 只能存储 key 为 int 型的,它比 ArrayMap 少了计算 key 的哈希值,同时对象数组只需要存 value 即可,这也就避免了 key 的装箱操作和分配空间,,建议用 SparseArray\ 替换 HashMap\。 类似的还有 SparseIntArray 代替 HashMap\ 等。 +![](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) \ No newline at end of file diff --git a/blogs/Android/ContextProvider.md b/blogs/Android/ContextProvider.md new file mode 100644 index 0000000..ab36ec9 --- /dev/null +++ b/blogs/Android/ContextProvider.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 + +``` + +进程内访问数据: + +```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) \ No newline at end of file diff --git "a/blogs/Dalvik \\ ART.md" "b/blogs/Android/Dalvik \\ ART.md" similarity index 100% rename from "blogs/Dalvik \\ ART.md" rename to "blogs/Android/Dalvik \\ ART.md" diff --git a/blogs/Android/HandlerThread.md b/blogs/Android/HandlerThread.md new file mode 100644 index 0000000..c224645 --- /dev/null +++ b/blogs/Android/HandlerThread.md @@ -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) \ No newline at end of file diff --git a/blogs/Android/IntentService.md b/blogs/Android/IntentService.md new file mode 100644 index 0000000..7cf7244 --- /dev/null +++ b/blogs/Android/IntentService.md @@ -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 销毁 + +#### 参考 \ No newline at end of file diff --git a/blogs/RecyclerView.md b/blogs/Android/RecyclerView.md similarity index 100% rename from blogs/RecyclerView.md rename to blogs/Android/RecyclerView.md diff --git a/blogs/SQLite.md b/blogs/Android/SQLite.md similarity index 100% rename from blogs/SQLite.md rename to blogs/Android/SQLite.md diff --git a/blogs/SharedPreferences.md b/blogs/Android/SharedPreferences.md similarity index 100% rename from blogs/SharedPreferences.md rename to blogs/Android/SharedPreferences.md diff --git a/blogs/WebView.md b/blogs/Android/WebView.md similarity index 100% rename from blogs/WebView.md rename to blogs/Android/WebView.md diff --git a/blogs/AOP_AspectJ.md b/blogs/Android/埋点/AOP_AspectJ.md similarity index 100% rename from blogs/AOP_AspectJ.md rename to blogs/Android/埋点/AOP_AspectJ.md diff --git a/blogs/埋点.md b/blogs/Android/埋点/埋点.md similarity index 100% rename from blogs/埋点.md rename to blogs/Android/埋点/埋点.md diff --git a/blogs/埋点之代理 View.OnClickListener.md b/blogs/Android/埋点/埋点之代理 View.OnClickListener.md similarity index 100% rename from blogs/埋点之代理 View.OnClickListener.md rename to blogs/Android/埋点/埋点之代理 View.OnClickListener.md diff --git a/blogs/埋点之应用前后台判断.md b/blogs/Android/埋点/埋点之应用前后台判断.md similarity index 100% rename from blogs/埋点之应用前后台判断.md rename to blogs/Android/埋点/埋点之应用前后台判断.md diff --git a/blogs/Android/屏幕适配.md b/blogs/Android/屏幕适配.md new file mode 100644 index 0000000..59753f4 --- /dev/null +++ b/blogs/Android/屏幕适配.md @@ -0,0 +1,18 @@ +--- +屏幕适配 +--- + +#### 目录 + +1. 思维导图 +2. 适配方案 +3. 参考 + +#### 思维导图 + +#### 适配方案 + +#### 参考 + +[一种极低成本的Android屏幕适配方式](https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA) + diff --git "a/blogs/I\\O 优化.md" "b/blogs/Android/性能优化/I\\O 优化.md" similarity index 100% rename from "blogs/I\\O 优化.md" rename to "blogs/Android/性能优化/I\\O 优化.md" diff --git a/blogs/Android/热修复.md b/blogs/Android/热修复.md index de1390b..a06f021 100644 --- a/blogs/Android/热修复.md +++ b/blogs/Android/热修复.md @@ -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) \ No newline at end of file +[热修复——Tinker 的集成与使用](https://mp.weixin.qq.com/s/xpB_ipYv9cN8k8fdr_7wCw) + +[剖析ClassLoader深入热修复原理](https://www.jianshu.com/p/95387cc07e3c) \ No newline at end of file diff --git a/blogs/String.md b/blogs/Java/String.md similarity index 100% rename from blogs/String.md rename to blogs/Java/String.md diff --git a/blogs/final.md b/blogs/Java/final.md similarity index 100% rename from blogs/final.md rename to blogs/Java/final.md diff --git a/blogs/反射.md b/blogs/Java/反射.md similarity index 100% rename from blogs/反射.md rename to blogs/Java/反射.md diff --git a/blogs/泛型.md b/blogs/Java/泛型.md similarity index 100% rename from blogs/泛型.md rename to blogs/Java/泛型.md diff --git a/blogs/注解.md b/blogs/Java/注解.md similarity index 98% rename from blogs/注解.md rename to blogs/Java/注解.md index ea0a08d..3fddeca 100644 --- a/blogs/注解.md +++ b/blogs/Java/注解.md @@ -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) \ No newline at end of file +[Java 注解及其在 Android 中的应用](https://juejin.im/post/5b824b8751882542f105447d) \ No newline at end of file diff --git a/images/AOP_AspectJ.png b/images/Android/AOP_AspectJ.png similarity index 100% rename from images/AOP_AspectJ.png rename to images/Android/AOP_AspectJ.png diff --git a/images/Android 虚拟机.png b/images/Android/Android 虚拟机.png similarity index 100% rename from images/Android 虚拟机.png rename to images/Android/Android 虚拟机.png diff --git a/images/Animator.png b/images/Android/Animator.png similarity index 100% rename from images/Animator.png rename to images/Android/Animator.png diff --git a/images/Android/ContentProvider.png b/images/Android/ContentProvider.png new file mode 100644 index 0000000..7be4bf7 Binary files /dev/null and b/images/Android/ContentProvider.png differ diff --git a/images/Context 类层次.png b/images/Android/Context 类层次.png similarity index 100% rename from images/Context 类层次.png rename to images/Android/Context 类层次.png diff --git a/images/Context.png b/images/Android/Context.png similarity index 100% rename from images/Context.png rename to images/Android/Context.png diff --git a/images/DeceAcceInterpolator.gif b/images/Android/DeceAcceInterpolator.gif similarity index 100% rename from images/DeceAcceInterpolator.gif rename to images/Android/DeceAcceInterpolator.gif diff --git "a/images/I\\O 流程.png" "b/images/Android/I\\O 流程.png" similarity index 100% rename from "images/I\\O 流程.png" rename to "images/Android/I\\O 流程.png" diff --git "a/images/I\\O 请求流程.png" "b/images/Android/I\\O 请求流程.png" similarity index 100% rename from "images/I\\O 请求流程.png" rename to "images/Android/I\\O 请求流程.png" diff --git "a/images/Linux I\\O 模型.png" "b/images/Android/Linux I\\O 模型.png" similarity index 100% rename from "images/Linux I\\O 模型.png" rename to "images/Android/Linux I\\O 模型.png" diff --git a/images/RecyclerView.png b/images/Android/RecyclerView.png similarity index 100% rename from images/RecyclerView.png rename to images/Android/RecyclerView.png diff --git a/images/SharedPreferences.png b/images/Android/SharedPreferences.png similarity index 100% rename from images/SharedPreferences.png rename to images/Android/SharedPreferences.png diff --git a/images/SnapView.png b/images/Android/SnapView.png similarity index 100% rename from images/SnapView.png rename to images/Android/SnapView.png diff --git a/images/TypeEvaluator.gif b/images/Android/TypeEvaluator.gif similarity index 100% rename from images/TypeEvaluator.gif rename to images/Android/TypeEvaluator.gif diff --git a/images/WebView.png b/images/Android/WebView.png similarity index 100% rename from images/WebView.png rename to images/Android/WebView.png diff --git a/images/String.png b/images/Java/String.png similarity index 100% rename from images/String.png rename to images/Java/String.png diff --git a/images/compact_after.png b/images/Java/compact_after.png similarity index 100% rename from images/compact_after.png rename to images/Java/compact_after.png diff --git a/images/compact_before.png b/images/Java/compact_before.png similarity index 100% rename from images/compact_before.png rename to images/Java/compact_before.png diff --git a/images/反射.png b/images/Java/反射.png similarity index 100% rename from images/反射.png rename to images/Java/反射.png diff --git a/images/泛型.png b/images/Java/泛型.png similarity index 100% rename from images/泛型.png rename to images/Java/泛型.png