说说 Growth Hacker


大家应该注意到我的公众号改名了哈,今天就来随便聊聊我对”增长黑客”的认识。

最开始接触“增长黑客”(Growth Hacker)这个概念是源自一则关于Hotmail的营销事件。

1996年刚刚成立不久的Hotmail由于手头资金有限,他们仅靠在每一封用Hotmail发出的邮件末尾加上“我爱你,快来Hotmail申请你的免费邮箱。”这样一句简单的话,就给他们带来了每天3000个新用户的增长。

那么话说回来,到底什么才是“增长黑客”呢?

在互联网公司中,增长黑客是游走在产品、运营、研发、设计、用研等环节之间的多面手。他们的唯一目标就是通过严谨的数据分析以最低的成本获得高用户增长。

那有人可能要问了,怎么才能成为一名增长黑客呢?其实不管现在你是拥有技术背景的产品经理,还是对产品运营感兴趣的研发工程师都有相对的优势,不过归根结底还是要专业技能过硬、经得起实战检验,才是最基本的准入门槛。

增长黑客要建立”T“型的知识结构——在横向上对跨界知识信手拈来、有机整合,在纵向上有某一领域的专攻,甚至达到旁人难以企及的深度。

就我的个人成长经验来说,由于从小就对黑客和互联网感兴趣,自从高一拥有了自己的第一台个人电脑之后便开始折腾各种操作系统和软件,随后通过frontpage搭建了自己的第一个静态博客,大学期间小打小闹的也创过业,在2012年的暑假第一次接触到Android开发,然后闭关苦学3个月给学校开发出了一款移动图书馆APP,并且成功在360手机助手和腾讯应用宝以及图书馆主页上线。由于本科学的是网络工程,因此在网络编程、黑客攻防、路由交换等方面也得到一些专业技能的加强。此外因为兴趣比较广泛,学过设计用的PS、运营用的公众号、数据分析用的GA、产品用的Axure等等。业余时间也喜欢写写博客,从CSDN免费博客到WordPress搭的动态博客再到现在的Hexo静态博客,慢慢也学到了一些排版和布局的文案技巧,毕业后的第一份工作加入了一家初创团队因此也有幸结实了一些优秀的产品经理、创业达人和投资人。

总的来说,一路野蛮生长,跌跌撞撞,体内吸收了不少“混乱”的能量。然而那些在当初看来任性妄为、无足轻重的经历,终将在某一日连点成线,开花结果。正如乔布斯在斯坦福毕业典礼上的演讲:“你不可能从现在预测到未来,只有回头看时,才会发现事物之间的联系。所以你必须相信,那些生命中的点点滴滴,将会在你未来的生命里,以某种方式串联起来。你必须始终相信一些东西——你的勇气、宿命、生活、因缘、随便什么,它们将给你追寻内心真正所想的自信,带你走离平凡,变得与众不同。”


全栈增长工程师,欢迎关注

Share Comments

你有没有这些衣物?


最近感觉公号总写些技术类的文章不免有些枯燥,今天就开篇讲讲生活上一些值得思考的小事吧。

你的衣橱里有没有这些衣物?

拿我自己来说,其实一直到高中毕业前我的大部分衣服、鞋子都是我母亲一手操办购买的,每次她买完回来让我试穿一下,如果尺码没什么问题我基本都会说OK的(可能当时我也确实没什么审美)。**

久而久之,每逢过年准备购置新衣服的时候就会发现还有一大堆的旧衣服静静的躺在衣橱内甚至有些压根都没有穿出去过。这个时候我大都会跟母亲说要不咱们扔掉一些吧反正留着也没用,可你们应该是知道的父母那辈人总是以节俭自居,So…

反观我大学期间,很多时候不管是因为网购便宜还是和周围人攀比,还是会有各种各样的无用消费(这里不单单是衣物还有各种电子消费品等)。

要重视价值而非价钱

买便宜货不是明智之举。大学头一两年因为花的是家里的钱所以比较省,有时候会在像双十一这样的大型网购节的时候一口气买好些便宜可质感差的衣服。数量再多,经常穿的也就那几件,其他依然是闲置。

花大价钱也不是长久之计。一个人的形象好不好,不是靠几个奢侈品就能改变的。这让我想起了之前看到的一篇文章《为什么法国女人不买名牌包?》,文中讲了其实她们并不是买不起那一个包,她们是买不起和那个包等值的全套东西。不想打肿脸,硬塞进那个不属于她们的阶级。有什么比背着香奈儿的包,去挤地铁更让人心酸的事情呢?时尚是用钱砸出来的,可也得看砸在了什么地方。如果说服饰能推动你的人生顺丰顺水,那才值得投资。

定期整理下衣物,该扔就扔

这些年,我基本定期就会收拾一次家,每次都能扔掉一大箱子的东西,其中衣物居多其次是些电子产品之类的。说实话每次扔完心情就会特别好,感觉世界又清爽了好多,我扔东西的原则是:近一年没穿过的衣服鞋子、未来半年不会再用到的生活用品、功能重复的电子产品还有好多失去收藏意义的摆件。

说到扔东西,我们真的还会去穿那些一年都没有碰过的衣服嘛?

不。

很多人压根就再也不会去穿了,每次从一个储物箱挪到另一个,但有人心里可能就想了在衣橱里多放一两件衣服又不是什么大事万一哪天用的到呢,至少它还在那儿,不是么?

一两件这样的衣服的确不是问题,但是如果抱定这样的想法,年复一年,怕是衣橱里已经堆满了太多无用的衣服,反而是那些真正有用的没有搁置的地方了。

关于是否要留下这些衣服,这种想法明确地把人分为不同的两群。

觉得需要留下的人,心心念念在自己得到的东西上,生怕有任何损失,我们可以把这种心态下的人生称之为“存量人生”。

而觉得不需要留下的人,他们努力探寻这个世界的真实,用理性控制自己的情绪,小心翼翼地和万兽群奔保持距离。他们的想法是审慎地分析评估真正对自己有用的东西,目的是带上最少的负担和给养,好继续向前飞奔,可以把这种心态下的人生称之为“增量人生”。

不同的人生态度会带来不同的结果

和所谓关键处的几次选择相比,在某种人生态度下日复一日的践行,对人生的决定性作用可能更大。追求存量者到最后往往两手空空,追求增量者很可能不求自得。从更长的时间尺度上来看,在出发点上的毫厘之差,也许会在其后拉开惊人的距离。


FullStackEngineer的公众号,更多分享

Share Comments

静态网站的动态化

说到静态网站的动态化,其实就跟想减肥又不愿多运动一个道理,那到底鱼和熊掌能不能兼得呢?静态网站确实有诸多优点但它的部署发布流程太繁琐了,之前我为了偷懒写了一个脚本每次写完文章一键发布!

后来在一次team分享会上cc同学提出了一个小创意说现在静态网站+markdown的组合非常流行,我们可不可以做一个移动版的app随时把写好的文章以静态网站的形式发布出去?而且兴致勃勃的说目前没有人这么做如果做出来肯定会火一把😄 。

最近在看持续集成相关的文章,然后发现完全可以通过CI+静态网站生成器+github手机客户端完成之前的idea,于是google了一下发现很多文章都有讲如何通过Travis、flow.ic等持续集成工具来发布由hexo、Jekyll等等静态网站生成器构建的博客,这仿佛也印证了那句话“当你想到一个idea的时候,可能有1000个人已经想到了,100个人在计划了,10个人准备全力去做了,一个人已经干出来了” 不过关键还是看执行力。

既然网上很多文章都有讲为什么还要来写这篇文章呢?还不是因为各种坑嘛,在实际搭建部署的过程中遇到各种莫名的环境配置问题,思路大家都懂😂

关于持续集成和静态网站生成器之类的概念在这里就不重复讲了,有问题自行google。

我的博客框架


我的博客系统是用Hexo+Travis CI+Github搭建的。
因为要使用Gitpage服务,所以生成的网页文件必须是在master分支

  • master分支用来存放生成的Html文件

  • blog_source分支用来存放博客源文件

在Github上生成Access Token


在github的设置页面,点击左侧的Personal access tokens,然后点击右上角的Generate new token按钮,他会让你输入密码,最后进入下图的页面:

这里需要注意因为你要把这个token给CI服务器所以为了安全起见尽量只给必要的权限即可。

Travis CI的环境配置


这里我们把一些敏感的信息都配置在CI服务器上,例如:github的用户名、邮箱、Gitpage的地址、还有刚刚生成的Token。

.travis.yml的配置


这里我们还需要在存放博客源码的仓库里创建一个.travis.yml配置文件,如下图:

内容如下:

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
language: node_js
node_js: stable

# S: Build Lifecycle
install:
- npm install
- npm install -S hexo-generator-json-content


#before_script:
# - npm install -g gulp

script:
- hexo g

after_script:
- cd ./public
- git init
- git config user.name "${USER_NAME}"
- git config user.email "${EMAIL}"
- git add .
- git commit -m "Update docs"
- git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" master:master
# E: Build LifeCycle

branches:
only:
- blog_source

其中变量的含义:

  • GH_TOKEN——访问github的token
  • GH_REF——博客托管在Gitpage上的地址
  • USER_NAME——github的用户名
  • EMAIL——github的邮箱

愉快的发布文章


1、通过Android studio来管理发布博客
在AS中有很多不错的插件来支持markdown,这样我们就可以像平时写代码一样来写文章了,完成之后push到服务器,CI那边就会自动编译发布。

2、通过github网站直接发布文章
这种方式就更不依赖环境了,只要有一台可以联网的设备你就可以开心的写文章了。

3、通过github手机客户端
github推出了Android版的客户端,这样写文章是不是更容易了呢


FullStackEngineer的公众号,更多分享

Share Comments

[译文] 我不使用Android Data Binding的四个理由

为什么我还在用ButterKnife。

免责声明:本文是基于个人经验和实践可以随意反驳,是否采纳自行决定。

1、专家不建议这么做
ButterKnife的作者Jake在下面这个github issue中直指要点。

data binding在最简单的场景下是比较有用的。但它并没有什么创新,所以在复杂度增加的情况下还是会像其他平台上的解决方案一样用起来非常痛苦(例如:XAML)。当这个库扩展到高级的情况下,将会迫使你把绑定的逻辑写到代码中,那里才是它真正该在的地方。

事实上,我同意其中的两点:

1、它的扩展性并不好。
2、业务逻辑应该在代码中。

2、它让你写出意大利面式的代码
一旦我们开始实现复杂的布局,将会使我们的Data Binding解决方案越来越复杂。

首先我们将会面临下面的问题:

1、Layout 要求你给他们分别传递数据。

2、你也可能想为你的布局创建不同的数据源。

3、同样的问题也会在ViewStubs中发生。

4、当你使用Picasso加载图片的时候,你需要为他实现一个自定义的data binding adapter,那样的话你就不能作为依赖mock和注入了。

我们可能会试着做些更复杂的事情:

1、在layout中增加presentation的逻辑。

1
2
3
4
5
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

2、在listeners中增加Lambda表达式。

1
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"

3、在layouts中使用导入的class类。

1
2
3
<data>
<import type="android.view.View"/>
</data>

我们的逻辑一部分在代码中一部分在布局文件中,这将很快变成一个噩梦,并且闻起来像是意大利面式的代码。

3、单元测试也不能用了
我非常喜欢Robolectric和Mockito,他们节约了我很多时间在创建和运行测试实例的时候,没有了他们我将无法工作。

Data Binding的一个特性对于我来说是一个bug:如果layout发生了异步更新,那就意味着在我设置了绑定之后单元测试中我无法确定view上的数据是否正确。

我记得google用Espresso实现的测试框架,但如果有可能的话我还是希望用单元测试的方式来测UI。我喜欢将Activities、Fragments和Views分开来测试而不是在一个大的Instrumentation Test中导入他们。

4、它比ButterKnife提供的功能少很多
ButterKnife提供了很多不错的特性,可能有些我们都不记得了:

1、资源绑定

2、View 列表

3、多个监听器的绑定

当我们用自定义控件做一些高级的实现的时候,资源绑定是非常有用的,我们可以通过它获取到Dimensions和Drawables。

当我们有一系列的视图触发同样的操作的时候,多视图绑定和多监听器绑定会让我们少写很多代码,例如:一系列的EditText和Buttons。

而如果你使用Data Binding库将得不到这些功能。

为什么你会使用Data Binding

1、我可以开发的更快
长远来看,快速并不一定总是好的。当我们开发app的时候,我们是在跑一场马拉松而不是一次百米冲刺……不是吗?

2、它已经存在于系统sdk中
不需要引入第三方库总归是好事情。如果你被调入到一个已经出现了方法数快超过限制的项目中时,你的leader将不希望你再引入过多的第三方库。

3、我在遵循MVVM的模式
如果你正确的利用观察者模式实现了MVVM,Data Binding库将会帮助你在views中实现观察者模式。

谢谢你看了这么长时间!


FullStackEngineer的公众号,更多分享

Share Comments

[译文] Android开发最佳实践

你的软件开发效率不仅仅取决于你有多么深的知识和经验,还依赖于你的开发工具、环境配置和团队协作能力。

我最近在 Droidcon Berlin做了一个关于在我们Zalando Tech team的一个Android开发者最佳实践的分享。接下来你可以找到我演讲中的一些要点,将使你的开发生活更加愉快并且让你的应用更加稳定。

1、你的AndroidManifest文件真实的样子是什么

我们大多数人早已知道我们在文本编辑器中看到的AndroidManifest.xml 文件跟实际编译好生成的的内容并不一样。这主要是因为你在工程中包含的第三方库中存在额外的<uses-permission/>标签,将和你自己的manifest文件混合。(The Commons Blog查看更详细的信息)

在构建APK前检查你的manifest,我们可以用Android Studio 2.2中提供的新特性:Merged Manifest Viewer。这个工具将会给你展示你的AndroidManifest是如何基于build types, flavors, and variants这些配置和你的依赖包merge的。你可以导航到AndroidManifest.xml文件点击底部的Merged Manifest标签使用这个工具。

2、Support annotations是你的朋友

另外一个特别有用的工具是support annotations library。你可以通过在你的build.gradle文件中添加“com.android.support:support-annotations:23.4.0” 来包含入你的工程。使用这些metadata annotations去修饰你的代码帮助你发现bug定义编码规则。最常规的使用场景是标记nullable和non-nullable,链接资源,并且指定从哪个线程被回调。

既然这些annotations是metadata annotations,所以即使你违反了它定义的语法规则你的工程也会编译。然而它会被Android Studio 和 Lint标记为高亮,并且在你的持续集成工具输出中对你的团队成员是可见的。

3、快速、无痛的code review

假设你想要去做一个code review,检查开发feature是如何工作的是有意义的,所以你需要去编译你的工程。这种情况有下面这样的一种通用工作流:

1.在你当前分支Stash changes
2.Checkout branch来做review
3.在你的IDE中重新加载gradle配置
4.在IDE中读代码
5.编译启动并且测试app
6.重复 (1) — (5)的动作

“这里有什么问题吗?”你会问。是的,除了下面这种情况之外没什么问题,当你的工程有1000+的类和配置文件不同的时候你将会用你强劲的MacBook花超过三分钟用在编译你的代码上。

我们的解决方案是使用一个专用的IDE实例和存储库文件夹来做code review。在这种情况下你的工作在一段时间内不会停止,你可以在任何时候回到你的主IDE和分支。这里有个小的免责声明:我们建议你使用一台至少有16GB RAM的机器,它为你节省的时间绝对非常值。

4、快速的应用修改

即使你的工程很小,你会经常花时间在编译部署最新的变化到测试机和模拟器上。如果你有上百个类和xml文件,每次编译和部署会花去你很多时间,即使你用配置很高的电脑。此外,你需要手动定位到应用变化的地方,这也需要一些时间。

在2015年底,Android社区收到两个可以让代码变化应用得更快的工具。第一个是JRebel,它是来自Java后端世界并且已经成为很长一段时间的行业标准。另外一个工具便是google在Android Studio 2.0中发布的Instant Run,这些工具都有共同的目的,但是JRebel有更多的特性,并且需要按年支付许可证。

这两个工具单看看不出有什么不同,于是我们通过文档和一些博客分析了他们的不同:

来源:
https://developer.android.com/studio/run/index.html#instant-run
Reto Meier: “Instant Run: How Does it Work?!”
Oleg Selajev: “Looking at JRebel for Android and Instant Run …”

几乎每周这两个工具都在积极的开发和改进。根据我们的经验,虽然很多用例还没有涉及,但你已经可以受益于这些工具了。

5、衡量执行时间

另一个非常有用的特性是在应用程序调试和性能分析日志记录方法输入/输出和执行时间。对于这些需求,我们使用一个简单的和优雅的方法注释工具——Hugo by Jake Wharton。如果你仅仅只是想看日志输出并不需要像Systrace这样深度和复杂的工具。

你需要做的只是去标注目标方法,如图所示:

1
2
@DebugLog
public String getName(String first, String last) {/* ... */}

在日志中找到相应的方法调用的打印信息:

1
2
V/Example: --> getName(first="Jake", last="Wharton")
V/Example: <-- getName [16ms] = "Jake Wharton"

6、如何从你的设备上读取日志输出信息

为了日常的需要,我们大多数人使用Android Studio内置的Monitor来读取日志。在简单的场景下它使用很方便,但我们注意到几个权衡使用这种方法:

1.日志是难以阅读,你应该使用外部工具或格式化配置。

2.Android Studio的日志工具是和你部署的应用程序的进程ID关联的。如果你重新部署应用程序或杀死进程,你以前的日志都会丢失,因为Android Studio是跟进程ID绑定的。

为了解决这个问题我们可以使用Jake Wharton — pidcat。它的主要好处如下:

1.好颜色模式和格式。

2.由包名称连接到调试应用程序,而不是进程ID。所有的日志都将被保持在重新部署应用程序后。

7、网络输出日志记录和分析

最常见的阅读你应用网路交互日志输出的方式是通过HTTP客户端。然而,这种方法有几个权衡:

1.如果你要在开发过程中保持所有的网络请求日志,你将注意到,应用程序的性能有所下降,需要一些时间来打印日志。

2.如果你的应用有一些额外的库需要用到网络,(例如:Google Analytics)你需要为这些额外的库做一些配置 来让所有的数据被记录。

这里有另外一种方法:使用HTTP监控软件 Charles Proxy,这种类型的工具会提供如下的功能,把你的应用包装为黑盒:

1.HTTP/HTTPS通道的监控和记录

2.重新修改返回值和服务器响应的边界情况

3.在网络回调的地方打断点

4.将SSL证书安装到设备读取加密流量

更新,评论中 Nahuel Barrios提到的另外一个可以替代的网络监控工具是 Facebook Stetho我们仍然不能通过Stetho读取Google Analytics中的SSL加密的通信信息,如果你知道任何关于这个的信息请联系我。

8、保证在不同版本的操作系统上测试

我一直在做并且推动我同事做的事情是在Lollipop和更高的(API 21+)的版本中测试每一个功能。这样我可以在测试期间捕获这些bug。

你会发现通常的兼容问题都是些触摸反馈和系统颜色的不一致,我们经常看到app由于兼容问题在老的API中崩溃。

9、自动化屏幕交互测试

我们经常需要检查一些场景在不同设备上做重复的UI点击和输入。如果你有三到四台测试设备那将是非常烦人的,你需要一个回归测试计划去测试通过30各场景。

自动化的第一步,我们通过输入adb命令或者脚本去实现从而避免每次都要手动和设备交互。你可以通过系统按键、键盘输入和屏幕触控来实现adb命令的输入。

但是如果你有三台设备要同时测试一个场景,你会怎么做?我们可以使用adb-ninja来让不同设备同时测试。

10、检查你的build.gradle配置

有时甚至一些有经验的开发者也会使用一些过时的配置实践。让我们检查一下你的build.gradle文件,看看你有没有中招:

1.舍弃mavenCentral,去用jcenter作为依赖仓库。jcenter拥有更快的响应时间并且已经集成了mavenCental的内容。

2.检查Android Plugin for Gradle的版本,保持最新的版本可以提高编译性能并且会得到像Instant Run这样好用的功能。

3.不要指定依赖库的版本范围,要使用像“23.4.0”这样的版本常量,减少每次编译依赖库时的网络请求。

4.设置编译版本minSdkVersion 21或者更高,将会提高构建速度。


FullStackEngineer的公众号,更多分享

Share Comments

[译文] 如何正确在闪屏页加载耗时的库

在这篇文章中我想展示的是当开发人员有一个初始化很慢的库时可能不希望在主线程中来初始化这个库,因为它将阻塞UI线程使应用无响应。相反,开发人员希望在后台加载它,然后将结果通知到主线程中。

闪屏页

首先,如果你已经有一些初始化的东西在自己定义的application中,你可能需要一个适当的闪屏页。这意味着当点击应用程序图标的同时应该出现闪屏页。它可以很容易通过设置SplashActivity的主题背景实现。

1
2
3
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@drawable/background_splash</item>
</style>

在你的AndroidManifest.xml中

1
2
3
4
5
6
7
8
<activity
android:name=".splash.SplashActivity"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

通常闪屏页是一个logo,所以这个@drawable/background_splash可以是一个layer-list,例如:

1
2
3
4
5
6
7
8
9
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/holo_blue_dark"/>

<item>
<bitmap
android:gravity="center"
android:src="@drawable/ic_hockey_stick"/>
</item>
</layer-list>

关于这个的实现就到这里。

顺便说一句,如果你使用作为你bitmap的资源,注意这个bug。不幸的是,没有相应的解决方案所以在API版本小于23的情况下闪屏页中的图片要用png格式。

初始化库

现在我们有了闪屏页,接下来干什么呢?现在我们应该考虑一下如何加载这些耗时的库了,Dagger 2和RxJava来救驾!

如果这个漫长的初始化库只是需要在闪屏页加载一些数据的话我们可以把它定义在SplashModule中,这样的话当我们不再使用的时候可以清除掉它的引用。

1
2
3
4
5
6
7
@Module
public class SplashModule {
@Provides @NonNull @SplashScope
public SplashLibrary splashLibrary() {
return new SplashLibrary(); // Takes >5 seconds.
}
}

目前我们还不能在任何地方注入这个库,因为它会阻塞UI线程。我们将会创建一个Observable来接受SplashLibrary实例,但仍然不会被初始化因为我们通过Lazy<> 实例化它。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Module
public class SplashModule {
// ...

@Provides @NonNull @SplashScope
public Observable<SplashLibrary> observable(final Lazy<SplashLibrary> library) {
return Observable.defer(new Func0<Observable<SplashLibrary>>() {
@Override public Observable<SplashLibrary> call() {
return Observable.just(library.get());
}
});
}
}

注入这个库

最后我们可以在SplashActivity中注入Observable

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
/** Observable which will emit an item when fully initialized. */
@Inject Observable<SplashLibrary> splashLibraryObservable;

/** Subscription to unsubscribe in onStop(). */
private Subscription subscription;

@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// ...

subscription = splashLibraryObservable
// Init library on another thread.
.subscribeOn(Schedulers.computation())
// Observe result on the main thread.
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<SplashLibrary>() {
@Override public void call(SplashLibrary splashLibrary) {

// Use the initialized library.

Intent intent = new Intent(activity, MainActivity.class);
startActivity(intent);
}
});
}
}

这里仍然有一些需要注意的坑:
1、这个库可能抛出异常=>我们需要实现onError()方法。
2、当我们在初始化完成前离开或者旋转页面时可能会导致内存泄露因为我们在回调方法中持有activity的引用。

处理当初始化一个重量级库的时候引发的错误

为了解决这个问题,我们可以通过一个Observer订阅subscribe()。非常简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.subscribe(new Observer<SplashLibrary>() {
final String TAG = "Observer<SplashLibrary>";

@Override public void onCompleted() { }

@Override public void onError(Throwable e) {
Log.d(TAG, "Library init error!", e);
// Possible UI interaction.
// ...
finish();
}

@Override public void onNext(SplashLibrary splashLibrary) {
// ...
// Use the initialized library.

Intent intent = new Intent(activity, MainActivity.class);
startActivity(intent);
finish();
}
});

处理当用户离开activity页面时引发的内存泄露问题

在这个例子中我们仅仅从Subscription取消订阅是不够的,因为当对象正在初始化的过程中Subscription不能释放资源从而是我们在内存中持有已经销毁的activity的资源导致内存泄露。如果在Application中开启了StrictMode.enableDefaults();我们很容易在LogCat中看到,当旋转activity页面日志如下:

1
2
3
E/StrictMode: class .SplashActivity; instances=2; limit=1
android.os.StrictMode$InstanceCountViolation: class .SplashActivity; instances=2; limit=1
at android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1)

这就是为什么我们需要去释放在Observer创建中持有的对activity的引用,我们可以通过创建一个实现了Observer的静态类来实现,并且在onDestroy()中清除引用,通过这种方法我们可以确保没有任何泄露。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static final class OnInitObserver implements Observer<SplashLibrary> {
@Nullable private SplashActivity splashActivity;

OnInitObserver(@NonNull SplashActivity splashActivity) {
this.splashActivity = splashActivity;
}

@Override public void onCompleted() { /* ... */ }
@Override public void onError(Throwable e) { /* ... */ }
@Override public void onNext(SplashLibrary splashLibrary) { /* ... */ }

public void releaseListener() {
splashActivity = null;
}
}
1
2
3
4
5
@Override protected void onDestroy() {
super.onDestroy();

onInitObserver.releaseListener();
}

记住这些点,可以在闪屏页中很容易初始化一个库、发起一个网络请求或者做一些复杂的处理。

感谢阅读!源码在这里


FullStackEngineer的公众号,更多分享

Share Comments