动态切换Launcher之ActivityAlias

0x81 背景

每逢双11或是重大节日来临,我们会发现像淘宝京东这种APP的Launcher会在不更新的情况下更换,动态切换Launcher图标的技术如果使用ActivityAlias功能将会特别容易。

0x82 ActivityAlias 是什么

看名字就能知道,ActivityAlias就是别名的意思,是Google官方为方便开发为Activity设置别名的时候提供的方便方法。
利用别名这个技术可以实现很多蹩脚的需求,比如这个动态更换Launcher图标,在比如wxapi的回调。

Read More

发布Library到JitPack

0x81 关于JitPack

JitPack是一个Maven仓库,但是他的配置比写大量的gradle配置代码要省事的多。

0x82 发布基本配置

  • 在Project的build.gradle中添加如下类路径classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
  • repo中添加maven { url "https://jitpack.io" }
  • module中插件配置apply plugin: 'com.github.dcendents.android-maven',并填写group名group='com.github.fioneragh'

0x83 Github发布信息

在Github上发布一个Release,填写完发布信息后即可在JitPack上找到刚刚发布的库。

AnimatedVectorDrawable

0x81 AnimatedVectorDrawable

承接上篇SVG在Android上的实现——VectorDrawable,本片主要介绍SVG的动画。
AnimatedVectorDrawable通过ObjectAnimatorAnimatorSet两组API对VectorDrawable进行动画处理,本着分治原则,一个动画通常包含动画描述文件、动画文件和VectorDrawable文件。

0x82 动画描述文件

动画描述文件就是将矢量图与动画关联起来:

1
2
3
4
5
6
7
8
9
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/vector" >
<target
android:name="anim_group"
android:animation="@anim/rotation" />
<target
android:name="anim_name"
android:animation="@anim/animated_path" />
</animated-vector>

<target></target>子标签指定矢量图中哪一部分需要执行哪一个动画。

Read More

SVG在Android上的实现——VectorDrawable

0x81 SVG

SVG全称Scalable Vector Graphics,意为可伸缩矢量图形,由于它的是矢量图,所以无论如何放大都不会失真。
SVG由Path描述,通过PathData控制究竟如何渲染显示图形,SVG的具体语法不是本文的重点,故不多加阐述。

0x82 Android 实现

有大牛在早期曾造了一个轮子——android-pathview,这个库内容比较简单,主要就是两个类,一个SVG路径解析类SvgUtils,一个根据解析结果绘制类PathView

在Lolipop(Android 5.0)之后,Google官方引入了对矢量图的支持,虽然支持有限(兼容性问题),但是官方支持总是好的。
并且谷歌提供了支持库(Support Library)用于提供向后兼容。

Read More

Android Path

0x81 Path 是什么

Path 顾名思义是路径的意思,Path API的功能很单纯,就是使用画笔Paint按照路径Path在画布Canvas上绘制相应的图形。
通过使用Path,可以方便的绘制所需的内容,从而免去画笔繁琐的绘制方法。

0x82 相关 API

1
2
3
4
5
6
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;

android.graphics包中包含了View绘制所需的类。

Read More

集成Weex

0x81 Weex 是什么

Weex官方主页
Weex 是什么,用它官方的slogan——“A framework for building Mobile cross-platform UI”,它是一个跨平台移动UI框架。
Weex 足够轻量,易扩展且高性能。

0x82 Weex 特点

Weex 采用Vue的语法,最新的Weex更是支持直接采用Vue文件作为源文件,这也更方便Vue.js开发者开发移动应用。
Weex 有强大的扩展系统,可以方便自定义组件与原生交互。
Weex 同时拥有比较高的性能,因为模板引擎将文件转换为js文件后在移动设备上经由JS引擎进一步转换渲染为原生组件,有媲美RN甚至优于RN的性能表现。

Read More

Android DataBinding[更新]

0x81 前言

DataBinding 解决了 Android UI 编程的一个痛点,官方原生支持MVVM模型可以让我们在不改变既有代码框架的前提下,非常容易地使用这些新特性。
DataBinding 如今已经官方实现双向绑定,当然双向绑定如果使用不当很容易出现死循环,在使用时还是要多加注意。

0x82 配置方法

配置方法已经很简单,在build.gradleandroid scope中配置如下:

1
2
3
dataBinding {
enabled true
}

但是DataBinding所依赖的支持库可能和你的配置存在差异。

Read More

使用 RxBinding

0x81 什么是RxBinding

RxBinding 是一个异步调用库,用RxJava实现,用于处理Android 开发中控件异步事件的处理。

0x82 RxBinding 原理

其实RxBinding 的代码很简单,目的也很单纯。它的内部托管了原本用于设定事件监听的方法,转为触发RxJava中的发送事件并返回一个Observable,这样便可以通过订阅的方法进行事件的消费。

以RxView的clicks方法为例:

1
2
3
4
5
@CheckResult @NonNull
public static Observable<Void> clicks(@NonNull View view) {
checkNotNull(view, "view == null");
return Observable.create(new ViewClickOnSubscribe(view));
}

clicks方法返回了一个Observable,而这个Observable是通过ViewClickOnSubscribe实例创建的。

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
final class ViewClickOnSubscribe implements Observable.OnSubscribe<Void> {
final View view;

ViewClickOnSubscribe(View view) {
this.view = view;
}

@Override public void call(final Subscriber<? super Void> subscriber) {
verifyMainThread();

View.OnClickListener listener = new View.OnClickListener() {
@Override public void onClick(View v) {
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(null);
}
}
};

subscriber.add(new MainThreadSubscription() {
@Override protected void onUnsubscribe() {
view.setOnClickListener(null);
}
});

view.setOnClickListener(listener);
}
}

最核心的call方法,首先verifyMainThread();检查接下来的操作是否是在Android主线程。
之后创建了一个View的点击监听器,监听器内的实现就是调用一下onNext(),当然先检查了是否这个subscriber已解除订阅。
然后添加一个Subscription来保证解除订阅时移除监听器避免内存泄露。
最后就是给view添加这个监听器,这样点击操作便是发送一个事件给将来的订阅者。

0x83 订阅Observable

既然返回值是Observable,那么我们就可以像普通的订阅操作一样处理订阅关系。

1
2
3
4
5
6
7
RxView.clicks(textView).throttleFirst(2000, TimeUnit.MILLISECONDS).subscribe(
new Action1<Void>() {
@Override
public void call(Void aVoid) {
EventBus.getDefault().post(new DeviceRelateEvent.RobotTracePageFullEvent());
}
});

以上就是给textView这个控件添加点击事件并返回一个Observable,因此我们可以用throttleFirst操作符,限制2s内不得重复发送该事件,这样就可以防止重复点击带来的各种问题。
在call方法中处理本来监听中需要进行的逻辑操作即可。

Android Module BuildConfig

0x81 问题起因

抽时间把一个Project 抽成了一个个的Module,意图尽可能的分层分治,但是却遇到了一个很有意思的问题,log打不出来。

0x82 问题分析

log打印如下,非常简单的只是判断了一下是否是调试模式,如果不是则不打印

1
2
3
4
5
6
7
public static void d(String content) {
if (!isDebug) {
return;
}
String tag = generateTag();
Log.d(tag, content);
}

上面的代码看似十分正常,而实际上即使是Debug模式,仍然不打印log,原因只有一个,isDebug变量为false
看变量定义private static boolean isDebug = BuildConfig.DEBUG;,BuildConfig来自import com.fionera.base.BuildConfig;是这个Module的。

1
2
3
4
5
6
7
8
9
10
package com.fionera.base;

public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.fionera.base";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0";
}

可以看到DEBUG值为true,那为什么会不打印呢,只能调试程序看这个变量到底是什么,不用猜也知道,肯定是false。

Read More

Fragment 日常笔记 StateLoss

0x81 StateLoss 问题

FragmentTransaction 在调用commit()时,其意图是执行某种操作,而若此时onSaveInstanceState()方法已被调用,将会抛出IllegalStateException
为什么会这样?onSaveInstanceState()方法已被调用,说明FragmentManager已经保存了它管理的Fragment的FragmentState,继续操作的状态将不会被保存,checkStateLoss()方法认为已经保存过这样会发生状态丢失,因此会抛出IllegalStateException

0x82 StateLoss 触发时机

之前我们常常觉得Support Library提供的API似乎更容易出现问题,而旧平台设备出现问题的次数也非常少,难道真的是Android系统随着更新问题越来越多?
其实这主要取决于生命周期发生的变化上,在Android 3.0 之前,onSaveInstanceState()方法在onPause回调之前就会被调用,而在Android 3.0 之后,这一方法被推迟到了onStop回调之前。
这一重要变化意味着什么,系统回收Activity从原本的onPause回调前都不可能被杀掉转变为部分版本(新版本)能保证onStop回调之前不被杀掉,而其他版本(旧版本)在onPause到onStop这段时间会被系统回收。
这个时候Support Library不得不根据状况兼容不同的版本——允许onPause到onStop之间的StateLoss。

Read More