Android Html.from() 末尾大量空白去除
解决方法
1 | /** |
1 | /** |
全称:Model-View-Presenter
;MVP
是从经典的模式 MVC
演变而来,它们的基本思想有相通的地方 Controller
/ Presenter
负责逻辑的处理,Model
提供数据,View
负责显示。
模型与视图完全分离,我们可以修改视图而不影响模型
可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部
我们可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁。
如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)
由于对视图的渲染放在了 Presenter
中,所以视图和 Presenter
的交互会过于频繁。还有一点需要明白,如果 Presenter
过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更,那么 Presenter
也需要变更了。
MVVM
是 Model-View-ViewModel
的简写。它本质上就是 MVP
的改进版。MVVM
模式将 Presenter
改名为 ViewModel
,基本上与 MVP
模式完全一致。唯一的区别是,它采用双向绑定(DataBinding),View的变动,自动反映在 ViewModel,反之亦然。
其中的 VM
是 ViewModel
的缩写,ViewModel
可以理解成是 View
的数据模型和 Presenter
的合体,ViewModel
和 View
之间的交互通过 DataBinding
完成,而 DataBinding
可以实现双向的交互,这就使得视图和控制层之间的耦合程度进一步降低,关注点分离更为彻底,同时减轻了 Activity
的压力。
Model
变化时,View-Model
会自动更新,View
也会自动变化。很好做到数据的一致性,不用担心,在模块的这一块数据是这个值,在另一块就是另一个值了。所以 MVVM
模式有些时候又被称作:model-view-binder
模式。View
的功能进一步的强化,具有控制的部分功能,若想无限增强它的功能,甚至控制器的全部功几乎都可以迁移到各个 View
上(不过这样不可取,那样 View
干了不属于它职责范围的事情)。View
可以像控制器一样具有自己的 View-Model
。View
上处理,大大的对控制器进行了瘦身。不用再为看到庞大的控制器逻辑而发愁了。View
或 ViewController
的数据处理部分抽象出来一个函数处理 model
。这样它们专职页面布局和页面跳转,它们必然更一步的简化。Bug
很难被调试。你看到界面异常了,有可能是你 View
的代码有 Bug
,也可能是 Model
的代码有问题。数据绑定使得一个位置的 Bug
被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。model
也会很大,虽然使用方便了也很容易保证了数据的一致性,当时长期持有,不释放内存,就造成了花费更多的内存。View
,但是数据双向绑定技术,让你在一个 View
都绑定了一个 model
,不同模块的 model
都不同。那就不能简单重用 View
了。个人觉得 MVVM
有点像 H5
的 Vue
框架,微信小程序开发也是这种模式。
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性,以及类的关联关系和组合关系的充分理解。
当然,软件设计模式只是一个引导,在实际的软件开发中,必须根据具体的需求来选择:
单例模式,它的定义就是确保某一个类只有一个实例,并且提供一个全局访问点。
单例模式具备典型的3个特点:
因此当系统中只需要一个实例对象或者系统中只允许一个公共访问点,除了这个公共访问点外,不能通过其他访问点访问该实例时,可以使用单例模式。
单例模式的主要优点就是节约系统资源、提高了系统效率,同时也能够严格控制客户对它的访问。也许就是因为系统中只有一个实例,这样就导致了单例类的职责过重,违背了“单一职责原则”,同时也没有抽象类,所以扩展起来有一定的困难。
EventBus.getDefault()、Fragment 创建、网络请求工具类、SP 储存的工具类、弹窗工具类等等
所谓抽象工厂模式就是提供一个接口,用于创建相关或者依赖对象的家族,而不需要明确指定具体类。他允许客户端使用抽象的接口来创建一组相关的产品,而不需要关系实际产出的具体产品是什么。这样一来,客户就可以从具体的产品中被解耦。
它的优点是隔离了具体类的生成,使得客户端不需要知道什么被创建了,而缺点就在于新增新的行为会比较麻烦,因为当添加一个新的产品对象时,需要更加需要更改接口及其下所有子类。
作为抽象工厂模式的孪生兄弟,工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个,也就是说工厂方法模式让实例化推迟到子类。
工厂方法模式非常符合“开闭原则”,当需要增加一个新的产品时,我们只需要增加一个具体的产品类和与之对应的具体工厂即可,无须修改原有系统。同时在工厂方法模式中用户只需要知道生产产品的具体工厂即可,无须关系产品的创建过程,甚至连具体的产品类名称都不需要知道。虽然他很好的符合了“开闭原则”,但是由于每新增一个新产品时就需要增加两个类,这样势必会导致系统的复杂度增加。
Activity 基类
对于建造者模式而已,它主要是将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。适用于那些产品对象的内部结构比较复杂。
建造者模式将复杂产品的构建过程封装分解在不同的方法中,使得创建过程非常清晰,能够让我们更加精确的控制复杂产品对象的创建过程,同时它隔离了复杂产品对象的创建和使用,使得相同的创建过程能够创建不同的产品。但是如果某个产品的内部结构过于复杂,将会导致整个系统变得非常庞大,不利于控制,同时若几个产品之间存在较大的差异,则不适用建造者模式,毕竟这个世界上存在相同点大的两个产品并不是很多,所以它的使用范围有限。
Android Dialog 创建
何谓观察者模式?观察者模式定义了对象之间的一对多依赖关系,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并且自动更新。
RxJava 的运用
代理模式就是给一个对象提供一个代理,并由代理对象控制对原对象的引用。它使得客户不能直接与真正的目标对象通信。代理对象是目标对象的代表,其他需要与这个目标对象打交道的操作都是和这个代理对象在交涉。
代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了的作用和保护了目标对象的,同时也在一定程度上面减少了系统的耦合度。
AIDL 应用
职责链模式描述的请求如何沿着对象所组成的链来传递的。它将对象组成一条链,发送者将请求发给链的第一个接收者,并且沿着这条链传递,直到有一个对象来处理它或者直到最后也没有对象处理而留在链末尾端。
避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止,这就是职责链模式。在职责链模式中,使得每一个对象都有可能来处理请求,从而实现了请求的发送者和接收者之间的解耦。同时职责链模式简化了对象的结构,它使得每个对象都只需要引用它的后继者即可,而不必了解整条链,这样既提高了系统的灵活性也使得增加新的请求处理类也比较方便。但是在职责链中我们不能保证所有的请求都能够被处理,而且不利于观察运行时特征。
OkHttp 拦截器相关的封装
1 | public class Singleton { |
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
1 | public class Singleton { |
这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
1 | public class Singleton { |
这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
1 | public class Singleton { |
解决上面第三种实现方式的线程不安全问题,做个线程同步就可以了,于是就对getInstance()方法进行了线程同步。
缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。
1 | public class Singleton { |
由于第四种实现方式同步效率太低,所以摒弃同步方法,改为同步产生实例化的的代码块。但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
1 | public class Singleton { |
Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。
优点:线程安全;延迟加载;效率较高。
1 | public class Singleton { |
这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
优点:避免了线程不安全,延迟加载,效率高。
1 | public enum Singleton { |
借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过。
首先附上启动流程:Launcher 点击图标开始
Launcher startActivity
AMS
查找应用进程是否创建AMS
通知从 Zygote
进程中 fork
创建出一个新的进程分配给该应用 AMS
通过 Binder
通知应用进程performLaunchActivity
attachBaseContext()
, onCreate()
new Activity()
, 并为此 Activity
创建一个new PhoneWindow(this)
onCreate()
setContentView()
new DecorView()
& addView(contentRoot, contentParent)
onFinishInflate()
: 此步骤只是 inflate
所有的 DecorView
上的布局 views
,并不可见 onStart()
onResume()
window.addView(mDecorView)
onAttachedToWindow()
、 onMeasure()
、 onSizeChanged()
、 onLayout()
、 onDraw()
DecorView
加给 Window
, 应用首页才可见, onWindowFocusChanged(true)
onPause()
onWindowFocusChanged(false)
onStop()
onDestroy()
onDetackedFromWindow()
在业务代码中,Application
启动时经常会初始化很多项目内的第三方库,这对 App 的启动会有很大影响,所以尽量把一些不太紧要的初始化异步执行,把必要的一些初始化才放到 onCreate
中执行。
至于异步初始化的方法则有:其他线程
、IntentService
、WorkManager
、MessageQueue.addIdleHandler()
等等进行处理。
IntentService
Android 8.0+ 由于Service
后台被限制已经弃用,推荐使用JobIntentService
或Jetpack
组件包的WorkManager
进行代替使用。
在首次安装后或冷启动时,从 Launcher 中点击图标到闪屏页,在性能一般的机器上面,会有短时间的白屏或黑屏,这样对用户而言显然是不太好的。
我们可以在闪屏页配置一个背景,通常是一个和闪屏页相似的背景,然后就不会出现白屏或黑屏,而是设置的闪屏页背景,然后在显示闪屏页。
闪屏页即启动的第一个 Activity,App 启动速度和闪屏页的 onCreate 方法也有关系,所以尽量不要在闪屏页的 onCreate 做太多操作。
启动页主题
1 | <style name="SplashTheme" parent="@android:style/Theme.Light.NoTitleBar.Fullscreen"> |
splash_bg
可以是图片也可以是自定义的 layer
组合。
1 | <activity |
Android 低版本4.x及以下,SDK < 21
的设备,采用的 Java
运行环境是 Dalvik
虚拟机。它相比于高版本,最大的问题就是在安装或者升级更新之后,首次冷启动的耗时漫长。这常常需要花费几十秒甚至几分钟,用户不得不面对一片黑屏,熬过这段时间才能正常使用 APP。这个问题的根本原因就在于,安装或者升级后首次 MultiDex
花费的时间过于漫长。
DEX
字节码,避免 ODEX
耗时DEX
字节码、DEX
文件、ODEX
文件中选取最合适的产物启动 APPOPT
,并实现合理的中断及恢复机制Redex
是 Facebook
的一个开源工具,官方的介绍是 An Android Bytecode Optimizer
,Android 字节码优化器
。可以减小 Android
Apk
的大小和提高 App
启动速度。
根据官方文档的说明,主要的优化项有以下几点:
1、混淆和压缩
类似 Android
中的 proguard
,将字节码中的类名、方法名等替换成简短的字符串。
2、内联函数functionA
-> functionB
-> functionC
,内联后变成 functionA
(包含 functionB
代码)-> functionC
,减少函数调用时间。
3、删除代码
类似于标记回收算法,从某些入口函数可以遍历标记出可以访问到的方法,最后未标记到的方法可被移除。理论比较简单,但在 Android
中还有反射,或者资源文件中对代码的引用等特殊情况需要考虑到。
4、基于反馈的class分布
也就是可以对 Dex
进行重组,根据使用方提供的 App
启动时加载的类序列配置文件调整 Dex
中类的顺序,把 App
冷启动时需要加载的类,放到 Dex前部。
5、删除接口
删除只有一个实现类的接口,直接使用实现类。
6、删除 MetaDataDex
文件中的一些元数据在运行中并不会使用到。所以可以用 Dex
中已有的字符串代替 Java
源文件引用,删除运行时使用不到的注解。
提前加载 SharedPreferences
在 Multidex
之前CPU是空闲的,加载系统类是可行的,所以可充分利用这段时间加载SharedPreferences
。
启动阶段不启动子进程
初始化子进程会消耗CPU资源,在启动阶段会导致主进程CPU资源紧张,导致启动阶段资源紧张初始化过慢。
启动阶段不启动 Service
、ContentProvider
在 Application
生命周期方法 attachBaseContext
方法和 onCreate
方法中间还会执行ContentProvider
生命周期方法,如果在 Application
初始化过程中启动了 Service
或者 Contentprovider
会执行 ContentProvider
生命周期方法,非常耗时。
提前异步类加载
对启动阶段用到的类进行提前异步加载,加载方法有两种:
Class.forName()
只加载类本身及其静态变量的引用类。new 类实例
可以额外加载类成员变量的引用类。启动阶段抑制 GC
支付宝就用了该方案
CPU
锁频
单条依赖排除引用的某个第三方库
1 | implementation("android.support.v7.widget.Toolbar") { |
全局配置排除
在需要排除的模块的配置文件 build.gradle
中,顶层插入下方配置相关的内容,在 all
内一行行添加需要排除的库即可,就不用在单个依赖下去排除。
1 | configurations { |
在 Android
项目的配置文件 build.gradle
中的 android
块中添加配置,重新同步即可
1 | android { |
1 | android { |
mainDexClasses.pro
1 | # 这里只是示例 |
先直接贴代码:
1 | /** |
然后在 BottomSheetDialogFragment 的 onStart 回调内调用该拓展方法即可,也可传入相关参数调整样式。
1 |
|
查看 BottomSheetDialog
源码,在下面的方法中,引入了一个布局 design_bottom_sheet_dialog.xml
,其中的 bottomSheet
就是我们自定义传入视图的容器控件。
1 |
|
布局文件 design_bottom_sheet_dialog.xml
内容如下
1 |
|
可以看出 bottomSheet
默认会去引入一个全局的主题样式 bottomSheetStyle
,点进去发现是引入的 Widget.MaterialComponents.BottomSheet.Modal
,挨着挨着查找下去:
1 |
|
很容易可以看出,这个默认白色背景的来源了。
需要用到的环境和软件:
下载官方 Jdk17 文件:
1 | mkdir /usr/local/java/jdk17 |
解压
1 | tar xf /home/jdk17/jdk-17_linux-x64_bin.tar.gz -C /usr/local/java/jdk17 |
设置环境变量
gedit /etc/profile
,在文件最下方添加以下内容:
后面的版本号根据你自己解压下来的 Jdk 文件夹版本名称而定
1 | # JAVA |
下载最新版本的 Android Studio,下载完成后解压到你想要的目录即可。
1 | wget https://redirector.gvt1.com/edgedl/android/studio/ide-zips/2021.1.1.21/android-studio-2021.1.1.21-linux.tar.gz |
我这里解压后,将解压出来的 android-studio
目录放到了 /usr/local/
目录下:
1 | /usr/local/android-studio |
进入 android-studio/bin
目录,执行 ./studio.sh
启动软件。
启动成功后,配置完成,记录下你配置的 SDK 目录,建议一路配置都用默认即可
我这里的 SDK 目录默认为:
1 | /root/Android/Sdk |
然后进入新建项目的界面,我们不用新建项目,点击左侧的 Customize -> All settings
,在设置搜索框里输入 SDK
按回车键。
然后点开 Android SDK
的配置页面:选中 SDK Tools
Tab,勾选中 NDK (Size by side)
和 cmake
,然后点击 Apply
,等待下载完成关闭 Android Studio。
然后进入 SDK 目录,你会发现 cmake
和 ndk
都在该目录内。
注意:
cmake 目录下没有直接放相关文件,而是多了一层版本号的文件夹,这里需要修改一下,将版本号文件夹内的全部文件复制一份到上一级目录下,免得后面执行编译脚本报错。
cmake
路径修改,$SDK/cmake/3.18.1/xxxxx -> $SDK/cmake/xxxxx
添加环境变量
gedit /etc/profile
,在文件最下方添加以下内容:
版本号和你的有差异请自己替换
1 | # ANDROID_SDK |
然后刷新配置
1 | source /etc/profile |
此时可以输入:java -version
、cmake
、 ndk-build
进行检测是否配置好
在 /root
目录新建一个文件夹 Work
,进入该文件夹:
下载 Github 源码:
1 | git clone https://github.com/FreeRDP/FreeRDP.git |
开始编译
进入克隆下来的 FreeRDP 目录,输入以下命令开始编译
1 | ./scripts/android-build-freerdp.sh --ndk $ANDROID_NDK --sdk $ANDROID_SDK --release |
若需要编译 H264,修改 android-build-release.conf
开启 H264 WITH_OPENH264=1
1 | ./scripts/android-build-freerdp.sh --ndk $ANDROID_NDK --sdk $ANDROID_SDK --openh264-ndk $ANDROID_NDK_15C --openh264 --release |
等待编译完成,若前面的步骤有错误,期间可能会编译报错,看错误提示解决后,重新执行上面的编译命令即可。
查看编译好的 so 文件
编译完成后,so 存放目录为:
1 | /root/Work/FreeRDP/client/Android/Studio/freeRDPCore/src/main/jniLibs |
剩下的就是把这个导出来,放到 Android Studio 相关的项目里就行了,具体相关的源码可以访问 FreeRDP Github 地址:https://github.com/FreeRDP/FreeRDP