Comments

今年过年在老家学到了一个词,叫现成话。这是一句扬中话,可能江浙沪以外的朋友不一定能看懂,我解释一下。

我们每个人在每天与人交流或者评价他人的时候,实际上都是拿着自己的尺子跟心目中的标准去衡量别人。你根本不知道别人为什么这样想,也根本不知道别人为什么这样做。所以,不要随随便便就拿着自己的各种标准去绑架别人。同样,当我们面对别人说自己,或者“绑架”自己的时候,也不必太在意别人究竟说的是什么。

你唯一要做的,就是坚持你自己心目中想法,然后拿着结果去啪啪打那些人的脸就好了

记得 15 年底,我犹豫是不是该在珠海下手买套房了,当时身边有太多的朋友劝我,让我不要买。说珠海房价泡沫的;说珠海房价会跌的;说我年纪轻轻有钱不出去玩却拿来还银行贷款的。

我问他们,我说如果到时候房价没跌我买不起了怎么办?那哥们儿笑笑跟我说,“没跌咱就撤呗,不在珠海干了。”

坦率地讲,作为朋友,我是很感谢他们在我做出人生重大选择的时候给我建议和提醒,好心当做驴肝肺不是咱的风格。

结果是,我依然自己做决定,一咬牙一跺脚在 2015 年底入了楼市的坑。接下来整个 2016 年珠海的楼价直线上涨,到 2016 年底的时候,我那套房子的价格已经几乎翻了一倍。如果现在再买,对我而言早已负担不起。

于是好玩的地方就来了,现在有时候我跟当初好心劝我的那些朋友聊起这事的时候,他们已经完全忘记自己当初怎么劝我不要买房,口径几乎统一180° 转弯,说我当初做对了幸好买了之类的。

更有当初说房价没跌就撤的那哥们儿,早在大半年前就跑去别的城市了。后来有一次我跟他谈起这是,他笑着搓了搓手跟我说:“反正老子打一枪换一个地方,最终肯定还是回老家过日子的。”

但是我不一样啊。我本身是想先在珠海定居的,我也知道如果我选择在这座城市定居,房子是早晚的事情。更何况从 15 年下半年开始,珠海的房价苗头早就不对了,用下半身思考都知道不可能会跌下来。

所以,如果当初我听了那哥们儿的话,换来的结果是什么呢?也许我依然在珠海租着房子,每天飘在这座城市的上空,无依无靠,面对着 3W+/平 的房子傻眼,不知道要干多少年才能买到房子。而那些当初劝我的朋友呢?他们只是一说而已,最后走的走,换的换,难道还能因为当初听了他们的话,再去一个个追着他们要什么损失费吗?到最后受影响的也只有我自己,与别人无关。

什么是“现成话”,就是形容有些人讲话,永远都只会顺着眼前的情况去讲,等后面情况一旦发生改变,这样的人又能立刻顺着改变后的情况说出另一番话来,完全忘记之前自己讲的是什么,永远都站不住自己的立场。

我表姐,快30岁了还没结婚,最近处了一个男生,才交往了两个多月,平时大家也都上班,就周末吃个饭逛个街什么的。年三十那天我们回外婆家拜年,我舅舅就开始念叨她谈恋爱是“马拉松”,不知道平时两个人在干嘛。还讲他们自己以前谈恋爱,吃一顿饭,望两眼,成与不成几乎就能定下来了。成就继续,不成大家从此就路人不要再联系,不知道我们现在年轻人谈恋爱怎么这么难,吃饭逛街看电影出去玩的,钱花了没结果。我表姐跟我舅舅一来二去,被气得巴拉巴拉直掉眼泪,晚上差点都没在家过年。

我了解我舅舅的脾气性格,因为我知道,如果反过来,我表姐和一个“只吃过一顿饭,望过两眼”的男人结了婚定终身,婚后如果感情不和闹矛盾,到了我舅舅那儿肯定又是一顿说,而且话会完全跟上面相反。到那个时候,绝对又开始怪我姐姐当初找男朋友不仔细交往一段时间再决定。

过去我特别讨厌听到这样的“现成话”,同时也非常讨厌说这种话的人。无奈的是,这样的人总会存在于我们身边。就比如,我住的地方离公司有些远,平时坐公交不太方便了,于是开始有人建议我说“你不如赶紧买台车吧,早晚的事儿”。但是一旦我买了车之后,这些人一看我车,譬如是一个还算中上档次的,肯定马上又会说“我靠你可真有钱,买这么好的车,我才不过买了个什么什么而已”,然后完全忘记自己上个月才建议我买车的。(摊手表情)

这种就是典型的“说现成话”,尽管对方可能也只是一说而过,但总能让听的人像吃了苍蝇屎一样浑身不舒服。

我老妈最近几天又开始跟我唠叨,说哪儿哪儿有一个好姑娘,问我要不要去看一下。(把相亲说得这么清新脱俗,我服)每次她这么说我都会很不耐烦地拒绝掉。原因很简单,我讨厌相亲,更讨厌这种把自己认为对的事物强加到别人身上的行为,你怎么知道你认定的好的就一定也是我觉得好的呢?如果到最后发现不好了,是算我没处理好,还是算你当初没介绍好呢?你不是说她好姑娘的呢?

我想还是如人饮水,冷暖自知吧。

以前我每次听到”现成话”,内心都是很反感的,总想着要说点什么反驳回去。但是经过了这一年的各种事情之后,我开始变得坦然,也知道对这些话我只需要一笑而过就行,放在心里去想,怎么都觉得是在自己给自己找不痛快。

郭德纲之前在一次采访中说,现实生活中总有一些人,不分青红皂白,也不知道究竟在你身上发生了什么事儿,上来就劝你大度一点,或者劝你这劝你那的。原话是,“我挺厌恶有一种人,不明白任何情况,就劝你一定要大度的人。这种人你要离他远一点,因为雷劈他的时候会连累到你!”

在我看来,那些只会说“现成话”的人,同样也要离他们远一点,否则到最后这些人被结果啪啪打脸的时候,同样也会连累到你。

Comments

WDMyCloud 买了将近一年时间了,本来准备只给 Mac 做 Time Machine 备份用的,奈何 3T 空间实在撑不满,多着也是浪费,再加上平时白天上班,家里带宽闲着也是闲着,于是今天折腾了一下给他添加了迅雷远程下载功能,亲测可用。

首先需要说明的是,网上大部分教程抄来抄去,几乎全是 Windows 下操作的版本,其实对于 Mac 用户而言,完全不用 PuTTY,因为 Mac 自带的终端一直都支持 ssh ,而 WinSCP 在 Mac 下有更好的替代软件 Cyberduck,俗称小黄鸭。所以对于 Mac 用户而言,理论上你只需要准备后者,剩下的完全不需要考虑。

步骤

一、降级 WDMyCloud 固件版本

迅雷官方的远程下载模块目前不支持 4.0+ 版本的 WDMyCloud 固件,所以如果你不小心被西部数据升级到了最新版本,需要先降回去。方法很简单:

万事先理清思路,我们的思路:v04.04 –> V04.00 –> V03.04

所以,先准备好这两个版本的固件:

http://download.wdc.com/nas/sq-040000-607-20140630.deb

http://download.wdc.com/nas/sq-030401-230-20140415.deb

然后照着这篇文章完成降级就好,很简单。

这一步我遇到几个问题,注意一下:

  • Mac 用户如果不知道你的 WDMyCloud 在局域网内 IP 是多少,可以通过 ping 一下 wdmycloud.local 获得。

  • 在降到 V04.00 之后,再次上传固件准备降到 V03.04 的时候,WDMyCloud 会提示你没有空间上传固件了,我初步判断是 WDMyCloud 的一个 bug 导致的,它在升完之后没把我们上一次传的 deb 文件删掉导致。解决办法就是去 设置->实用工具->系统出厂还原->仅系统 还原一下系统,然后再继续降级。如果你没找到,看下图:

  • V04.00 –> V03.04 刷完之后 WDMyCloud 会提示“升级失败”。这是正常的,因为我们本身就是在做降级操作。去“固件”里看一下,已经是 V03.04 了就算大功告成。另外记得把自动更新关掉,防止再次被升级到最新固件。

Read on →
Comments

问题

钱包 2.0.0_beta 版本上线之后,我们给新获得理财权限的用户增加了一个欢迎对话框。按照 Google 官方关于开发对话框时的指导,钱包把所有会用到的 Dialog 全部采用 DialogFragment 进行了封装,并且放到了一个包下进行管理。

DialogFragment 的用法非常简单,而且内部封装好了show() 方法,很容易就可以把对话框展现出来,就像这样

1
2
3
4
5
public void showNoticeDialog() {
        // Create an instance of the dialog fragment and show it
        DialogFragment dialog = new NoticeDialogFragment();
        dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
    }

然而上线后不到一周时间,我在后台看到了很多用户遇到了这样的奔溃:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
  at android.support.v4.app.z.v(SourceFile:1440)
  at android.support.v4.app.z.a(SourceFile:1458)
  at android.support.v4.app.k.a(SourceFile:634)
  at android.support.v4.app.k.b(SourceFile:613)
  at android.support.v4.app.q.show(SourceFile:139)
  at com.meizu.flyme.wallet.fragment.p.c(SourceFile:195)
  at com.meizu.flyme.wallet.fragment.p.c(SourceFile:390)
  at com.meizu.flyme.wallet.fragment.p.b(SourceFile:61)
  at com.meizu.flyme.wallet.fragment.p$6.a(SourceFile:469)
  at com.meizu.flyme.wallet.fragment.p$6.onResponse(SourceFile:464)
  at com.android.volley.toolbox.u.deliverResponse(SourceFile:60)
  at com.android.volley.toolbox.u.deliverResponse(SourceFile:30)
  at com.android.volley.g.run(SourceFile:99)
  at android.os.Handler.handleCallback(Handler.java:815)
  at android.os.Handler.dispatchMessage(Handler.java:104)
  at android.os.Looper.loop(Looper.java:194)
  at android.app.ActivityThread.main(ActivityThread.java:5824)
  at java.lang.reflect.Method.invoke(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:372)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1010)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:805)

这个错误堆栈正是在调用了 DialogFragmentshow()方法之后才出现的,真的是非常奇怪。一开始,我们以为是简单的 fragment 生命周期问题导致的 crash,然而等我在 show() 方法之前加上了isAdded() 判断还是没有用之后,我知道事情可能没那么简单了。

Read on →
Comments

本文由 Lin Shen 译自 Common questions on AsyncTask,原文作者 Colt McAnlis, 转载请务必注明出处!

关于 AsyncTask 的常见问题

我在 Google 工作最喜欢的一点,就是把一些比较复杂的概念,分解成一小部分一小部分,这样就可以确保每一位工程师都能清楚地理解。在我最近的《Android 性能优化典范》视频中,我提到了一个表面上看上去很直观,但是它的一些属性可能会带来一些万万没想到的负面影响的东西,我是说,毫无悬念,这货就是 关于 AsyncTask:

我在这个视频里强调了一些开发者之前在使用 AsyncTask 时可能并不会注意到的事项。多亏了我们有一些非常棒的 android 开发者交流社区,能让我们看到大家反馈的一些问题:

让我们来挖掘其中的一些问题,看看能不能深入展开讨论一下:

Read on →
Comments

Project Home

https://github.com/shawnlinboy/android-OverscrollViewPager/

Description

最近项目里接到一个需求,效果如下:

简而言之,需要在 ViewPager 滑到最左或者最右的时候,仍然支持可滑动。

具体就是根据是在 ViewPager 最右边向右滑了,还是在最左边向左滑了,做出响应。这就要求对 ViewPager 的 overscroll 行为做出监听。

网上也有一些类似的轮子,但是我感觉做得真心复杂,而且都是为了实现轮播的,所以可扩展性并不强。因此我这里要做的,就是对 ViewPager 的overscroll 行为做出简单的监听并封装。剩下的,交给开发者自己去 do whatever you want 就好。

原理很简单,对手势做出监听即可。所以也算是对ViewPager 的overscroll 行为监听的另一种实现思路吧。

使用上,只需要把 ViewPager 替换成我这个就好,adapter 不需要改,也没必要改。因为我只会告诉你,你的 ViewPager 是否到头了,然后是哪边到头向哪边滑了,剩下的是你的 //TODO

欢迎各路大神对它进行拓展并发起 pr。

Demo

Comments

今天下班前被组里的小伙伴问了一个问题:

如果一个工程需要定义两个 flavor,每个 flavor 需要用一份单独的 AndroidManifest.xml,应该怎么配置?

这个问题,熟悉 gradle 的同学应该是能轻松搞定的。我们知道,gradle 在编译 apk 的时候支持给每个不同的 flavor 指定 src、res、甚至 AndroidManifest.xml 文件都没有问题。

首先我们定义两个 flavor:

1
2
3
4
productFlavors {
        normal {}
        meizu {}
    }

为了让两个 flavor 分别取不同的 AndroidManifest.xml,我们在 src 下面建立一个叫 meizu 的文件夹,里面单独放置这个 flavor 要用的清单文件,就像这样:

然后我们配置 sorceSets 闭包:

1
2
3
4
5
6
7
8
sourceSets {
        main {
            manifest.srcFile 'src/main/AndroidManifest.xml'
        }
        meizu {
            manifest.srcFile 'src/meizu/AndroidManifest.xml'
        }
    }

src/meizu/AndroidManifest.xmlsrc/main/AndroidManifest.xml的区别在于,前者删掉了 SecondActivity 的 action,理论上,如果我们编译 meizu flavor,那么在点击按钮之后,因为采用了 action 的方式来启动 activity,会因为 action 找不到导致失效。然而结果是这样吗?大家可以试一下。

结果是我们依然可以很顺利地跳到 SecondActivity…

为什么?

这就需要大家了解 gradle 在编译时,对 manifest 采用的 merge 策略。

引用一下 Ezio Shiki知乎上的一段回答:

Manifest可以通过Merge的方式合并多个Manifest源。通常来说,有三种类型manifest文件需要被merge到最终的结果apk,下面是按照优先权排序:productFlavors和buildTypes中所指定的manifest文件应用主manifest文件库manifest文件简单来说,manifest的merge会将每个元素及其子元素的节点和属性进行合并。

例如:

1
2
3
<activity
    android:name=com.foo.bar.ActivityOne
   android:theme=@theme1/>

1
2
3
<activity
    android:name=com.foo.bar.ActivityOne
   android:screenOrientation=landscape/>

合并会成为

1
2
3
4
<activity
    android:name=com.foo.bar.ActivityOne
   android:theme=@theme1
   android:screenOrientation=landscape/>

不过

1
2
3
<activity
    android:name=com.foo.bar.ActivityOne
   android:theme=@theme1/>

1
2
3
4
<activity
   android:name=com.foo.bar.ActivityOne
   android:theme=@theme2
   android:screenOrientation=landscape/>

合并会产生一个冲突,因为都有theme,而theme的属性不同。

要了解manifest合并的更高级应用,查看Manifest Merger

所以,看明白了吗?简单来说,AndroidManifest.xml 文件在 gradle 打包编译的时候,不是你指定哪个,它就100%去用哪个的。

  • 首先,你的 main 里面必须要有一份基本的,不可以因为要分 flavor 就把 main 里面的删掉,否则会直接编不过
  • 接着,你要知道 Manifest 的 merge 关系,从你的 flavor 到 main,它是一层层合并的,合并的规则上面已经提到了。
  • 最后,如果我有一个 Activity,或者 Service,或者 Receiver,真的要用另一份 AndroiManifest.xml 里的怎么办?

关于这个问题,官方文档给出了我们答案:

tools:node markers

没错,我们可以使用 tools:node replace 来解决我们的问题。现在来修改一下 src/meizu/AndroidManifest.xml,在 SecondActivity 的声明里加上,如下图所示:

再编译一下这个 flavor,点击按钮,可以看到报错了。

现在已经没有对应的 Activity 来解析这个 action 了,也就是说我们为 meizu 这个 flavor 指定的 AndroidManifest.xml 总算“生效”了。

Demo 地址:https://github.com/shawnlinboy/Android-MultiFlavors

参考文章:

https://www.zhihu.com/question/22842123/answer/55675046

http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger#TOC-tools:node-markers

http://my.oschina.net/fallenpanda/blog/373183

Comments

好久不更新博客,上来讲一下最近踩道的一个坑,顺便感觉可以普及一下在 AsyncTask 更新 UI 时的正确姿势

最近我负责的一个模块,后台数据统计总在报 Glide 加载图片的时候报错导致停止运行,堆栈大概是这个样子的:

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
29
30
31
java.lang.RuntimeException: Unable to destroy activity {MY PACKAGE NAME}: java.lang.IllegalStateException: Activity has been destroyed
    at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4097)
    at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:4115)
    at android.app.ActivityThread.access$1400(ActivityThread.java:177)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1620)
    at android.os.Handler.dispatchMessage(Handler.java:111)
    at android.os.Looper.loop(Looper.java:194)
    at android.app.ActivityThread.main(ActivityThread.java:5771)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1004)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:799)
Caused by: java.lang.IllegalStateException: Activity has been destroyed
    at android.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1383)
    at android.app.BackStackRecord.commitInternal(BackStackRecord.java:745)
    at android.app.BackStackRecord.commitAllowingStateLoss(BackStackRecord.java:725)
    at com.bumptech.glide.manager.RequestManagerRetriever.getRequestManagerFragment(SourceFile:159)
    at com.bumptech.glide.manager.RequestManagerFragment.onAttach(SourceFile:117)
    at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:865)
    at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1079)
    at android.app.BackStackRecord.run(BackStackRecord.java:852)
    at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1485)
    at android.app.FragmentManagerImpl.dispatchDestroy(FragmentManager.java:1929)
    at android.app.Fragment.performDestroy(Fragment.java:2279)
    at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1029)
    at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1079)
    at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1061)
    at android.app.FragmentManagerImpl.dispatchDestroy(FragmentManager.java:1930)
    at android.app.Activity.performDestroy(Activity.java:6297)
    at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1151)
    at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4084)

于是呢,本着对开源事业的满腔热血,我二话没说便到 Glide 下面给丫开了 issue。当然了,在此还是要赞扬一下 @TWiStErRob, 这位兄台看起来不像是 Glide 的官方作者,但一直很热心地回答着各路神仙给 Glide 开的 issue。于是毫无例外我的也很快得到了回复。但答案似乎并没有太多卵用,无非就是让我提供更多细节给他们排查……算了,求人不如求自己,给他们提供细节之前,不如我自己查一遍吧。

晚上花了一点点时间理了一下整个流程:首先,既然是 android.app.FragmentManagerImpl.enqueueAction 报的 Activity has been destroyed,肯定要想到是哪个 Activity 和它 attach 的 Activity,因为这个模块从头到尾都是我一个人在弄,所以这并不是很难。我追到了我在一个 Fragment 里,假设叫它 MyMainFragment,在这里里面我调用 Glide 加载了一些图片到 RecyclerView 中,这些看起来都没什么问题。但为什么会报 Activity has been destroyed ?我想到了去它 attach 的 Activity 去看 MyFragment 当时是怎么被 commit 进来的。

MyMainFragment attach 的 Activity 中,我看到了如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private class FireUpTask extends AsyncTask<Void, Void,Void> {

        @Override
        protected Void doInBackground(Void... params) {
            handleIntent();
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            initFragment();
        }
    }

    private void initFragment() {
        if (!isDestroyed()) {
            FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
            fragmentTransaction.add(R.id.main_container, MyMainFragment.newInstance());
            fragmentTransaction.commitAllowingStateLoss();
        }
    }

这个 FireUpTask 在 Activity 的 onCreate() 方法中被创建并执行,其实要做的很简单,无非就是想在后台异步把跳转进来的 Intent 处理完,然后再切到 MyMainFragment,看起来是没什么问题,而且为了防止有时候操作很快或者 Monkey 测试的时候如果切到 MyMainFragment 时 Activity 已经被销毁,我还特地加多了 if (!isDestroyed()) 判断,可为什么还是出问题?

先看一下 isDestroyed()) 方法:

Returns true if the final onDestroy() call has been made on the Activity, so this instance is now dead.

看来,SDK 告诉我们,如果这个方法返回 true ,那证明 Activity 的最后 onDestroy() 已经被调用,Activity 实例现在已经挂掉了。对啊!!!确实是这样啊,我都加了这个判断了啊,可为什么还是报错?

别着急,就在 isDestroyed()) 下面,还有一个方法,isFinishing()),于是赶紧看了一下文档:

Check to see whether this activity is in the process of finishing, either because you called finish() on it or someone else has requested that it finished. This is often used in onPause() to determine whether the activity is simply pausing or completely finishing.

这下子一目了然了吧,如果你的 Activity 正在结束,或者因为你主动调了 finish() (我在这个 Activity 里确实有一处会主动调 finish(),又或者因为其它别的什么鬼导致了 Activity 被请求销毁,这个时候可能 isDestroyed()) 可能还没有来得及返回 true,但是 isFinishing()) 就会返回 true 告诉你 Activity 确实正在被停止。

为了证实这一点,我尝试搜索了 Android 源码对这两个方法的使用。发现 Google 官方在拨号应用里就尝试做出了这样的判断:

http://androidxref.com/6.0.0_r1/xref/packages/apps/Dialer/src/com/android/dialer/calllog/ClearCallLogDialog.java#74

在“电话”应用的清除通话记录对话框中,Google 也是简单粗暴地 new 了一个 AsyncTask 来在后台清掉通话记录,然后在 onPostExecute(Void result) 中去更新 UI。亮点在于,大家可以看 Google 的工程师写了什么:

1
2
3
4
5
6
7
final Activity activity = progressDialog.getOwnerActivity();
if (activity == null || activity.isDestroyed() || activity.isFinishing()) {
  return;
}
if (progressDialog != null && progressDialog.isShowing()) {
  progressDialog.dismiss();
}

它拿了这个 dialogFragment 的宿主 Activity,然后对当前 Activity 的“死活”做出了判断,在 activity == null || activity.isDestroyed() || activity.isFinishing() 这三个都不会发生的时候,才会继续后面的操作。

这是至关重要的!虽然我们都知道,Dialog 这种东西是必须 attach 在一个带合法 Window Token 的组件,比如 Activity 或者 Frgament 上,理论上只要 Dialog 显示着,这个组件都不会被销毁。但是,我们却无法考虑到一些极端情况,比如有的用户手速确实很快,或者有些机器性能确实比较好反应很快,或者,你的应用在跑 Monkey 测试的时候,更加有可能出现这种 Activity 提前挂掉的情况。这个时候,如果应用内部不 handle 这个问题,那么呵呵呵,停止运行就来了。

回到我自己的这个问题,既然可以从源码里读到 Google 给出的答案,下面就是修自己的锅了,很简单,我们也可以仿照加上类似的处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private class FireUpTask extends AsyncTask<Void, Void,Void> {

        @Override
        protected Void doInBackground(Void... params) {
            handleIntent();
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            if (isDestroyed() || isFinishing()) {
                return;
            }
            initFragment();
        }
    }

    private void initFragment() {
            FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
            fragmentTransaction.add(R.id.main_container, MyMainFragment.newInstance());
            fragmentTransaction.commitAllowingStateLoss();
    }

这样一来,如果 AsyncTask 跑完,准备去切 Fragment 的时候,Activity 已经挂了,这个时候就不会再进到 transaction 里面,自然 Fragment 也不会被 attach 进来,自然 Glide 去加载图片那些问题就都不会有了。

看来平时还是要多看看 Android 源码,感觉很多坑 Google 的工程师也知道并且有填坑攻略,但还是要自己去发现的。

Comments

Why Proguard

Proguard 是什么?要清楚这个概念,我们先看看 Proguard 官方是怎么定义的,再看看 Android 官方是怎么定义

Proguard 官方

ProGuard is a free Java class file shrinker, optimizer, obfuscator, and preverifier. It detects and removes unused classes, fields, methods, and attributes. It optimizes bytecode and removes unused instructions. It renames the remaining classes, fields, and methods using short meaningless names. Finally, it preverifies the processed code for Java 6 or higher, or for Java Micro Edition.

ProGuard 是一个免费的压缩、优化、混淆,预验证 Java 类的工具。它能在编译期间检测并移除没有用到的类、变量、方法和属性,也能优化字节码并且移除没有用到的指令。ProGuard会把那些类、变量、和方法用一些短小且无意义的名称去重命名。最后,对于 Java 6 或者更高的版本,或者 Java Micro Edition,它还会预校验已处理的类代码,从而利于更快加载。

看起来有点意思,再来看一下 Android 官方的定义

The ProGuard tool shrinks, optimizes, and obfuscates your code by removing unused code and renaming classes, fields, and methods with semantically obscure names. The result is a smaller sized .apk file that is more difficult to reverse engineer. Because ProGuard makes your application harder to reverse engineer, it is important that you use it when your application utilizes features that are sensitive to security like when you are Licensing Your Applications.

ProGuard 通过移除未使用的代码和使用一些语意模糊的名字来重命名类、变量、方法和属性名,从而达到压缩、优化,和混淆代码的目的。最终可以得到一个更小的 .apk 文件,这个文件会增大软件逆向工程(反编译)的难度。正因为 ProGuard 会让你的应用更加难以被逆向工程反编译,所以对于独立应用而言,如果你对你的代码安全很敏感,建议在签名阶段还是“ ProGuard 一下” 。

Read on →

Copyright © 2014 - 2017 - linshen - @ . +