肥言肥语

肥肥鱼胡说八道的地方

0%

如何提高软件系统的可维护性和可复用性是面向对象程序设计思想需要解决的核心问题。在面向对象程序设计中,可维护性的复用是以设计原则为基础的。每一个设计原则都蕴含着面向对象程序设计的思想,可以从不同的角度提升一个软件系统的架构水平。

面向对象程序设计原则是为支撑可维护性的复用而诞生的,它们是从很多的设计方案中总结出来的指导性原则,通常体现在设计模式中。

阅读全文 »

线上出现一些 OutOfMemoryError,经过分析崩溃数据,发现出现 OOM 时进程的可用空间还是非常大的。

从崩溃的堆栈信息中可以分析出引发该 OOM 的主要与 OkHttp 有关系,而业务场景中都是普通的数据请求(JSON),并没有使用 BitMap 这一类的较大内存占用的资源。更何况,进程的可用内存空间还是很充足的。

虽然堆栈信息不尽相同,但是最终从大量堆栈 LOG 中分析出有价值的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again
at java.lang.Thread.nativeCreate(Native Method)
at java.lang.Thread.start(Thread.java:733)
at tech.fiissh.base.utils.c.u(SourceFile:7)
at tech.fiissh.base.common.e.c.a(SourceFile:40)
at tech.fiissh.base.common.e.c.a(SourceFile:156)
at tech.fiissh.base.common.e.b.a(SourceFile:30)
at tech.fiissh.base.common.e.b.a$1.handleMessage(SourceFile:6)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6942)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)

java.lang.OutOfMemoryErrorat java.lang.String.<init>(String.java:255)
at libcore.io.IoUtils$FileReader.toString(IoUtils.java:272)
at libcore.io.IoUtils.readFileAsString(IoUtils.java:114)
at com.android.org.conscrypt.CertPinManager.readPinFile(CertPinManager.java:111)
at com.android.org.conscrypt.CertPinManager.rebuild(CertPinManager.java:85)
at com.android.org.conscrypt.CertPinManager.<init>(CertPinManager.java:49)
at com.android.org.conscrypt.TrustManagerImpl.<init>(TrustManagerImpl.java:137)
at com.android.org.conscrypt.TrustManagerImpl.<init>(TrustManagerImpl.java:97)
at com.android.org.conscrypt.TrustManagerFactoryImpl.engineGetTrustManagers(TrustManagerFactoryImpl.java:80)
at javax.net.ssl.TrustManagerFactory.getTrustManagers(TrustManagerFactory.java:219)
at tech.fiissh.thrid.okhttp.internal.Util.platformTrustManager(Util.java:670)
at tech.fiissh.thrid.okhttp.OkHttpClient.<init>(OkHttpClient.java:256)
at tech.fiissh.thrid.okhttp.OkHttpClient$Builder.build(OkHttpClient.java:1035)
at tech.fiissh.base.common.net.e.b.<init>(OkHttpStack.java:73)
at tech.fiissh.base.common.net.g.<init>(NetworkDispatcher.java:51)
at tech.fiissh.base.common.net.i$1.run(RequestQueue.java:110)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:841)

经过与源码文件进行比较,OkHttpStack 第73行的方法实际上是通过 OkHttpClient.Builder 来实例化一个 OkHttpClient 对象。

通常情况下我们所理解的 OOM 是由于进程的可用堆内存不够(即 Runtime.getRuntime().maxMemory() 小于需要申请的内存大小)的情况下系统会抛出 OutOfMemoryError。其报错信息中详细的记录了我们的内存分配需求和可用堆内存的大小信息:

1
java.lang.OutOfMemoryError: Failed to allocate a XXX byte allocation with XXX free bytes and XXXKB until OOM

通过对 pthread_create 关键字的搜索,最终在 thread.cc 中找到了抛出该异常的位置,并且通过对 Thread::CreateNativeThread 函数的分析发现,在创建线程时,系统会先判断当前线程数是否超过了系统对线程数的限制,如果超过该限制则抛出 java.lang.OutOfMemoryError:pthread_create (XXXXKB stack) failed 异常。

那么,问题的根源在哪里?通过对代码的分析,发现每次发起一个网络请求的时候都会创建一个 OkHttpClient,而每个 OkHttpClient 对象都会初始化一个线程池(线程的生命周期又较长),如果在短时间内发起多次请求,那么线程池会被创建多个,而线程数也会随之增加。解决的方案则是将 OkHttpClient 通过单例的方式对外提供。

针对上述线程创建的问题,可以参考 不可思议的OOM,作者针对出现类似问题的场景做了深入分析。

关于 HTTPClient 的问题,可以参考 OkHttp竟然玩出OOM?,作者针对为什么创建多个线程池的场景进行了深入的分析。

Setting RepositoryIntelliJ IDEA(Android Studio) 的一项配置托管服务,我们可以通过在 Github 或者其他 Git 服务上创建一个 Git Repository 的方式统一托管、同步配置信息:

  • Github 创建一个代码仓库(例如: android-studio-setting-repository
  • Github 上创建一个 Personal access tokens(保留 readwrite 相关权限即可)
  • 将版本库地址填入 Android StudioFile/Setting Repository/Upstream URL(尽量使用 https 而不是 ssh 的方式)
  • 点击 Overwrite Remote 完成配置信息的提交

Overwrite Remote 是将本地配置信息写入到远程仓库,Overwrite Load 是将远程仓库的信息写入到本地,Merge 则是合并两者之间的差异。

我的 Android Studio 配置仓库为 fiissh/android-studio-setting-repository

Commit Message 规范

每一个 Commit Message 应该包含一个 headerbodyfooter。其中,headerbodyfooter 之间以空行作为间隔。header 是必须要填写的:

  • header 通常包含此次提交的类型:
    • feat 新特性
    • fix bug 修复
    • docs 文档改动
    • style 格式化
    • refactor 重构代码
    • test 添加缺失的测试, 重构测试, 不包括生产代码变动
    • chore 更新grunt任务等; 不包括生产代码变动
  • body 通常包含此次改动的影响范围、改动的详细信息
  • footer 通常包含一些需要关闭的 issues 信息等

模板文件

建议将如下内容保存到本机的某个目录,例如 MAC~/.git-commit-template.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# <类型>: (类型的值见下面描述) <主题> (最多50个字)

# 解释为什么要做这些改动
# |<---- 请限制每行最多72个字 ---->|

# 提供相关文章和其它资源的链接和关键字
# 例如: Github issue #23

# --- 提交 结束 ---
# 类型值包含
# feat (新特性)
# fix (bug修复)
# docs (文档改动)
# style (格式化, 缺失分号等; 不包括生产代码变动)
# refactor (重构代码)
# test (添加缺失的测试, 重构测试, 不包括生产代码变动)
# chore (更新grunt任务等; 不包括生产代码变动)
# --------------------
# 注意
# 主题和内容以一个空行分隔
# 主题限制为最大50个字
# 主题行大写
# 主题行结束不用标点
# 主题行使用祈使名
# 内容每行72个字
# 内容用于解释为什么和是什么,而不是怎么做
# 内容多行时以'-'分隔
# --------------------

使用模板

使用如下命令将模板文件指定为 Gitcommit.template

1
git config --global commit.template <.git-commit-template.txt file path>

例如:

1
git config --global commit.template ~/.git-commit-template.txt

使用插件

对于 Android Studio 或者 IDEA 来说,可以使用 Git Commit Template 插件,在图形化 Commit 界面使用该插件生成标准 Commit Message

Android 中使用线程有多种不同的方式,其中最根本的是通过 ThreadRunnable 派生线程对象。

但是,使用 ThreadRunnable 实现多线程的方式,无法有效的在任务结束之后将结果返回。为了解决这一问题,Java 额外提供了 CallableFutureTask 两种创建线程的方式,允许我们在线程执行完成之后得到返回的结果。

另外,基于 ThreadRunnableCallableFutureTask 的封装,Android 系统中还提供了如下多线程的实现方式:

  • AsyncTask:基于线程池和 Handler 的封装,通常用于执行需要更新 UI 的任务
  • HandlerThread:基于 ThreadHandler 的封装,接收来自 Handler 的消息,其内部只有一个线程在执行任务
  • IntentService:基于 ServiceHandlerThread 的封装,线程的优先级更高

本文将围绕线程和线程池的使用展开讨论。

阅读全文 »

Flutter 中,MethodChannel 允许我们发送与方法调用相关的消息。MethodChannel 适用于 Flutter 端和设备平台端进行直接的方法调用,这种方法调用是双向的,即既可以通过 MethodChannelFlutter 调用设备平台上的方法,也可以从设备平台上调用 Flutter 层的代码。

阅读全文 »

本文主要是对 Flutter 的基本框架进行介绍。本文主要参考了 美团技术团队-Flutter 原理与实践 一文。美团技术团队-Flutter 原理与实践 是一篇非常优秀的介绍 Flutter 原理的文章。

Flutter 的设计目标是高性能的跨平台的(AndroidiOSUI 框架。与传统的基于 WebView 的(例如 Cordova 或者 AppCan 等框架)或者基于原生控件系统的(例如 React Native)跨平台开发框架所不同的是,Flutter 是基于跨平台的渲染引擎 Skia 来实现的全新的跨平台的开发框架。这也就是说,Flutter 对于设备平台的依赖只有图形渲染相关的部分,这在一定程度上保证了不同平台、不同设备上的一致的用户体验。从执行效率的角度来说,Flutter 使用的 Dart 语言相较于其他大多数跨平台开发框架使用的 JavaScript 语言在执行效率上高效很多。

阅读全文 »