IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android:主流三方库源码教你快速上手Leakcanary,保准看明白 -> 正文阅读

[移动开发]Android:主流三方库源码教你快速上手Leakcanary,保准看明白

}



...

}

复制代码




在RefWatcher的基类构造器RefWatcherBuilder的构造方法中新建了一个HeapDump的构造器对象。其中**HeapDump就是一个保存heap dump信息的数据结构**。



接着来分析下install()方法中的链式调用的listenerServiceClass(DisplayLeakService.class)这部分逻辑。



[]( )4、AndroidRefWatcherBuilder#listenerServiceClass()

------------------------------------------------------------------------------------------------------------



public @NonNull AndroidRefWatcherBuilder listenerServiceClass(

@NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {

return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));

}

复制代码




在这里,传入了一个DisplayLeakService的Class对象,它的作用是展示泄露分析的结果日志,然后会展示一个用于跳转到显示泄露界面DisplayLeakActivity的通知。在listenerServiceClass()这个方法中新建了一个ServiceHeapDumpListener对象,下面看看它内部的操作。



[]( )5、ServiceHeapDumpListener

------------------------------------------------------------------------------------



public final class ServiceHeapDumpListener implements HeapDump.Listener {

...



public ServiceHeapDumpListener(@NonNull final Context context,

    @NonNull final Class<? extends AbstractAnalysisResultService> listenerServiceClass) {

  this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");

  this.context = checkNotNull(context, "context").getApplicationContext();

}



...

}

复制代码




可以看到这里仅仅是在ServiceHeapDumpListener中保存了DisplayLeakService的Class对象和application对象。它的作用就是接收一个heap dump去分析。



然后我们继续看install()方法链式调用.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())的这部分代码。先看AndroidExcludedRefs.createAppDefaults()。



[]( )6、AndroidExcludedRefs#createAppDefaults()

----------------------------------------------------------------------------------------------------



public enum AndroidExcludedRefs {

...



public static @NonNull ExcludedRefs.Builder createAppDefaults() {

  return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));

}



public static @NonNull ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) {

  ExcludedRefs.Builder excluded = ExcludedRefs.builder();

  for (AndroidExcludedRefs ref : refs) {

    if (ref.applies) {

      ref.add(excluded);

      ((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());

    }

  }

  return excluded;

}



...

}

复制代码




先来说下**AndroidExcludedRefs**这个类,它是一个enum类,它**声明了Android SDK和厂商定制的SDK中存在的内存泄露的case**,根据AndroidExcludedRefs这个类的类名就可看出这些case**都会被Leakcanary的监测过滤掉**。目前这个版本是有**46种**这样的**case**被包含在内,后续可能会一直增加。然后EnumSet.allOf(AndroidExcludedRefs.class)这个方法将会返回一个包含AndroidExcludedRefs元素类型的EnumSet。Enum是一个抽象类,在这里具体的实现类是**通用正规型的RegularEnumSet,如果Enum里面的元素个数大于64,则会使用存储大数据量的JumboEnumSet**。最后,在createBuilder这个方法里面构建了一个排除引用的建造器excluded,将各式各样的case分门别类地保存起来再返回出去。



最后,我们看到链式调用的最后一步buildAndInstall()。



[]( )7、AndroidRefWatcherBuilder#buildAndInstall()

-------------------------------------------------------------------------------------------------------



private boolean watchActivities = true;

private boolean watchFragments = true;

public @NonNull RefWatcher buildAndInstall() {

// 1

if (LeakCanaryInternals.installedRefWatcher != null) {

  throw new UnsupportedOperationException("buildAndInstall() should only be called once.");

}



// 2

RefWatcher refWatcher = build();

if (refWatcher != DISABLED) {

  // 3

  LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);

  if (watchActivities) {

    // 4

    ActivityRefWatcher.install(context, refWatcher);

  }

  if (watchFragments) {

    // 5

    FragmentRefWatcher.Helper.install(context, refWatcher);

  }

}

// 6

LeakCanaryInternals.installedRefWatcher = refWatcher;

return refWatcher;

}

复制代码




首先,在注释1处,会判断LeakCanaryInternals.installedRefWatcher是否已经被赋值,如果被赋值了,则会抛出异常,警告 buildAndInstall()这个方法应该仅仅只调用一次,在此方法结束时,即在注释6处,该LeakCanaryInternals.installedRefWatcher才会被赋值。再来看注释2处,调用了AndroidRefWatcherBuilder其基类RefWatcherBuilder的build()方法,我们它是如何建造的。



[]( )8、RefWatcherBuilder#build()

--------------------------------------------------------------------------------------



public final RefWatcher build() {

if (isDisabled()) {

  return RefWatcher.DISABLED;

}



if (heapDumpBuilder.excludedRefs == null) {

  heapDumpBuilder.excludedRefs(defaultExcludedRefs());

}



HeapDump.Listener heapDumpListener = this.heapDumpListener;

if (heapDumpListener == null) {

  heapDumpListener = defaultHeapDumpListener();

}



DebuggerControl debuggerControl = this.debuggerControl;

if (debuggerControl == null) {

  debuggerControl = defaultDebuggerControl();

}



HeapDumper heapDumper = this.heapDumper;

if (heapDumper == null) {

  heapDumper = defaultHeapDumper();

}



WatchExecutor watchExecutor = this.watchExecutor;

if (watchExecutor == null) {

  watchExecutor = defaultWatchExecutor();

}



GcTrigger gcTrigger = this.gcTrigger;

if (gcTrigger == null) {

  gcTrigger = defaultGcTrigger();

}



if (heapDumpBuilder.reachabilityInspectorClasses == null) {

  heapDumpBuilder.reachabilityInspectorClasses(defa  ultReachabilityInspectorClasses());

}



return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,

    heapDumpBuilder);

}

复制代码




可以看到,**RefWatcherBuilder包含了以下7个组成部分:**



*   1、**excludedRefs : 记录可以被忽略的泄漏路径**。

*   2、**heapDumpListener : 转储堆信息到hprof文件,并在解析完 hprof 文件后进行回调,最后通知 DisplayLeakService 弹出泄漏提醒**。

*   3、debuggerControl : 判断是否处于调试模式,调试模式中不会进行内存泄漏检测。为什么呢?因为**在调试过程中可能会保留上一个引用从而导致错误信息上报**。

*   4、**heapDumper : 堆信息转储者,负责dump 内存泄漏处的 heap 信息到 hprof 文件**。

*   5、**watchExecutor : 线程控制器,在 onDestroy() 之后并且在主线程空闲时执行内存泄漏检测**。

*   6、**gcTrigger : 用于 GC,watchExecutor 首次检测到可能的内存泄漏,会主动进行 GC,GC 之后会再检测一次,仍然泄漏的判定为内存泄漏,最后根据heapDump信息生成相应的泄漏引用链**。

*   7、**reachabilityInspectorClasses : 用于要进行可达性检测的类列表。**



最后,会使用建造者模式将这些组成部分构建成一个新的RefWatcher并将其返回。



我们继续看回到AndroidRefWatcherBuilder的注释3处的 LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true)这行代码。



[]( )9、LeakCanaryInternals#setEnabledAsync()

--------------------------------------------------------------------------------------------------



public static void setEnabledAsync(Context context, final Class<?> componentClass,

final boolean enabled) {

final Context appContext = context.getApplicationContext();

AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {

@Override public void run() {

  setEnabledBlocking(appContext, componentClass, enabled);

}

});

}

复制代码




在这里直接使用了**AsyncTask内部自带的THREAD\_POOL\_EXECUTOR线程池**进行阻塞式地显示DisplayLeakActivity。



然后我们再继续看AndroidRefWatcherBuilder的注释4处的代码。



[]( )10、ActivityRefWatcher#install()

------------------------------------------------------------------------------------------



public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {

Application application = (Application) context.getApplicationContext();

// 1

ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);



// 2

application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);

}

复制代码




可以看到,在注释1处创建一个自己的activityRefWatcher实例,并在注释2处调用了application的registerActivityLifecycleCallbacks()方法,这样就能够监听activity对应的生命周期事件了。继续看看activityRefWatcher.lifecycleCallbacks里面的操作。



private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =

new ActivityLifecycleCallbacksAdapter() {

  @Override public void onActivityDestroyed(Activity activity) {

      refWatcher.watch(activity);

  }

};

public abstract class ActivityLifecycleCallbacksAdapter

implements Application.ActivityLifecycleCallbacks {

}

复制代码




很明显,这里**实现并重写了Application的ActivityLifecycleCallbacks的onActivityDestroyed()方法,这样便能在所有Activity执行完onDestroyed()方法之后调用 refWatcher.watch(activity)这行代码进行内存泄漏的检测了**。



我们再看到注释5处的FragmentRefWatcher.Helper.install(context, refWatcher)这行代码,



[]( )11、FragmentRefWatcher.Helper#install()

-------------------------------------------------------------------------------------------------



public interface FragmentRefWatcher {

void watchFragments(Activity activity);



final class Helper {



  private static final String SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME =

      "com.squareup.leakcanary.internal.SupportFragmentRefWatcher";



  public static void install(Context context, RefWatcher refWatcher) {

    List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();



    // 1

    if (SDK_INT >= O) {

      fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));

    }



    // 2

    try {

      Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);

      Constructor<?> constructor =

          fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);

      FragmentRefWatcher supportFragmentRefWatcher   =

          (FragmentRefWatcher) constructor.newInstance(refWatcher);

      fragmentRefWatchers.add(supportFragmentRefWatcher);

    } catch (Exception ignored) {

    }



    if (fragmentRefWatchers.size() == 0) {

      return;

    }



    Helper helper = new Helper(fragmentRefWatchers);



    // 3

    Application application = (Application) context.getApplicationContext();

    application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);

  }



...

}

复制代码




这里面的逻辑很简单,首先在注释1处将Android标准的Fragment的RefWatcher类,即AndroidOfFragmentRefWatcher添加到新创建的fragmentRefWatchers中。在注释2处**使用反射将leakcanary-support-fragment包下面的SupportFragmentRefWatcher添加进来,如果你在app的build.gradle下没有添加下面这行引用的话,则会拿不到此类,即LeakCanary只会检测Activity和标准Fragment这两种情况**。



debugImplementation ‘com.squareup.leakcanary:leakcanary-support-fragment:1.6.2’

复制代码




继续看到注释3处helper.activityLifecycleCallbacks里面的代码。



private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =

new ActivityLifecycleCallbacksAdapter() {

  @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

    for (FragmentRefWatcher watcher : fragmentRefWatchers) {

        watcher.watchFragments(activity);

    }

}

};

复制代码




可以看到,在Activity执行完onActivityCreated()方法之后,会调用指定watcher的watchFragments()方法,注意,这里的watcher可能有两种,但不管是哪一种,都会使用当前传入的activity获取到对应的FragmentManager/SupportFragmentManager对象,调用它的registerFragmentLifecycleCallbacks()方法,在对应的onDestroyView()和onDestoryed()方法执行完后,分别使用refWatcher.watch(view)和refWatcher.watch(fragment)进行内存泄漏的检测,代码如下所示。



@Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {

View view = fragment.getView();

if (view != null) {

    refWatcher.watch(view);

}

}

@Override

public void onFragmentDestroyed(FragmentManagerfm, Fragment fragment) {

refWatcher.watch(fragment);

}

复制代码




注意,下面到真正关键的地方了,接下来分析refWatcher.watch()这行代码。



#### []( )12、RefWatcher#watch()



public void watch(Object watchedReference, String referenceName) {

if (this == DISABLED) {

  return;

}

checkNotNull(watchedReference, "watchedReference");

checkNotNull(referenceName, "referenceName");

final long watchStartNanoTime = System.nanoTime();

// 1

String key = UUID.randomUUID().toString();

// 2

retainedKeys.add(key);

// 3

final KeyedWeakReference reference =

    new KeyedWeakReference(watchedReference, key, referenceName, queue);



// 4

ensureGoneAsync(watchStartNanoTime, reference);

}

复制代码




注意到在注释1处**使用随机的UUID保证了每个检测对象对应 key 的唯一性**。在注释2处将生成的key添加到类型为CopyOnWriteArraySet的Set集合中。在注释3处新建了一个自定义的弱引用KeyedWeakReference,看看它内部的实现。



[]( )13、KeyedWeakReference

--------------------------------------------------------------------------------



final class KeyedWeakReference extends WeakReference {

public final String key;

public final String name;



KeyedWeakReference(Object referent, String key, String name,

    ReferenceQueue<Object> referenceQueue) {

  // 1

  super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));

  this.key = checkNotNull(key, "key");

  this.name = checkNotNull(name, "name");

}

}

复制代码




可以看到,**在KeyedWeakReference内部,使用了key和name标识了一个被检测的WeakReference对象**。在注释1处,**将弱引用和引用队列 ReferenceQueue 关联起来,如果弱引用reference持有的对象被GC回收,JVM就会把这个弱引用加入到与之关联的引用队列referenceQueue中。即 KeyedWeakReference 持有的 Activity 对象如果被GC回收,该对象就会加入到引用队列 referenceQueue 中**。



接着我们回到RefWatcher.watch()里注释4处的ensureGoneAsync()方法。



[]( )14、RefWatcher#ensureGoneAsync()

------------------------------------------------------------------------------------------



private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {

// 1

watchExecutor.execute(new Retryable() {

    @Override public Retryable.Result run() {

        // 2

        return ensureGone(reference watchStartNanoTime);

    }

});

}

复制代码




在ensureGoneAsync()方法中,在注释1处使用 watchExecutor 执行了注释2处的 ensureGone 方法,watchExecutor 是 AndroidWatchExecutor 的实例。



下面看看watchExecutor内部的逻辑。



[]( )15、AndroidWatchExecutor

----------------------------------------------------------------------------------



public final class AndroidWatchExecutor implements WatchExecutor {

...



public AndroidWatchExecutor(long initialDelayMillis)     {

  mainHandler = new Handler(Looper.getMainLooper());

  HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);

  handlerThread.start();

  // 1

  backgroundHandler = new Handler(handlerThread.getLooper());

  this.initialDelayMillis = initialDelayMillis;

  maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;

}



@Override public void execute(@NonNull Retryable retryable) {

  // 2

  if (Looper.getMainLooper().getThread() == Thread.currentThread()) {

    waitForIdle(retryable, 0);

  } else {

    postWaitForIdle(retryable, 0);

  }

}



...

}

复制代码




在注释1处**AndroidWatchExecutor的构造方法**中,注意到这里**使用HandlerThread的looper新建了一个backgroundHandler**,后面会用到。在注释2处,会判断当前线程是否是主线程,如果是,则直接调用waitForIdle()方法,如果不是,则调用postWaitForIdle(),来看看这个方法。



private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {

mainHandler.post(new Runnable() {

@Override public void run() {

  waitForIdle(retryable, failedAttempts);

}

});

}

复制代码




很清晰,这里使用了在构造方法中用主线程looper构造的mainHandler进行post,那么waitForIdle()最终也会在主线程执行。接着看看waitForIdle()的实现。



private void waitForIdle(final Retryable retryable, final int failedAttempts) {

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {

@Override public boolean queueIdle() {

  postToBackgroundWithDelay(retryable, failedAttempts);

  return false;

}

});

}

复制代码




这里**MessageQueue.IdleHandler()回调方法的作用是当 looper 空闲的时候,会回调 queueIdle 方法,利用这个机制我们可以实现第三方库的延迟初始化**,然后执行内部的postToBackgroundWithDelay()方法。接下来看看它的实现。



private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {

long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);

// 1

long delayMillis = initialDelayMillis * exponentialBackoffFactor;

// 2

backgroundHandler.postDelayed(new Runnable() {

@Override public void run() {

  // 3

  Retryable.Result result = retryable.run();

  // 4

  if (result == RETRY) {

    postWaitForIdle(retryable, failedAttempts +   1);

  }

}

}, delayMillis);

}

复制代码




先看到注释4处,可以明白,postToBackgroundWithDelay()是一个递归方法,如果result 一直等于RETRY的话,则会一直执行postWaitForIdle()方法。在回到注释1处,这里initialDelayMillis 的默认值是 5s,因此delayMillis就是5s。在注释2处,使用了在构造方法中用HandlerThread的looper新建的backgroundHandler进行异步延时执行retryable的run()方法。这个run()方法里执行的就是RefWatcher的ensureGoneAsync()方法中注释2处的ensureGone()这行代码,继续看它内部的逻辑。



[]( )16、RefWatcher#ensureGone()

-------------------------------------------------------------------------------------



Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {

long gcStartNanoTime = System.nanoTime();

long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime -     watchStartNanoTime);



// 1

removeWeaklyReachableReferences();



// 2

if (debuggerControl.isDebuggerAttached()) {

  // The debugger can create false leaks.

  return RETRY;

}



// 3

if (gone(reference)) {

  return DONE;

}



// 4

gcTrigger.runGc();

removeWeaklyReachableReferences();



// 5

if (!gone(reference)) {

  long startDumpHeap = System.nanoTime();

  long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);



  File heapDumpFile = heapDumper.dumpHeap();

  if (heapDumpFile == RETRY_LATER) {

    // Could not dump the heap.

    return RETRY;

  }



  long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);



  HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)

      .referenceName(reference.name)

      .watchDurationMs(watchDurationMs)

      .gcDurationMs(gcDurationMs)

      .heapDumpDurationMs(heapDumpDurationMs)

      .build();



  heapdumpListener.analyze(heapDump);

}

return DONE;

}

复制代码




在注释1处,执行了removeWeaklyReachableReferences()这个方法,接下来分析下它的含义。



private void removeWeaklyReachableReferences() {

KeyedWeakReference ref;

while ((ref = (KeyedWeakReference) queue.poll()) != null) {

    retainedKeys.remove(ref.key);

}

}

复制代码




## 学习分享

在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了

很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘

如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。

**2021最新上万页的大厂面试真题**

**[CodeChina开源项目地址:https://codechina.csdn.net/m0_60958482/android_p7](https://codechina.csdn.net/m0_60958482/android_p7)**

![](https://img-blog.csdnimg.cn/img_convert/b990d052a7cfefa91a10211a17e5a079.png)

**七大模块学习资料:如NDK模块开发、Android框架体系架构...**

![](https://img-blog.csdnimg.cn/img_convert/6108ab9d20a0fa3b426273b9b972a99b.png)

只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。

> 这份体系学习笔记,适应人群:
> 第一,学习知识比较碎片化,没有合理的学习路线与进阶方向。
> 第二,开发几年,不知道如何进阶更进一步,比较迷茫。
> 第三,到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!
apDumpDurationMs(heapDumpDurationMs)

          .build();



      heapdumpListener.analyze(heapDump);

    }

    return DONE;

}

复制代码 

在注释1处,执行了removeWeaklyReachableReferences()这个方法,接下来分析下它的含义。


private void removeWeaklyReachableReferences() {

    KeyedWeakReference ref;

    while ((ref = (KeyedWeakReference) queue.poll()) != null) {

        retainedKeys.remove(ref.key);

    }

}

复制代码 

学习分享

在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了

很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘

如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。

2021最新上万页的大厂面试真题

CodeChina开源项目地址:https://codechina.csdn.net/m0_60958482/android_p7

[外链图片转存中…(img-AcP8ZVlS-1631012593574)]

七大模块学习资料:如NDK模块开发、Android框架体系架构…

[外链图片转存中…(img-doZG46DX-1631012593576)]

只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。

这份体系学习笔记,适应人群:
第一,学习知识比较碎片化,没有合理的学习路线与进阶方向。
第二,开发几年,不知道如何进阶更进一步,比较迷茫。
第三,到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!
由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示 。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-09-08 10:51:10  更:2021-09-08 10:51:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 16:48:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码