设计模式基础

概述

最近在负责一个新项目的基础搭建,过程中涉及到一些模块的划分、设计,之前也确实没有系统的学习过设计模式相关的知识,因此就花了些时间看了下设计模式,记录下来。本篇为设计模式的一些基础概念,但我觉得受益匪浅,当你掌握了基本概念和一些理论,才能更好地去从更高的层次去理解、设计一个模块,一些新知识的学习也是如此,Rx、RN,这些框架的思想都很重要,当然最终还是要回归到实践,才能更深地理解和掌握某一个新技术和新知识点。后面会有设计模式相关的文章,除了一些设计模式的具体介绍也会对一些优秀开源库从设计方面做一些分析,有兴趣的,可以关注下。

一 设计模式的分类

目的准则

模依据其目的可分为创建型 (Creational)、 结构型(Structural) 、 或行为型(Behavioral) 三种。

  • 创建型模式与对象的创建有关
  • 结构型模式处理类或对象的组合
  • 行为型模式对类或对象怎样交互和怎样分配职责进行描述

范围准则

指定模式主要是用于类还是用于对象

  • 类模式处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时刻便确定下来了
  • 对象模式处理对象间的关系,这些关系在运行时刻是可以变化的,更具动态性
  • 创建型类模式将对象的部分创建工作延迟到子类,而创建型对象模式则将它延迟到另一 个对象中

Read More

multidex与插件化

前言

随着应用越来越大,很多大型应用都会遇到方法数的爆棚以及方法信息存储区的问题,该篇文章主要以这两种问题为背景,介绍dex拆分、加载以及插件化方案的一些技术点。

65536 与 INSTALL_FAILED_DEXOPT

  1. 生成的apk在android 2.3或之前的机器上无法安装,提示INSTALL_FAILED_DEXOPT

  2. 方法数量过多,编译时出错,提示:

    1
    Conversion to Dalvik format failed:Unable to execute dex: method ID not in [0, 0xffff]: 65536

Android 2.3 INSTALL_FAILED_DEXOPT 的问题

该问题由dexopt的LinearAlloc限制引起的, 在Gingerbread或者以下系统LinearAllocHdr分配空间只有5M大小的。

Dalvik linearAlloc是一个固定大小的缓冲区。在应用的安装过程中,系统会运行一个名为dexopt的程序为该应用在当前机型中运行做准备。dexopt使用LinearAlloc来存储应用的方法信息。当方法数量过多导致超出缓冲区大小时,会造成dexopt崩溃。 目前Android 4.x提高到了8MB或16MB,在很大程度上解决了这个问题。

超过最大方法数限制的问题

该问题是由DEX文件格式限制。一个DEX文件中method个数采用使用原生类型short来索引文件中的方法,也就是4个字节共计最多表达65536个method,field/class的个数也均有此限制。对于DEX文件,则是将工程所需全部class文件合并且压缩到一个DEX文件期间,也就是Android打包的DEX过程中, 单个DEX文件可被引用的方法总数(自己开发的代码以及所引用的Android框架、类库的代码)被限制为65536;

解决方案

  • Android系统的LinearAlloc空间大小,在高版本上已经提升了,所以在一定程度上,不会出现这个问题,Facebook则采用了一种hack的方式,直接修改虚拟机的LinearAlloc空间大小:Dalvik patch for Facebook for Android
  • 拆分Dex,官方有提供方案Google Multidex,让一个apk里支持多个.dex文件,这样就可以突破65536的限制,但该方案有一定的弊端,下面会提到
  • 插件化,将整个工程的划分为Host+多个插件的形式,这样就可以将方法分散到各个插件中,按需加载各个模块。从软件工程的角度来看,这种方案会更好,动态部署,并行开发,更高的编译效率,但这种方案对已有的大项目来讲,重构起来影响非常大,实现成本也相对较高
  • 使用ProGuard等其它优化工具清除项目中无用方法,第三方库中的方法进行清除处理,这个可以作为优化的手段,但对于大型应用上,并不能从根本上解决方法数的问题

上面也提到,在高版本上,LinearAlloc方法区的空间已经扩大了很多,所以LinearAlloc的问题很少会再出现。采用dex的拆分以及插件化后并按需加载,对避免LinearAlloc的问题也更有益。

Read More

Android性能优化-减小图片下载大小

原文链接 https://developer.android.com/topic/performance/network-xfer.html

内容概要

  1. 理解图片的格式 PNG JPG WebP

  2. 如何选择一种图片格式

  3. 确定准确质量值

  4. 服务端的尺寸大小

减小图像下载大小

大多数流量传输都包含图像。 因此,你制作的下载的图片越小,就会为用户提供更好的网络体验。 本篇文章提供了让你的图像文件更小以及更加网络友好的指导。

理解图像的格式

Android应用通常使用以下某种或多种文件格式的图片:PNG,JPG和WebP。 对于每种格式,你都可以采取措施缩小图片大小。

PNG

缩小PNG文件的关键是减少构成图像的每行像素中使用的唯一颜色数。通过使用更少的颜色,可以提高在其它管道中的压缩能力。

减少独唯一颜色的数量会取得显着效果的原因是,PNG压缩效果基于一个水平方向相邻像素颜色变化程度的函数。因此,减少PNG图像每行中唯一颜色数量可以减少文件大小。

当决定是否采用这种策略时,你应该记住,减少唯一颜色的数量实际上等于对图像应用了有损编码。然而,编码工具可能不会判断一个细小的偏差导致多糟糕的视觉效果。因此,为了保证有效压缩和可接受的图像质量的平衡,你应该手动执行此工作,

有两个特别有用的方法你可以采取:优化索引格式和应用矢量量化。

优化索引格式

任何减少颜色的尝试都应该首先尝试优化颜色,以便将图像导出为PNG时可以使用INDEXED格式。 INDEXED颜色模式会选择最佳的256色彩,并用索引将所有像素值替换到调色板中。 结果是从1600万色彩减少到仅256色彩:等同于从每像素3(没有透明度)或4(具有透明度)字节减少到每像素1字节。这种变化向减少文件迈出了重大的一步。

Read More

Android性能优化-线程性能优化

原文链接:Better Performance through Threading

线程的性能

熟练使用Android上的线程可以帮助你提高应用程序的性能。 本篇文章讨论了使用线程的几个方面:使用UI或主线程; 应用程序生命周期和线程优先级之间的关系; 以及平台提供的帮助管理线程复杂性的方法。 在每一部分,本篇都描述了潜在的陷阱以及如何避免它们的策略。

主线程

当用户启动你的应用程序时,Android会创建一个新的 Linux process 以及一个执行线程。 这个main线程,也称为UI线程,负责屏幕上发生的一切。 了解其工作原理可以帮助你使用主线程设计你的应用程序以获得最佳性能。

内部细节

主线程具有非常简单的设计:它的唯一工作就是从线程安全的工作队列中取出并执行工作块,直到应用程序被终止。 框架从各个地方生成一些这些工作块。 这些地方包括与生命周期信息,用户事件(如输入)或来自其他应用程序和进程的事件相关联的回调。 此外,应用程序还可以在不使用框架的情况下显式地将工作块加入队列。

应用程序执行的任何代码块都会被绑定到一个事件回调上,例如输入,布局填充或绘制。 当某个时间触发一个事件时,事件发生的所在线程会将事件加入到主线程的消息队列。 之后主线程可以处理该事件。

当发生动画或屏幕更新时,系统试图每16ms左右执行一个工作块(负责绘制屏幕),以便以每秒60帧的速度平滑地渲染。 为了让系统达到这个目标,一些操作必须发生在主线程上。 但是,当主线程的消息队列包含太多或太耗时的任务,为了让主线程能够在16ms内完成工作,你应将这些任务移到工作线程中去。 如果主线程不能在16ms内完成执行的代码块,则用户可能感觉到卡顿或UI响应较慢。 如果主线程阻塞大约5秒钟,系统将显示“(ANR)”对话框,允许用户直接关闭应用程序。

从主线程移除多个或耗时的任务,以便它们不会干扰到平滑渲染和对用户输入的快速响应,是你在应用程序中采用线程的最大原因。

线程和UI对象的引用

按照设计,Android UI对象不是线程安全的。 应用程序应该在主线程上创建,使用和销毁UI对象。 如果尝试修改或甚至引用除主线程之外的线程中的UI对象,结果可能是异常,静默失败,崩溃和其他未定义的错误行为。

UI对象引用导致的问题可以划分为两种:显式引用和隐式引用。

Read More

Android性能优化-减小APK大小

原文链接:Reduce APK Size

前言

用户通常会避免下载比较大的应用,特别是连接到2G和3G网络,或者按流量收费的设备。这篇文章描述了如何减小apk的大小,帮助你让更多的用户下载你的app。

一 理解APK的结构

在讨论如何减小apk大小之前,理解apk的结构很有必要。一个APK文件包括一个ZIP 文件,该ZIP包含app的所有文件。包括java 字节码文件,资源文件和一个包含了编译后的资源文件。APK包含以下目录:

  • META-INF/:包含了CERT.SF 和 CERT.RSA 签名文件, 以及 MANIFEST.MFmanifest 文件.
  • assets/: 包含了app的assets,app可以通过 AssetManager 对象获取到这些资源
  • res/: 包含了那些没有被编译到 resources.arsc的资源
  • lib/: 包含了用于软件处理器的编译代码,该目录包含一个子目录,针对不同平台: armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, and mips.

一个APK也包含了下面的文件,但只有 AndroidManifest.xml 是强制性的

  • resources.arsc:

    包含了编译后的资源。该文件包含了 res/values/文件夹下的所有XML内容。打包工具抽取了XML内容,将它编译成二进制格式,并且进行了压缩。该内容包括language strings和styles,以及未直接包含在resources.arsc 文件中的内容路径。比如layout文件和图片。

  • classes.dex:

    包含可以被Dalvik/ART 识别,以dex文件格式编译后的代码

  • AndroidManifest.xml:

    包含了Android核心mainfest文件。该文件罗列了app名字,版本,访问权限,和引用的library文件。该文件采用二进制XML格式。

二 减少资源的数量和大小

APK的大小对app的加载速度以及内存的使用和电量消耗都有影响。一种减小APK大小的最简单方法就是减少APK的资源文件数量和大小。也可以移除那些app不再使用的资源,或者使用可扩展的 Drawable 对象替代图片文件。这部分讨论了这些方法,以及其它几种减小app资源以便最终达到减小APK总体大小的其它方法。

移除无用资源

使用 lint 工具,AndroidStudio中的一个静态的代码分析工具。可以检测res/ 目录下那些没有被引用的资源. 当 lint工具发现了项目中潜在的无用资源,就会打印类似如下的信息:

1
2
res/layout/preferences.xml: Warning: The resource R.layout.preferences appears
to be unused [UnusedResources]

注意: lint 工具不能够扫描assets/ 目录, assets 资源是通过反射的方式引用的,或者app中引用的其它library 文件。但lint并不会移除资源,它只会提示它们的存在。

你引入的Libraries有可能引入了无用的资源。Gradle可以通过在 build.gradle 文件中开启shrinkResources 来帮你自动的移除这些资源:

Read More

Android性能优化-内存优化

原文链接 Manage Your App’s Memory

前言

在任何软件开发环境中,RAM都是比较珍贵的资源。在移动操作系统上更是这样,因为它们的物理内存通常受限。尽管在ART和Dalvik虚拟机都会进行垃圾回收的巡航,但这并不意味着你可以忽略何时,何地分配和释放内存。你应该避免内存泄露,通常此后又一些静态成员变量导致,也应该在恰当的时间(定义的一些生命周期回调的方法里)释放所有Reference 对象。

这里见识了你如何减少app内存的使用。因为android是基于java的,所以对于内存的管理,可以参考java相关的书籍,下面章节中也会有所讲解。关于如何分析app运行中的内存使用,可以参考 Tools for analyzing RAM usage。关于ART和Dalvik虚拟机管理内存的更多细节,可以参考 Overview of Android Memory Management.

一 监测可用内存和内存使用

Android 框架,AndrStudio和Android SDK都提供了分析app内存使用的途径。Android框架暴露了几个API,允许你的app动态的减少内存使用、AndroidStudio和Android SDK提供了几种工具帮你分析app的内存使用情况。

分析RAM使用的工具

  1. Device Monitor 拥有一个 Dalvik Debug Monitor Server (DDMS) 工具,可以帮助你检测app进程中内存的分配。你可以通过该信息去分析app的总体内存使用情况。比如,先执行垃圾回收事件然后再去看那些仍然保留在内存中的对象。通过这种方式去定位app中所进行的内存分配或者遗留在内存中的对象。

    更多关于DDMS的使用请参考 Using DDMS

  2. Android Studio中的Memory Monitor 可以向你展示某一个过程中的内存分配情况。该工具以图形化的方式展示了某一时段可用的和已经分配的java内存,以及发送的垃圾回收事件。也可以触发垃圾回收事件并获取app运行期间java堆内存的快照。Memory Monitor tool 的输出信息也可以帮你定位到app密集发生垃圾回收事件的点,这些点会降低了app速度。

    关于如何使用Memory Monitor tool的更多信息可以参考 Viewing Heap Updates.

  3. 垃圾回收事件也会展示在 Traceview viewer中。 Traceview 允许你以时间线的方式查看trace log文件,并可以分析一个事件段内都发生了什么。你可以使用该工具确定在你的代码在垃圾回收事件发生时都做了什么操作。

    更多信息关于如何使用Traceview viewer, 可以参考 Profiling with Traceview and dmtracedump.

  4. Android Studio中的Allocation Tracker tool可以帮助你分析app是如何分配内存的。Allocation Tracker 记录了app内存的分配并在快照中列出了所有的分配对象。可以使用该工具追踪哪些地方分配了过多的对象。

    更多关于如何使用Allocation Tracker tool,可以参考 Allocation Tracker Walkthrough.

依据事件释放内存

根据RAM的物理内存和设备的操作行为,Android设备可以在变化的可用内存中运行。在内存压力的情况下,系统的广播信号会提示,app可以监听这些信号然后对内存的使用做恰当的处理。

可以使用 ComponentCallbacks2 来监听内存信号响应app生命周期或者设备的事件。onTrimMemory() 方法可以帮助你监听app在前台或者后台时内存相关的事件。

在Activity中实现onTrimMemory() 回调,如下:

Read More

Android性能优化-App后台优化

原文链接 Background Optimizations

前言

后台进程是内存和电池敏感的。一个隐式的broadcast可能会启动很多监听它的后台进程,即使这些进程可能做得工作不多。这可能丢设备性能和用户体验都有比较大的影响。

为了缓解这种问题,7.0(API 24)做了以下限制:

  • Target为 Android 7.0 (API level 24)的App,将不会再收到在mainfest中注册的 CONNECTIVITY_ACTION广播。运行中的App仍然可以在Main Thread中通过Context.registerReceiver()注册 CONNECTIVITY_CHANGE 广播来监听
  • App 将不能够发送或者接收 ACTION_NEW_PICTURE or ACTION_NEW_VIDEO。这种优化会影响到所有的app,不仅是target为Android7.0的设备。`

因此如果你使用了这些intennt,应该尽快的移除对它们的依赖,以便你的app可以在Target为Android 7.0的设备上正常运行。Android框架提供了几种解决方案去减小对这些隐式广播的依赖。比如,JobScheduler and GcmNetworkManager提供了强健的机制去调度特定情况下的网络操作。比如,你也可以使用JobScheduler去响应content provider的变化。JobInfo对象封装了JobScheduler用于调度job的参数。当满足指定的条件的时候,系统会通JobService过执行该job。

这篇文章将会告诉你如何使用替代的方法,比如JobScheduler去为你的app做这些限制的适配。

一 CONNECTIVITY_ACTION的限制

上面提到,Android 7.0 (API level 24) 将不再能够收到mainfest中注册的 CONNECTIVITY_ACTION 广播。Android框架中已经提供了几种替代方案,如何选择依赖于你的具体实现。

注意:一个通过 Context.registerReceiver()注册的BroadcastReceiver 在app运行期间是可以继续收到广播的。

Read More

Android性能优化-App启动优化

原文地址:https://developer.android.com/topic/performance/launch-time.html#common

通常用户期望app响应和加载速度越快越好。一个启动速度慢的app很可能会给用户留下不好的印象,除了导致用户在应用市场上的打分低之外,很有可能导致致用户直接卸载。

这篇文章提供了优化app启动时间的方法。先解释了app进程启动的内部流程。然后讨论如何优化启动的性能。最后列出几个常见的启动问题和解决方案。

一 启动内幕

App启动可能发生在以下三种状态 之一,每一种都会影响到展现给用户的时间:冷启动、热启动和温启动(翻译的有点怪,介于冷和热之间吧)。

冷启动下,app所做的事情不较多,其它两种情况,系统只需要将app从后台切到前台。建议你在冷启动的基础上做优化,这样也会提升热启动和温启动的性能。

为了更好地优化app的启动,了解系统和app层做了什么以及如何相互影响很有必要。

1.1 冷启动

冷启动指:在app启动之前,系统的进程还没有,直到app启动创建app的进程。冷启动会发生在device重启或者app被杀死的情况下。这种启动在优化启动时间上,有更大的挑战,因为相比其它两种启动方式,系统和app有更多的工作需要处理。

冷启动之前,系统会执行以下三个task:

1、加载并启动app

2、在app启动后,立即展示空白的window

3、创建app进程

一旦系统创建了app进程,那么app进程就会执行以下步骤

1、创建app对象

2、启动main thread

3、创建MainActivity

4、Inflate view

5、布置屏幕

6、进行首次绘制

Read More

JS学习-字符串和数组

一 字符串

字符串就是零个或多个排在一起的字符,放在单引号或双引号之中。

1
2
'abc'
"abc"

1.1 单引号与双引号

单引号字符串的内部,可以使用双引号。

双引号字符串的内部,可以使用单引号。

1
2
'key = "value"'
"It's a long journey"

单引号内部使用单引号,双引号内部使用双引号必须在前面加上反斜杠,用来转义:

1
2
3
4
5
'Did she say \'Hello\'?'
// "Did she say 'Hello'?"
"Did she say \"Hello\"?"
// "Did she say "Hello"?"

由于HTML语言的属性值使用双引号,所以很多项目约定JavaScript语言的字符串只使用单引号。

当然,只使用双引号也完全可以。重要的是,坚持使用一种风格,不要两种风格混合。

Read More

JS学习-封装和继承

封装

一 封装

1.1 prototype对象

1.1.1 构造函数的缺点

JavaScript通过构造函数生成新对象,因此构造函数可以视为对象的模板。实例对象的属性和方法,可以定义在构造函数内部。所有实例对象都会生成相同的属性。

但是,这样做是对系统资源的浪费,因为同一个构造函数的对象实例之间,无法共享属性和方法。所有创建的对象的方法功能是相同的,完全可以只定义一份。

1.1.2 prototype属性的作用

构造函数也有自己的属性和方法,其中有一个prototype属性指向另一个对象,一般称为prototype对象。该对象非常特别,只要定义在它上面的属性和方法,能被所有实例对象共享。也就是说,构造函数生成实例对象时,自动为实例对象分配了一个prototype属性。

  • 定义在prototype上面的属性和方法,能被所有实例对象共享
1
2
3
4
5
6
7
8
9
10
11
function Animal (name) {
this.name = name;
}
Animal.prototype.color = "white";
var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');
cat1.color // 'white'
cat2.color // 'white'

Read More