2014年9月1日星期一

给Apk瘦身

原文地址:http://t.cn/RhA02xh,这里简单总结如下。

现在apk越来越大,原因有如下几个:
  1. 多dpi支持([l|m|tv|h|x|xx|xxx]dpi)
  2. Android开发工具和生态系统的发展
  3. 为了提高用户体验的高分辨率ui设计
  4. ...
减少Apk大小的,可以从如下几个方面入手:
  • Java源代码
  • 资源文件res/assets
  • native code
保持代码卫生 :去掉不用的代码和类库,保持代码干净;
运行Proguard:proguard能够便利你所有的代码路径,把没有用到的代码重apk中剔除。并且能够重命名变量,尽量精简你的代码;
使用Lint:上面的proguard只是分析Java代码。lint工具能够分析你资源文件(/res),使用./gradlew lint就能够检测出没有用的资源文件;
合理使用资源:资源文件并不必要覆盖所有的dpi,我个人只是覆盖了hdpi、xhdpi、xxhdpi;
最小化资源配置:引入第三方库的时候,去掉你用不上的资源,例如语言翻译、图片资源,可以使用Android Gradle Plugin的resConfig来配置;
压缩图片:pngquant, ImageAlpha, ImageOptin等工具
限制架构数量:一般只要支持armabi和x86就够了
尽可能的重用:android:tint改变颜色,使用xml旋转得到不同角度的图片
代码中渲染:例如帧动画

2013年8月1日星期四

Android中实现类似iOS的SwitchButton控件

iOS的SwitchButton深入人心,也被Android上的产品设计借鉴,在Android4.0中,系统就带有原生的Switch控件了。但是在老版本的Android上,怎样实现这个功能呢?
最简单的方法就是,把SwitchButton看成是个CheckBox或者ToggleButton,直接设置button属性或者background属性就可以。








但是这样实现,并没有像iOS上那样的滑动动画效果。下面介绍两种自定义的SwitchButton的控件的开源库。
http://ankri.de/switch-button-for-android-2-3-gingerbread/ 
这篇博文中详细介绍了其实现方法,也可以下载到源代码。这个可以用手拖动开关,效果非常接近了。但是也是有缺点的,不能实现背景移动,其背景是一个ProgressDrawable。
https://github.com/Issacw0ng/SwitchButton
这个是我坚果的最完美模拟iOS效果的SwitchButton。可以实现拖动,背景也可以跟着拖动。但是代码的实现上不是那么严谨,不能调整大小,不能灵活自定义样式。

这两个的实现原理基本类似,代码也比较少,有兴趣的同学可以研究一下源代码,非常值得学习的开源项目。

2013年7月15日星期一

[Android] Context, 什么是Context

注:本文翻译自Context, What Context?,原文链接在这里,作者是Dave Smith。ps:这个网站的是设计风格非常清新。

Context可能是Android应用中最常用的元素,而它也可能是最容易误用的。
Context对象是如此常见和传递使用,它可能会很容易产生并不是你预期的情形。加载资源、启动一个新的Activity、获取系统服务、获取内部文件路径以及创建view(其实还远不止这些)统统都需要Context对象来完成。我(原文作者)想做的只是给大家提供一些Context是如何工作的见解,以及让大家在应用中更有效的使用Context的技巧。

Context的类型
并不是所有的context实例都是等价的。根据Android应用的组件不同,你访问的context推向有些细微的差别。

Application - 是一个运行在你的应用进程中的单例。在Activity或者Service中,它可以通过getApplication()函数获得,或者人和继承于context的对象中,通过getApplicationContext()方法获得。不管你是通过何种方法在哪里获得的,在一个进程内,你总是获得到同一个实例。

Activity/Service - 继承于ContextWrapper,它实现了与context同样API,但是代理这些方法调用到内部隐藏的Context实例,即我们所知道的基础context。任何时候当系统创建一个新的Activity或者Service实例的时候,它也创建一个新的ContextImpl实例来做所有的繁重的工作。每一个Activity和Service以及其对应的基础context,对每个实例来说都是唯一的。

BroadcastReciver - 它本身不是context,也没有context在它里面,但是每当一个新的广播到达的时候,框架都传递一个context对象到onReceive()。这个context是一个ReceiverRestrictedContext实例,它有两个主要函数被禁掉:registerReceiver()和bindService()。这两个函数在BroadcastReceiver.onReceive()不允许调用。每次Receiver处理一个广播,传递进来的context都是一个新的实例。

ContentProvider - 它本身也不是一个Context,但是它可以通过getContext()函数给你一个Context对象。如果ContentProvider是在调用者的的本地(例如,在同一个应用进程),getContext()将返回的是Application单例。然而,如果调用这和ContentProvider在不同的进程的时候,它将返回一个新创建的实例代表这个Provider所运行的包。

保存引用
第一个我们需要解决问题是,在一个对象或者类内部保存一个context引用,而它生命周期却超过其保存引用的对象的生命周期。例如,创建一个自定义的单例,它需要一个context来加载资源或者获取ContentProvider,从而保存一个指向当前Activiy或者Service的引用在单例中。

糟糕的单例
public class CustomManager {
    private static CustomManager sInstance;
 
    public static CustomManager getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new CustomManager(context);
        }
 
        return sInstance;
    }
 
    private Context mContext;
 
    private CustomManager(Context context) {
        mContext = context;
    }
}
这里的问题在于,我们不知道这个context是从哪里来的,并且如果保存一个最终指向的是Activity或者Servece的引用是并不安全的。这是一个问题,是因为一个单例在类的内部维持一个唯一的静态引用,这意味着我们的对象,以及所有其他它所引用的对象,将永远不能被垃圾回收。假如这个Context是一个Activity,我们将保存与这个Activity相关的所有的view以及其他大的对象,从而造成内存泄漏。

为了解决这个问题,我们修改单例永远只是保存Application context:

改善的单例:
public class CustomManager {
    private static CustomManager sInstance;
 
    public static CustomManager getInstance(Context context) {
        if (sInstance == null) {
            //Always pass in the Application Context
            sInstance = new CustomManager(context.getApplicationContext());
        }
 
        return sInstance;
    }
 
    private Context mContext;
 
    private CustomManager(Context context) {
        mContext = context;
    }
}
现在这个例子中,我们的Context来自哪里都没有关系,因为我们这里保存引用是安全的。Application  Context 本身就是一个单例,所以我们再创建另外一个static引用,不会造成任何内存泄漏。另外一个很好的例子是,在后台线程或者一个等待的Handler中保存Context的引用,也可以使用这样的方法。

为什么我们不能总是引用Application context呢?正如前面说的,引用Application context永远不用担心内存泄漏的问题。问题的答案,就像我在开始的介绍中说的,是因为不同context并不是等价的。

Context的能力
Conext能做的通用操作决定于这个context最初来源于哪里。下表所列的是,在应用中常见的会收到context对象的,以及对应的每种情况,它可以用于哪些地方:

ApplicationActivityServiceContentProviderBroadcastReceiver
Show a DialogNOYESNONONO
Start an ActivityNO1YESNO1NO1NO1
Layout InflationNO2YESNO2NO2NO2
Start a ServiceYESYESYESYESYES
Bind to a ServiceYESYESYESYESNO
Send a BroadcastYESYESYESYESYES
Register BroadcastReceiverYESYESYESYESNO3
Load Resource ValuesYESYESYESYESYES

注:NO1 表示Application context的确可以开始一个Activity,但是它需要创建一个新的task。这可能会满足一些特定的需求,但是在你的应用中会创建一个不标准的回退栈(back stack),这通常是不推荐的或者不是是好的实践。
NO2 表示这是非法的,但是这个填充(inflation)的确可以完成,但是是使用所运行的系统默认的主题(theme),而不是你app定义的主题。
NO3 在Android4.2以上,如果Receiver是null的话(这是用来获取一个sticky broadcast的当前 值的),这是允许的。

用户界面UI
从前面的表格中可以看到,application context有很多功能并不是合适去做,而这些功能都与UI相关。实际上,只有Activity能够处理所有与UI相关的任务。其他类别的context实例功能都差不多。

幸运的是,在应用中这三种操作基本上都不需要在Activity范围之外进行,这很可能是android框架故意这么设计的。尝试显示一个使用Aplication context创建的Dialog,或者使用Application context开始一个Activity,系统会抛出一个异常,让你的application崩溃,非常强的告诉你某些地方出了问题。

一个并不明显的问题是填充布局(inflating layout)。如果你已经读过了我(原文作者)的上一篇文章Layout inflation,你就已经知道它可能是一个非常神秘过程,伴随一些隐藏的行为。使用正确的context关系到其中的一个行为。当你使用Application context来inflate一个布局的时候,框架并不会报错,并返回一个使用系统默认的主题创建一个完美的view给你,而没有考虑你的applicaiton自定义的theme和style。这是因为Acitivity是唯一的绑定了在manifast文件种定义主题的Context。其他的Context实例将会使用系统默认的主题来inflater你的view。导致显示的结果并不是你所希望的。

规则的路口
可能有些读者已经得出两个规则互相矛盾的结论。可能有些情况下,在某些Application的设计中,我们可能既必须长期保存一个的引用,并且为了完成与UI相关的工作又必须保存一个Activity。如果出现这种情况,我将会强烈建议你重新考虑你的设计,它将是一个很好的“反框架”教材。

经验法则
绝大多数情况下,使用在你的所工作的组建内部能够直接获取的Context。只要这个引用没有超过这个组建的生命周期,你可以安全的保存这个引用。一旦你要保存一个context的引用,它超过了你的Activity或者Service的生命周期范围,甚至是暂时的,你就需要转换你的引用为Application context。


2013年4月20日星期六

每天一点差距

突然发现本来在同一个起点的人,突然发现已经不是同一个世界的人了。一步一步走下来的差距大的让人难以想象。。。

2013年1月11日星期五

【转帖】你有多少钱,我愿意嫁给你

发信人: nndbf (晴天小丫丫), 信区: Friends 标 题: 你有多少钱,我愿意嫁给你 发信站: 北邮人论坛 (Sat Nov 12 00:48:36 2011), 站内 小毛同学说,你写的帖子上“十大”了。打开网页看到这么多留言,心中惶恐。 其实,这个年代的好女孩有很多很多。 也许是媒体、电影和现实中,把“有钱、有房、有车”的结婚要求渲染地太重要了。 又或是,每个人心中都在不安:对未来的迷茫、对自己能力的质疑。 然后把这种不安投射在爱的人身上:怕Ta也对自己丧失安全感。 但生活,不是你不安,它就会眷顾你。 必须自己勇敢面对,承认自己的不足,然后扎扎实实、认认真真地努力走下去。 ——我举不出太多,只好写一个不典型的文科生的例子了———— 07年 周末出去打工发传单,早晨9点到晚上10点,站一天发出5000份才给50块钱,还要拖着笨重的腿赶公交车回学校; 每天更换学校报刊栏的报纸,一个月学校发260块,冬天必须得徒手,戴手套捻不开,每个冬天手都是冰凉冰凉的,一直到毕业; 08年 给别人的书做校对,300多万字,一个标点一个标点的反复核对3遍,拿到了不到800块钱,然后眼睛有一星期都在红肿; 给一个网站写资讯,因为跟纽约有时差,整个大二就没好好睡过几个晚上; 09年、10年 考虑后放弃了读研,绝大部分时间在校外实习,拿着实习工资,晚上时间做兼职翻译;也卖过几个不值钱的域名,1块钱注册800块卖的;当过家教,做过收银...太多了,我自己也不太记得了。 ...... 一个什么都不太懂的文科生,赚钱能力真得比你们弱爆了。 只能身兼数职、最廉价的劳动力了。 但是这4年,我负担起了自己和妹妹上大学的全部费用,外公胃癌手术一半的手术费,并且会定期打钱给家里。大学毕业第一个月,用攒下的钱和第一份正式的薪水,买了3台冰箱给两个舅舅和小姨,给家里安装了太阳能。 我没有觉得这有什么了不起,因为过去4年的每一块钱,我都赚的不容易。 而你们会写代码、会做那个、会做那个,都比我强多了。只要努力都会过得很好。 我只是想说,我们心底有最爱的人,才会有勇气面对各种不安、压力和困惑。 我们都还年轻,还有大把的光阴可以努力。 现在可能摆在面前的有很多很多困难。 忍一忍,就过去了。 也不用过于不安。 因为真正爱我们的人,永远在原地爱着我们。 【以下为帖子原文】 ——————————————————————— 马上要毕业的小毛同学,从9月开始,你进入一轮又一轮的笔试和面试。 有时候被公司鄙视了,或者感觉面的不好,你的眼神就开始躲着我。拿到不错的offer,你又喜上眉梢。 然后我又一不小心,居然一本正经地给你分析起来 ——种种加起来不过是年薪20来万而已,扣税扣金完了再交房租,其实不多。况且又不能保证解决户口...... ————————然后你就有点点沉默了———————— 你想给我一个安稳、可靠的形象。 你想多赚点钱照顾自己,照顾好家人也照顾好我。 但多数RD都要经历这么一个平平凡凡的开头。 以后或者升职加薪,或者跳槽,或者创业,或者转型......那都是后话了。 开始就是这种不痛不痒的薪水——养的活,却生活不起。 ————————所谓对的人—————————— 大概5月或者6月的时候,我在你们BYR发过一个征友的帖子。但那段时间恰好很忙:毕业、创业、回老家......所以一直就没联系站短的人。 因为工作总打交道,慢慢注意到身边还在实习的你。 从3月份一起工作过(你貌似一直都是实习生哦)到现在,也不算相处很短了吧。你总体上没有改变我对RD男的基本看法:比较单纯、实在,靠谱。 有一句特俗的话叫“在对的时间遇见对的人”。 所谓对的人,于我而言,就是那个在身边一直埋头写代码、没空抬头看我的苦逼程序猿了。o(∩_∩)o (对了,后来你说是因为你害羞。。。。。。。。) ————————RD男 能不能别这么实在———————— 貌似每个女生都觉得衣服不够穿,鞋子不够搭配......购物是女人的天性,不花钱不shopping就跟不让吃饭一样。 但你也。。太实在了。。。 去逛街买东西,你当着售货员的面跟我说,这个你买不起。。然后我当时觉得特别尴尬。。唉,小毛同学,下次能不能小声点只跟我说呀。。。o(∩_∩)o ——————————你有多少钱,我愿意嫁给你—————— 跟RD打交道挺多,基本也了解各个公司的薪资水平。 若无什么彩票砸中、创业成功之类的彩头,借鉴一下别人的经历,也能估计一下你这职业轨迹。 你要是问我,你有多少钱,我才愿意嫁给你。 我回答肯定特干脆。 你管饭么。 你管饭,我就愿意。 我说真的。你管饭么。 -- ※ 修改:·nndbf 于 Nov 13 16:53:45 2011 修改本文·[FROM: 117.79.233.*] ※ 来源:·北邮人论坛 http://bbs.byr.cn·[FROM: 117.79.233.*]

2012年7月3日星期二

[翻译]使用U盘和git在多个电脑上共享工作

译注:因为需要频繁的在各电脑上切换,例如在实验室电脑和自己的电脑上工作,工作代码又需要在同版本控制之下。要满足这样的需求,可以使用网上免费的Git托管服务器,例如GitHub,但是免费的托管项目,又需要开源。如果有一个可以可以移动的Git服务器,问题就解决了,本文就是一个把Git服务器版本库放到U盘里面的解决方案,当然你要保证你的U盘别丢了。(第一次翻译别人的博客,翻译不正确的地方请指出。)


以下是原文翻译,原文可以在这里找到:http://timwise.blogspot.com/2008/05/sharing-work-between-computers-with-usb.html.

===
因为我在网上没有找到正好符合这个目的(使用U盘和git在多个电脑上共享工作)的解决方案,所以我做了如下的方案。

此方法可以同远程的svn服务器同时使用,如果没有的话,也可以适用。

首先,在第一个电脑上,使用git-svn clone (或者 克隆一个git库,或者新建一个工作目录),从远程代码库获取你的工作的拷贝到本地:
mkdir ~/project.git
cd ~/project.git
git-svn clone svn://project-server/trunk
git repack #for good measure
然后插上U盘,我们假设U盘是vfat/fat32/fat16格式的,并且挂载的路径为/media/flash.在U盘上创建一个空版本库,我们创建一个bare库,因为我们并不需要在此库保存工作拷贝(译注:也就是说是服务器版本库,没有工作目录)。
mkdir /media/flash/project.git
git --bare init /media/flash/project.git
然后,把U盘上的git版本库作为远程分支加到本地的git版本库中。我这里使用"flash"作为远程版分支的名字,你可以使用任何你喜欢的名字:
git remote add flash /media/flash/project.git
到这里如果你立刻push,可能会出现错误。因为fat文件系统格式在文件上并不支持可执行标记,所以所有的hook都是自动激活状态的。因为我将来并不打算使用这些hook,所以我直接删除掉所有的hook,这样做可能会产生错误,但是对我来说并没有出错。所以使用如下命令删除所有的hook:
rm /media/flash/project.git/hooks/*
然后push你的当前工作目录内容到U盘:
git push flash
此命令会复制你所有提交了的工作到U盘,即使你还没有使用"git-svn dcommit"命令push到远程的svn服务器。你还可以指定提交分支:
git push flash mybranch
现在,假设你转换到另外一个电脑上工作,插入上面的那个U盘,我们这里假设路径和设备和上面的那台电脑一样。这里做和上面完全相同的操作来从svn上克隆版本库:
mkdir ~/project.git
cd ~/project.git
git-svn clone svn://project-server/trunk
git repack #for good measure
然后添加U盘的库作为远程库,并且pull下来所有的改变:
git remote add flash /media/flash/project.git
git pull flash master
git pull flash mybranch #if you like
当你在任意一台电脑上提交了一些改变到git,或者从svn上pull了最新的版本,都可以使用如下命令更新U盘的版本库:
git push flash
然后你可以在其他电脑上,从U盘里面pull最新的版本:
git pull flash master
如果没有在push到svn之前,先向U盘中push改变,事情就非常简单。如果你向U盘中push了一些改变,然后再向SVN服务器中push,你将需最做更多一些的工作。这是因为当你运行“git svn dcommit”来push你最新的git提交日志svn服务器的时候,删除了本地的提交日志,然后从服务器上获取回来这些日志。 这意味着git将不认识你本地的改变,因为本地改变与U盘中是一样的,而有不同的提交信息和SHA1值。但你试图push到U盘的时候,将会提示 "! [rejected] master -> master (non-fast forward)",因为老版本的提交信息还在那里。 为了解决这个问题,你需要扔掉U盘上匹配的那些改变。 如下面所示使用git reset命令, 其中HEAD~1应该是你需要扔掉的提交的个数(例如,HEAD~3将扔掉U盘上你最近的3个提交):
cd /media/flash/project.git
git --bare log #to see how many changes don't have svn information
git --bare reset HEAD~1
然后你就可以正常提交了:
cd ~/project.git
git push flash

2012年6月4日星期一

把文件转换为PDF格式--JODConverter的使用

项目中需求,需要把其他文件格式转换为PDF格式,到网上找了一些解决方案。其中这里说的比较全面:http://stackoverflow.com/questions/3022376/how-to-convert-ms-doc-to-pdfhttp://ihaztehcodez.michael-lloyd-lee.me.uk/2010/10/converting-microsoft-office-word-excel.html。综合考虑通用性和效果,决定采用OpenOffice API的组件,这里有OpenOffice的SDK:http://www.openoffice.org/download/other.html#tested-sdk

后来搜索到一个开源项目,正是我所需要的:JODConverter,项目主页在这里:https://code.google.com/p/jodconverter/ 很遗憾的是,此项目作者在2011年11月份就声明停止了对此项目的维护。先下载下来此项目的文件,果然能够顺利转换格式。

1 简介
JODConverter是一个使用OpenOffice.org或者LibreOffice,自动转换文件格式的工具,支持的格式有 PDF, RTF, HTML, Word, Excel, PowerPoint, 和Flash。JODConverter可以用作Java库,或者命令行工具,或者web应用。

2 基本用法:
下载项目主页上的二进制包,解压,即可在命令行中运行:
java -jar lib/jodconverter-core-3.0-beta-4.jar test.doc test.pdf
可以把test.doc文件,转换为pdf。
在Linux下转换如果转后出现了乱码,是因为缺少了相应的字体,安装相应的字体即可。http://riches.blog.51cto.com/1167414/394610

3 文档
这里有关于JODConverter的详细文档,不过此文档是JODConverter 2.x的,但是通过此文档可以详细了解JODConverter的作用。
最准确的文档还是要参考官网:https://code.google.com/p/jodconverter/wiki/GettingStarted 和
https://code.google.com/p/jodconverter/wiki/BuildingFromSource

4 自己编译源码,运行jodconverter-sample-webapp
按照官网的提示:使用Maven编译jodconverter-core源码,但是webapp怎么编译,并没有写,从这个提问中我们可以找到答案:https://groups.google.com/group/jodconverter/browse_thread/thread/fab586c38a2f5b24。最后编译得到的.war文件,即可运行web app。


参考网址:
 1 http://stackoverflow.com/questions/3022376/how-to-convert-ms-doc-to-pdf
 2 http://ihaztehcodez.michael-lloyd-lee.me.uk/2010/10/converting-microsoft-office-word-excel.html
 3 https://code.google.com/p/jodconverter/