Gitlab ci 调试笔记 (一)

Gitlab ci 调试笔记 (一)

通常情况下,我们对ci的认识都停留在构建工具这一层面,对于许多使用方面的细节并不关注,所以我想写一下近段时间修改ci文件的一些细节。

1.release分支的问题

build_prod:
  stage: build_prod
  only:
    - /^release-.+/

通常我们会这样写线上的构建条件,但如果某些情况下,有一个分支被命名为release-xxx,就会错误的触发线上环境构建。

我们可以这样修改:

build_prod:
  stage: build_prod
  only:
    variables:
      - $CI_COMMIT_TAG =~ /^release-.+/

如果构建分支有tag,则ci会自动设置”CI_COMMIT_TAG”这个环境变量,在only中使用环境变量去判断,从而确保只有release-xxx的tag会触发构建。

2.手动操作

ci作为一款自动化工具,通常情况下所有的操作都由某些事件的触发而自动执行和完成,但有时,我们希望能受控的执行一些操作

deploy_prod:
  when: manual
  only:
    variables:
      - $CI_BUILD_TAG =~ /^release-.+/

只需要添加”when: manual”即可达到我们的期望,满足条件时,在ci/cd面板上会显示执行按钮,点击该按钮来执行我们期望的操作。

3.使用ssh密钥

ci能执行shell,那么,能不能ssh连接其他服务器,执行一些操作呢,显然是可以的,只需要配置一下。

使用ssh有一些前提,比如ssh密钥,同时还需要信任服务器(这一步我们在本地通常都是随手输入y信任)

我们将ssh密钥预先生成好,并放置到环境变量里待用,同时将KNOWN_HOSTS也放置到环境变量里。

publish:
  before_script:
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts

似乎有点长,我们来解读一下。

首先判断一下ssh-agent是否存在,不存在则安装一下,
安装完成后运行ssh-agent,并将密钥和HOSTS添加进来,就完成了配置工作,接下来做什么就自行发挥了,帮你commit & push代码也是可以的。


本期内容不多,等下期再完善一下

如何设计一个好的前端架构

前言

什么时候好的前端架构,就是当有新的开发人员接触项目是,不会在理解数据跟踪及其背后体现的UI路径时不至遇上太多的问题。

什么样式好的架构

  • 易于管理
  • 方便理解
  • 规划清晰
  • 更新便捷
  • 组件化程度高
  • 流程方便

页面

页面代表着web应用中不同的目标,页面目录中的文件或者目录,代表着路由路径的目的地。这样,当我们通过路由并拆分出组件时,就能够便捷的径路与页面文件关联起来。
* 仅包含路由入口文件以及其所需要组建的关联
* 入口文件不应该包含完整逻辑,应该见逻辑根据功能拆分至不同的组件
* 规范命名,因为该文件代表着打包后的文件与路由组件

组件

组件越小,就越易于处理。将UI拆分成一个个小的组件。代码越少,我们对代码的掌控能力就越强,调试与必要时的更新就会越简单。可以通过以下方式:
* 将公共组件统一放到一个目录中
* 将每个文件的组件进行分类,确保其中不包含公共代码组件
* 尝试对组件进行概括,以便以后能在不同的场景中使用
* 将彼此相关的组件划分在一起。确保这些组件不会在目录之外的组件中使用

辅助函数

辅助函数应该强大且中立,辅助函数应该与渲染逻辑区分开,仅在需要使用的时候引用。其作用在于:
* 处理特定组件的逻辑
* 与浏览器规范相关
* 处理从后端接收到的数据,使其适用于业务
* 将公共的辅助函数划分到一起,便于管理

API服务

API服务是指负责在参数特定的情况下,调用服务器以获取数据的代码。我们不应该直接从UI逻辑中调用服务。因为如果我们需要在很多位置实现相同的API调用,name对不同位置进行修改迭代将变得非常困难。
* 应该将API服务进行封装,单独做一个服务来实现
* 应将从服务器接收的数据直接返回给组件
* 应该能接收配置或者变量等,作为API服务的必要参数进行传递

Config

Config 当中应包含web应用运行所在的环境具体配置。确保将配置与实际代码拆分出来。
* 使用不同的文件对应不同的环境类型
* 根据获取不同的资源类型而有所不同

路由

路由是保障web应用使用体验的主要方式,路由决定这我们在应用中需要显示的不同页面的URL格式或者模式。
* 路由的命名应该尽可能简短
* 尽可能保持路由的正确顺序

Static文件

Static文件是指未包含在逻辑当中的文件。
* 应该根据其类型进行分组
* 尽可能降低文件体积

其他

  • 如果是在用npm管理的话,package.json 应该有完善的相关命令,来保证开发人员流程畅通
  • readme 要写的足够详尽,因为一个开发如果要想了解一个项目的话,都会先阅读readme

以上就是我总结的一些想法,现在前端发展迅速,整个设局对于项目架构的思路也是日新月异,我只是希望我写的这些能起到一些帮助。

Android Lint扫描规则说明(二)

主要内容

对Android Studio支持的六类Android Lint规则, 本文主要对Performance包含的32个项的说明,主要内容都是文档翻译,适当加一些自己的感想。

分类详细说明

高效使用资源

UnusedIds

未被使用的资源id,在layout文件中定义了资源ID从未被使用过,但有时候它们可以让layout更容易阅读,没有必要删除未使用的资源id。

Overdraw

过度绘制:一个绘制区域被绘制的次数多于一次。

如果给一个root view设置了背景图,就要给它设置一个background=nulltheme,否则绘制过程会先绘制themebackground,然后再绘制设置的背景图,完全覆盖之前绘制的theme.background,这是 过度绘制

这个检测器依赖于根据扫描Java代码找出哪些布局文件与哪些Activity相关联,目前它使用的是一种不精确的模式匹配算法。因此,可能会因错误地推断布局与活动的关联而给出错误的提醒。

如果想把一个背景图应用在多个页面上,可以考虑自定义theme,并把背景图设置在theme里,在layout中设置theme代替设置background。如果背景图中有透明的部分,并且希望他和theme的背景有层叠效果,那么可以选择先把两个背景合并成一个背景图之后,在定义到theme里。

VectorPath

关于SVG的使用,给出一篇参考文章:Android vector标签 PathData 画图超详解,Android Studio可以创建使用SVG绘制出的drawable图像资源。

UselessLeaf

没有包含任何View,也没有设置背景的Layout是多余的,可以去掉。让界面更趋于扁平,嵌套更高效。

UselessParent

如果一个包含ViewLayout没有兄弟层级的Layout,而他的外部ViewGroup又不是ScrollView或者root级别,那么这个Layout可以移除,让他包含的View直接包含在它的父层级的Layout中。让界面更趋于扁平,嵌套更高效。

TooDeepLayout

Layout嵌套过深会影响性能,考虑使用平铺类型的Layout代替。默认最深的View嵌套是10层,也可以通过环境变量ANDROID_LINT_MAX_DEPTH进行设置。System.getenv("ANDROID_LINT_MAX_DEPTH");语句获取,如何设置还没找到。

TooManyViews

Layout内有太多的View:一个Layout文件内有过多的View会影响性能。考虑使用复合drawables或其他技巧来减少这个布局中的视图数量。默认最多的数量是80个,可以通过环境变量ANDROID_LINT_MAX_VIEW_COUNT进行设置。据说这个变量可以用System.getenv("ANDROID_LINT_MAX_DEPTH");语句获取,如何设置还没找到。

NestedWeights

Weight嵌套:使用非0layout-weight值,需要Layout被测量两次,如果一个包含非0值的LinearLayout被嵌套在另一个包含非0值的LinearLayout内部,那么,测量次数就会呈指数级增长。

DuplicateDivider

这个主要是讲RecyclerView的分割线,com.android.support:recyclerview-v7 提供了一个类DividerItemDecoration设置分割线样式,这个类在早期的版本内没有包含,所以在更新为新的版本后,可以使用这个类重新设置分割线。
具体使用,参考文章:Android RecyclerView 使用完全解析

MergeRootFrame

FrameLayout在一个layout文件中是root且没有使用background或者padding等属性,通常使用一个merge标签代替FrameLayout会更高效。但是这要看上下文设置,所以在替换之前要确认你已经理解了merge标签的工作原理

UnusedResources

未使用的资源:多指的是drawable类型的资源。多余的drawable资源会让APP变大,编译过程变长。

InefficientWeight

当LinearLayout只有一个Widget且使用了android:layout_weight时,定义对应的width/height的值为0dp,Widget就会自动占满剩余空间。因为不需要预先计算自己的尺寸,这种方式更高效。

高效的设置

DisableBaselineAlignment

在使用LinearLayout实现空间的按比例分割时,LinearLayout的空间用layout_weight属性在所包含的几个layout中间分割,那么应该设置被分割LinearLayoutbaseLineAligned="false",这样可以加快分割空间所做的运算。

LogConditional

LogConditional:使用android.util.Log打印调试日志,一般只会在DEBUG模式下使用,在release是不需要打印调试日志的,在buildToolsVersion大于等于17时, BuildConfig提供两个一个DEBUG常量来标记是否处于DEBUG模式,我们可以用if(BuildConfig.DEBUG){}包裹调试日志语句,这样编译器会在编译生成release包时,删除这些语句。如果真的需要在release模式下打印调试日志,可以使用@SuppressLint("LogConditional")注解告诉编译器在release包中保留这些日志信息。

UnpackedNativeCode

APP使用System.loadLibrary()加载Native库时,android 6.0或者更新的版本可以在Manifest文件中application标签中添加属性android:extractNativeLibs="false",这样可以提交加载速度,降低APP占用的存储空间。

更高效的替代方案

FloatMath

不要使用FloatMath类进行数学计算,推荐使用Math类。

Android早期版本因为浮点运算性能的原因,推荐使用FloatMath代替Math类进行数学计算。随着硬件和系统的发展,这个问题已经不复存在,甚至经过JIT优化之后的Math类运算速度会比FloatMath更快,所以,在Android F以上版本的系统上,可以直接使用Math类,而不是FloatMath。

UseValueOf

某些类构造新对象时,建议使用工厂方法,而不是new关键字声明新的对象。例如,new Intger(0)就可以使用Integer.valueOf(0)替代,工厂方法会使用更少的内存,因为它会让值相等的对象使用同一个实例。

ViewHolder

在给ListViewGradView之类的列表实现Adapter时,不能每次getView调用都去inflate一个新的layout,如果接口参数中给出了一个可以复用的View对象,就可以使用这个对象而不是重新生成。这个应该都很熟悉,也很简单基础了。

UseSparseArrays

KeyInteger类型的HashMap可以使用SparseArray代替,性能更好。可以使用替代HashMap的有SparseBooleanArray、SparseIntArray、SparseLongArray和泛型类SparseArray,每个对应的类型代表Value的类型。如果在某些情况一定要用HashMap实现,则可以用@SuppressLint注解抑制Lint检查。

WakelockTimeout

关于week lock的使用,这里提供一篇博客文章:Android 功耗分析之wakelock

UseCompoundDrawables

在一个TextView的四周有只具有展示作用的ImageView时,建议删除ImageView改用compound drawables:drawableTop, drawableLeft, drawableRight,drawableBottom,drawablePadding替代方案实现。

有关泄漏的提醒

Recycle

缺少recycle()调用:许多资源例如:TypedArrays, VelocityTrackers在使用完之后需要调用recycle()方法回收资源。

ViewTag

4.0版本系统之前,View.setTag(int, Object)的实现方式中,会把Object存储在一个静态的map里并且使用的是强引用。这就意味着如果这个Object包含了对Context对象的引用,这个Context就是泄漏了。

传递一个View做参数,这个View就能提供一个对创建它的Context的引用。类似的,View holders内包含View,也会有Context与这个View相关联。

HandlerLeak

Handler引用泄漏:声明Handler的子类如MyHandler为内部类,如果MyHandler类对象关联Looper.getMainLooper()或者Looper.getMainLooper().getQueue()时,会阻止无用的外部类对象被垃圾回收,导致泄漏。如果对应main thread 的关联,就不会有这个问题。

应对方法,声明MyHandler为静态内部类,并用WeakReference的方式持有一个外部类对象,MyHandler使用这个对象操作外部类的属性和方法。

DrawAllocation

绘制过程中的内存分配:避免在布局绘制过程中分配内存给新的对象。因为这些操作调用频率比较高,频繁分配内存会唤起垃圾回收,中断UI绘制,导致卡顿。

StaticFieldLeak

非静态内部类具有对其外部类对象的隐式引用。

如果外部类Fragment或者Activity,那么这个引用意味着长时间运行的处理程序/加载器/任务(handler/loader/task)将持外部类对象的引用,从而防止外部类对象被回收。

同理,长时间运行的处理程序/加载器/任务(handler/loader/task)对Fragment或者Activity的直接引用,也会造成泄漏。

ViewModel类应该禁止引用View或者non-application类型的Context对象。

代码提醒

AnimatorKeep

属性动画默认支持的属性如下面列表。如果超出这些范围,会通过反射调用本地定义的函数。声明一个属性动画对象例如:ObjectAnimator.ofFloat(view, "rotation", 0, 360) 中的“rotation”就是要操作的属性,如果属性不在下面的列表中例如ObjectAnimator.ofFloat(view, "position", 0, 360),就需要本地定义一个对应的方法setPosition(float position),并且这个方法需要加上@keep注解,防止被当做无用方法清理掉。

    static {
        PROXY_PROPERTIES.put("alpha", PreHoneycombCompat.ALPHA);
        PROXY_PROPERTIES.put("pivotX", PreHoneycombCompat.PIVOT_X);
        PROXY_PROPERTIES.put("pivotY", PreHoneycombCompat.PIVOT_Y);
        PROXY_PROPERTIES.put("translationX", PreHoneycombCompat.TRANSLATION_X);
        PROXY_PROPERTIES.put("translationY", PreHoneycombCompat.TRANSLATION_Y);
        PROXY_PROPERTIES.put("rotation", PreHoneycombCompat.ROTATION);
        PROXY_PROPERTIES.put("rotationX", PreHoneycombCompat.ROTATION_X);
        PROXY_PROPERTIES.put("rotationY", PreHoneycombCompat.ROTATION_Y);
        PROXY_PROPERTIES.put("scaleX", PreHoneycombCompat.SCALE_X);
        PROXY_PROPERTIES.put("scaleY", PreHoneycombCompat.SCALE_Y);
        PROXY_PROPERTIES.put("scrollX", PreHoneycombCompat.SCROLL_X);
        PROXY_PROPERTIES.put("scrollY", PreHoneycombCompat.SCROLL_Y);
        PROXY_PROPERTIES.put("x", PreHoneycombCompat.X);
        PROXY_PROPERTIES.put("y", PreHoneycombCompat.Y);
    }
ObsoleteSdkInt

无用的SDK版本检查:Android SDK的版本更新比较快,许多API的使用都需要通过检查SDK版本防止出现not found之类的崩溃。在APP迭代的过程中提升了minSdkVersion的值就会导致部分SDK版本检查不再需要。

这种SDK版本检查会引起不必要的资源搜索。

DevModeObsolete

以前,文档中建议在productFlavors中创建一个dev product。设定minSdkVersion 21,在开发过程中激活multidexing加速构建过程。现在已经不需要这么做了,在新版的IDE和Gradle插件中,会自动地识别所连接设备的API level,如果链接的设备API level大于等于21,就会自动打开multindexing,就跟之前设置了dev product的效果一样。

参考:Enable Android MultiDex

ObsoleteLayoutParam

无用的LayoutParam:当给Widget使用了所在Layout没有提供的LayouParam时,会有这个提示。这种情况一般出现在修改Layout类型时没有同时修改内部Widget的LayoutParam设置或者把一个Widget从一个Layout拷贝到另一个不同类型的Layout内部。

这种无用的LayoutParam在运行时会引起无效的属性解析,也会误导阅读这些代码的人。所以应该把这些无用的属性删除掉。

其他

WearableBindListener
UseOfBundledGooglePlayServices