你的Android应用完全不需要那么多的权限

Android系统的权限从用户的角度来看有时候的确有点让人摸不着头脑。有时候可能你只需要做一些简单的事情(对联系人的信息进行编辑),却申请了远超你应用所需的权限(比如访问所有联系人信息的权限)。

这很难不让用户对你保存戒备。如果你的应用还是闭源的那用户也没办法验证是否你的应用正在把他的联系人信息上传到应用服务器上面去。即使你向用户解释你为什么申请这个权限,他们最后也可能不会相信你。所以我在过去开发Android应用的时候避免去用一些奇技淫巧,因为这会额外去申请权限,用户也会对你不信任。

经过一段时间实践后,我有这样一个体会:你在完成某些操作的时候并不一定需要申请权限的

比如Android系统中有这样一个权限: android.permission.CALL_PHONE. 你需要这个权限来让你从你的应用中调用拨号器,对吗?下面的代码就是你如果拨打电话的,对吧?

Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("1234567890"))
startActivity(intent);

错!这个权限可以让你的手机在没有用户操作的情况下打电话!也就是说如果我的应用用了这个权限,我可以在你不知情的情况下每天凌晨三点去拨打骚扰电话。

其实正确的做法是这样的——使用 ACTION_VIEW 或者 ACTION_DIAL:

Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("1234567890"))
startActivity(intent);

这个方案的动人之处在于你的应用就不用申请权限啦。 为什么不需要权限呢?因为你使用的这个 Intent 会启动拨号器,并将你设置好的号码预先拨号。比起之前的方案,现在还需要用户点击“拨号”来打电话,没有用户的参与,这个电话就打不出了。说实话,这让我感觉很好,现在很多应用申请的权限让人有点不知所措。


另外一个例子:我为我的妻子写了一个叫做 Quick Map 应用,这个应用主要是为了解决她对现有的导航应用的吐槽。她只想要一个联系人列表和一条导航到这些联系人所在地的路径。

看到这里你可能觉得我需要申请访问所有联系人信息的申请来完成这个应用:哈哈哈,你又错了!如果你看了我的源码,你就知道其实我用了 ACTION_PICK 这个Intent 启动相关应用来获取联系人地址的:

Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType(StructuredPostal.CONTENT_TYPE);
startActivityForResult(intent, 1);

这意味着我的应用不但不需要申请权限,而且还不要额外的UI。这让应用的用户体验也提升了不少。


在我看来,Android系统最酷的部分之一就是 它的 Intent 系统。因为Intent 意味着我不需要任何东西都要自己来实现。每个应用都会在Android注册它所擅长处理的数据领域,比如电话号码,短信或者联系人信息。如果什么事情都要一个应用来解决,那么这个应用会变得十分臃肿。

Android系统另外一个优点就是我可以利用其它应用所申请的权限,这样我的应用就不需要再次申请了。Android系统中的以上两点可以让你的应用变得更加简单。拨号器需要权限来拨打电话,但是我只需要一个拨打电话的intent就行了,不需要权限。因为用户信任Android自带的拨号器,但不信任我的应用,这很好啊。

我写这篇博客的意义在于在你申请权限之前,你应该至少好好读读关于Intent的官方文档,看看是否可以通过其他应用来完成你的操作。如果你想更深入的了解,你可以研究一下这篇关于权限的官方文档,里面介绍更多更精细的权限。

总之,使用更少的权限不但可以让你获取更多的用户信任,对用户来说,也让他们获得了很好的用户体验。

source:Dan Lew  I don’t need your permission!

巧用Drawable 实现Android UI 元素间距效果

本文翻译自:Grid Spacing on Android     原文作者:Cyril Mottier

在大部分的移动UI或者Web UI都是基于网格概念而设计的。这种网格一般都是有一些对其的方块组成,然后它们组合成为一个块。使用网格这样的设计原则可以有助于对齐UI元素,提升UI的一致性,同时还能让用户更加容易的获取UI上面包含的内容。简而言之,网格是一个相当的强大的设计工具。

开发者在使用网格设计原则的时候需要在UI 元素之间添加一些额外的间距,比如padding、margin或者spacing(根据你的设计方案来选择使用哪种间距) 。这些间距有利于在不同的块之间设置清晰的分隔带同时不会整体UI的可读性。这些间距对我们Android 开发者来说也不陌生,我们在设计Android 界面时,也会使用View 的padding 和 margin 来达到类似的效果。在Android 开发中,为了将UI 和业务逻辑分隔,我们会使用 XML来定义UI。这种做法对于比较固定的UI很有效果,但当这些UI元素需要根据业务逻辑来确定隐藏或者显示的状态时,这种做法就有点困难了。这篇文章就根据这种情况提出了一些Android开发技巧来应对动态的网格UI。

没有间距的UI

首先让我们来看一个简单的例子。我们创建一个简单的 LinearLayout 。然后我们在TextView (显示“Application logo”)下方再内置一个 LinearLayout ,我们在其中水平依次放置3个Button。最后得到的效果图如下图所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="@dimen/spacing_medium">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="128dp"
        android:background="@color/light_gray"
        android:gravity="center"
        android:text="@string/application_logo"
        android:textAppearance="@android:style/TextAppearance.Material.Display1" />

    <LinearLayout
        android:id="@+id/buttons_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_first"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@drawable/purple"
            android:text="@string/button_1" />

        <Button
            android:id="@+id/btn_second"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@drawable/indigo"
            android:text="@string/button_2" />

        <Button
            android:id="@+id/btn_third"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@drawable/teal"
            android:text="@string/button_3" />

    </LinearLayout>

</LinearLayout>

take_1

添加间距后的UI

上图的所展示的UI就是基于网格设计的。当时UI里面的元素之间都没有间距。为了让用户更好地区分这些UI元素,我们给id 为 @id/buttons_container 的 LinearLayout 添加属性 android:layout_marginTop="@dimen/spacing_medium" ;给id 为 @id/btn_first@id/btn_second 的两个 Button 分别添加属性 android:layout_marginRight="@dimen/spacing_medium" ;这时的UI效果如下图所示:

take_2_1

添加了间距之后,整体的UI效果好多了,可读性更强了。可当我们动态的隐藏某些 View 的时候就会出现一些问题了。我们假设第三个Button 会根据用户的设备是否安装了 Google Play Services 来决定它的展示。如果这个设备没有 Google Play Services,那我们就把这个 Button to View.GONE 的 visibility 属性设为 View.GONE, 所得效果如下图:

take_2_2

出来的效果与我们预料中的一样,第三个 Button 没有再显示了,但是第二个 Button 的右边没有与上面的TextView 右边对齐。出现这种问题的原因是:拥有 margin 属性的view 会认为margin相应方向存在邻接 view。例如,每个拥有right/top margin view会认为它的 right/top 方向有一个邻接 view,因此,这个对应 margin 也就会生效,就算这个邻接view已经隐藏了。

设置间距的折衷方案——Java 和 GridLayout

一个比较直接的解决方案就是在Java 代码里面手动改变相应的margin 值,但说实话这不是一个好的方案。另一个方案就是使用能够自动处理元素之间的间距的布局。GridLayout 就符合这样的要求。但是这个布局让人蛋疼的是元素之间的间距不能自定义,只能使用默认的间距。

 

设置间距的最佳方案——LinearLayout 的divider

实际上 LinearLayout 已经有一个处理这种元素之间的间距的属性了。这个属性却没怎么被大家发现,一直很低调,但它的效果相当神奇。所以我们说的第三个方案就是使用一个固定高宽的 Drawable 作为 LinearLayout 的 元素分隔线(divider):

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <size
        android:width="@dimen/spacing_medium"
        android:height="@dimen/spacing_medium" />

    <solid android:color="@android:color/transparent" />

</shape>

现在你就可以把这个新创建的 Drawable 设为LinearLayout 的 divider,这样这个Drawable 就能让元素之间产生间距了:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:divider="@drawable/spacer_medium"
    android:orientation="vertical"
    android:padding="@dimen/spacing_medium"
    android:showDividers="middle">

    <!-- TextView -->

    <LinearLayout
        android:id="@+id/buttons_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:divider="@drawable/spacer_medium"
        android:orientation="horizontal"
        android:showDividers="middle">

        <!-- Buttons -->

    </LinearLayout>

</LinearLayout>

take_3

 

总结

Android 框架里面有许多的特性可以用来实现一些不常见的方案,而且最后效果出其不意。定义 Drawable 就是其中一种途径。如果你能吃透Android 里面的  Drawable  ,那么你的代码也可能大大地精简。

注意:文章LinearLayout的divider 属性设置是Android API 11 之后加进去的,这意味着Android API 11之前的设备要使用这个divider需要LinearLayoutCompat

如何给你的Android 安装文件(APK)瘦身

本文翻译自:Putting Your APKs on Diet           原作者:Cyril Mottier

Android的apk文件越来越大了这已经是一个不争的事实。在Android 还是最初版本的时候,一个app的apk文件大小也还只有2 MB左右,到了现在,一个app的apk文件大小已经升级到10MB到20MB这个范围了。apk文件大小的爆炸式增长主要是因为用户对app质量的期待越来越高以及开发者的开发经验增长,具体体现在以下几个方面:

  • Android设备 dpi 的多样化 ([l|m|tv|h|x|xx|xxx]dpi)
  • Android平台的进化,开发工具的改进以及开源类库生态系统的丰富
  • 用户对高质量UI的期待
  • 其他原因

Android开发者在设计一个app的时候应该将最终发布一个轻量级app作为一个最佳实践来考虑。为什么?首先这就意味着你拥有了一个简洁,易维护代码基础。其次,官方应用商店对超过50MB的apk设置了拓展包文件下载选项,apk文件在50MB以下更容易让用户下载。最后,我们的应用程序环境是一个带宽有限,存储空间有限的环境,apk安装文件越小,下载就会越快,安装也会更快,良性循环,最后说不定用户因为这个给好评。

在大部分情况下,apk大小的增长是为了满足消费者的需要和期待。然而,我认为apk大小的增速已经超过了用户对app期待的增速。所以,很大程度上,官方应用商店里面的那些程序可以瘦身至它们现在大小的一半甚至更多。在这篇文章里面,我将写下一些关于如何给apk文件瘦身的招式,希望你们能够喜欢。

reducing_apk_file_size

 

APK 文件格式

在说如何给apk瘦身之前,让我们先来看看apk文件内部的结构到底是怎么一回事。说简单点,一个apk文件就是包含一些文件的压缩包。作为开发者,我们通过使用 unzip 命令解压这个apk文件一探apk的内部结构。下面的文件结构就是我们使用 unzip <your_apk_name>.apk1这个命令所获得的:

/assets
/lib
  /armeabi
  /armeabi-v7a
  /x86
  /mips
/META-INF
  MANIFEST.MF
  CERT.RSA
  CERT.SF
/res
AndroidManifest.xml
classes.dex
resources.arsc

我们可能对上面大部分的文件和目录都很熟悉。它们和我们在实际开发app的时候所看到得项目结构一样,包含了: /assets, /lib, /res, AndroidManifest.xml. 还有一些文件可能是我们第一次看到。一般说来,classes.dex, 它包含了我们所写的Java代码经过编译后class文件;resources.arsc 包含了预编译之后的资源文件(比如values文件,XML drawables 文件等。)。

由于apk文件只是一个简单地压缩文件,这就意味着它有两种大小:即压缩前的大小和压缩后的大小。这篇文章我将主要关注压缩后的大小。

如何减少apk文件大小

减少apk文件大小可以从几个方面入手。由于每个app都是不同的,所以没有什么绝对规则来给apk文件瘦身。作为apk文件的三个重要组成部分,我们可以考虑从它们开始入手:

  • Java 源代码
  • 资源文件(resources/assets)
  • native code

所以接下来的招式都是由减少这些组件的大小出发,进而减少整个app的大小。

掌握良好的编码习惯

这是减少apk文件至关重要的第一步。你要对自己的代码了如子掌。你要移除掉所有无用处的dependency libraries,让你的代码一天比一天优秀,持续地优化你的代码。总而言之,保持一个简洁,最新的代码基础是减少apk文件至关重要的一环。

当然,从零开始一个项目并为这个项目保持一份简洁的代码基础很容易。项目越老,这个工作就越困难。事实上,拥有一大段历史背景的项目必须要去处理各种死代码和无用代码。还好有许多的开发工具可以帮我们来做这些事情……

使用 Proguard

Proguard 是一个很强悍的工具,它可以帮你在代码编译时对代码进行混淆,优化和压缩。它有一个专门用来减少apk文件大小的功能叫做 tree-shaking。Proguard 会遍历你的所有代码然后找出无用处的代码。所有这些不可达(或者不需要)的代码都会在生成最终的apk文件之前被清除掉。Proguard 也会重命名你的类属性,类和接口,然整个代码尽可能地保持轻量级水平。

也许现在你会认为 Proguard 是一个相当有效地工具。但是能力越大,责任也就越大。现在许多开发这认为Proguard有点让人不省心,因为它会重度依赖反射。哪些类或者属性需要被处理或者不能处理都要开发者对 Proguard 进行配置。

广泛使用 Lint

Proguard 只会对 Java 代码起作用,那么对哪些资源文件呢?比如一张图片 my_image 在 res/drawable 文件夹中,没有被使用,Proguard 只会移除掉 R 类中的引用,但是图片依然还在文件夹中。

Lint 一个静态的代码分析器,你只需通过调用 ./gradlew lint这个简单地命令它就能帮你检查所有无用的资源文件。它在检测完之后会提供一份详细的资源文件清单,并将无用的资源列在“UnusedResources: Unused resources” 区域之下。只要你不通过反射来反问这些无用资源,你就可以放心地移除这些文件了。

Lint 会分析资源文件(比如 /res 文件夹下面的文件) ,但是会跳过 assets 文件 ( /assets 文件夹下面的文件)。事实上assets 文件是可以通过它们的文件名直接访问的,而不需要通过Java引用或者XML引用。因此,Lint 也不能判定某个 asset 文件在项目中是否有用。这全取决于开发者对这个文件夹的维护了。如果你没有使用某个asset 文件,那么你就可以直接清除这个文件。

对资源文件进行取舍

Android 支持多种设备。Android的系统设计让它可以支持设备的多样性:屏幕密度,屏幕形状,屏幕大小等等。到了Android 4.4,它支持的屏幕密度包括: ldpi, mdpi, tvdpi, hdpi, xhdpi, xxhdpi and xxxhdpi。但是你要知道的一点是,Android 支持这么多的屏幕密度并不意味着你需要为每一个屏幕密度提供相应的资源文件。

如果你知道某些屏幕密度的设备只有很少部分用户在使用,那么你就可以直接不需要使用相应屏幕密度的资源文件。就我个人而言,我只会为我的应用提供 hdpi, xhdpi and xxhdpi2 这几个屏幕密度的支持。如果某些设备不是这几个屏幕密度的,不用担心,Android 系统会自动使用存在的资源为设备计算然后提供资源文件。

我这么做得原因很简单。首先,这些设备屏幕密度就能覆盖我 80% 的用户。其次,xxxhdpi 这个屏幕密度只是在为未来的设备做准备,但是未来还未到来。最后,我真的不怎么关心低屏幕密度(比如mdpi 和 ldpi),无论我为这几个屏幕密度努力,结果都是令人伤心地,还不如直接让Android系统对 hdpi 资源文件进行适当地缩放来匹配相应地低端机型。

同样地,在 drawable-nodpi 文件夹里面维持一个文件也能节省空间。当然前提是你觉得对这个文件进行相应地缩放之后呈现的效果你能接受或者这个文件出现的几率很少。

资源文件最少化配置

Android 开发经常会依赖各种外部开源代码库,比如Android Support Library, Google Play Services, Facebook SDK 等等。但是这些库里面并不是所有的资源文件你都会用到。比如, Google Play Services 里面会有一些为其他语种提供翻译,而你的app又不需要这个语种的翻译,而且这个库里面还包含了我的app中不支持的 mdpi 资源文件

还好从Android Gradle Plugin 0.7 开始,你可以配置你的app的build系统。这主要是通过配置 resConfig 和 resConfigs 以及默认的配置选项。下面的 DSL (Domain Specific Language)就会阻止 aapt(Android Asset Packaging Tool)打包app中不需要的资源文件。

defaultConfig {
    // ...

    resConfigs "en", "de", "fr", "it"
    resConfigs "nodpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"
}

 

 

 

 

压缩图片

Aapt(Android Asset Packaging Tool)就内置了 保真图像压缩算法。例如,一个只需 256 色的真彩PNG图片会被aapt 通过一个颜色调色板转化成一个 8-bit PNG 文件。这可以帮助你减少图片文件的大小。当然你还可以通过Google查找相应的优化工具,比如 pngquant, ImageAlpha 和 ImageOptim 等。你可以从中选择一个适合你的工具。

还有一种只在Android平台上存在的图片文件也可以优化,它就是 9-patches。就我目前所知道,我还没发现有这个文件的优化工具。然而你只需要求你的设计师将它的可扩展区域和内容区域尽可能地减少即可。这不但可以减少资源文件的大小,还能使得以后资源文件的维护变得更加简单。

限制app支持的cpu 架构的数目

一般说来Android 使用Java 代码即可以满足大部分需求,不过还是有一小部分案例需要使用一些 native code。就像之前对资源文件那样opinionated,你可以这些 native code opinionated。 在当前的Android 生态系统中,让你的app支持 armabi 和 x86 架构就够了。这里有一篇相当不错的关于如何瘦身native 代码库的文章,你可以参考参考。

尽可能地重用

重用资源可能是你在进行移动开发时需要了解的最重要的优化技巧之一。比如在一个 ListView 或者 RecyclerView,重用可以帮助你在列表滚动时保持界面流畅。重用还可以帮你减少apk文件的大小。例如,Android 提供了几个工具为一个asset文件重新着色,在Android L中你可以使用 android:tintandroid:tintMode 来达到效果,在老版本中则可以使用 ColorFilter 。

如果系统中有两种图片,一种图片是另一种图片翻转180°得到的,那么你就可以移除一种图片,通过代码实现。比如你现在有两种图片分别命名为 ic_arrow_expandic_arrow_collapse :

expand_collapse

 

你可以直接移除掉 ic_arrow_collapse 文件,然后在ic_arrow_expand 的基础上创建一个 RotateDrawable 。这个方法也可以让你减少设计人员的工作:

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/ic_arrow_expand"
    android:fromDegrees="180"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toDegrees="180" />

 

在合适的时候使用代码渲染图像

在某些情况下,直接使用Java 代码渲染图像也能获得不错的效果。比如逐帧动画就是一个很好的例子。最近我都在尝试一些Android Wear 的开发,了解了一下Android wearable support library。就像其他的Android support library 一样,这个库里面也有一些工具来处理穿戴设备的。

不过让我吃惊的是,当我简单地构建了一个 “Hello World”示例,最后得到的apk文件竟然有1.5MB。于是我快速地研究了一下 wearable-support.aar 文件,发现这个库有两个逐帧动画,并分别支持了3种不同的屏幕密度:一个 “success” 动画 (31 frames) 和一个 “open on phone” 动画 (54 frames)。

wearable_support

 

这个逐帧success动画是被一个叫做 AnimationDrawable 所定义的:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="true">
    <item android:drawable="@drawable/generic_confirmation_00163" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00164" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00165" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00166" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00167" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00168" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00169" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00170" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00171" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00172" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00173" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00174" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00175" android:duration="333"/>
    <item android:drawable="@drawable/generic_confirmation_00185" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00186" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00187" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00188" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00189" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00190" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00191" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00192" android:duration="33"/>
    <item android:drawable="@drawable/generic_confirmation_00193" android:duration="33"/>
</animation-list>

 

这样做得好处就是 (我当然在讽刺) 每帧显示33ms,这使得整个动画保持在30fps的频率。如果每帧16ms这将会导致整个库是之前的两倍大。如果你去看源码你会发现很有趣。在 generic_confirmation_00175 这一帧 (15 行) 将持续显示 333ms。 generic_confirmation_00185 紧跟着它。这个优化节省了9个类似的帧 (包含了从176 帧到 184 帧) 。不过最后神奇的是 wearable-support.aar 竟然神奇的包含了这个9个完全无用的帧,而且还以3中屏幕密度展示。3

在代码中来渲染这样的动画明显会很花时间。然而当你维持动画运行在60fps这样的频率可以大幅度的减少apk的大小。在写这篇博文的时候,Android还没提供工具来渲染这样的动画。但是我希望Google正在开发新一代的轻量级实时渲染系统来保证material design的细节呈现。当然“Adobe After Effect to VectorDrawable” 之类的设计工具也能提供很多方便。

如何更进一步?

上面所有的招式都集中在app或者library 开发者。也许我们还可以在app分发渠道方面为apk大小做出一些改变?我想可以在app 分发服务器端做一些改进,或者在官方应用商店。例如,我们可以期待官方应用商店在用户安装app的时候为设备绑定相应的native 库而摒弃那些无用的。

同样地,我们还可以想象只根据目标设备的配置来打包应用。不幸的是,这可能破坏Android 生态一个重要的功能特性:配置热置换。事实上,Android一开始就是位处理各种实时的配置更改(语言,屏幕转向)而设计的。如果我们移除掉与目标屏幕不兼容的资源文件,这可以极大的减少文件大小。不过Android需要处理实时的屏幕密度更改。即便我们假设废除这种功能,我们仍然需要处理为不同的屏幕密度设计的图片以及其他配置(比如屏幕朝向,最小宽度等)。

服务器端的apk打包看起来很强大。但这样会冒很大得风险,因为最终传送给用户的apk会于开发者发给的服务器的完全不同。分发一些缺失资源文件的apk可能会导致app崩溃。

总结

设计就是在一个约束集里面找出最好的方案。显然apk文件的大小就是一个约束。不要害怕为了让多个方面变得更好而放松一个方面的约束。例如,当你要降低UI的渲染效果时,不要犹豫,因为这可以让apk的大小减小,同时使得app的运行也更加流畅。你99%的用户是感受不到UI质量变低的,但是他们会注意到apk文件变小了,运行也更加流畅了。总之,你需要将app各方面进行整体考虑,而不是仅仅几个方面的斟酌。

Android Studio 开发技巧集锦二

本文翻译自:http://www.developerphil.com/android-studio-tips-of-the-day-roundup-2/

行复制

Mac OS: Command+D
Windows & Linux: Ctrl+D

这个快捷键的厉害之处在于它会复制当前行同时又不会影响系统的剪贴板。效果如下图:

11-duplicate_lines

扩大或者缩小选择范围

Mac OS : alt+/
Windows & Linux: (Ctrl+w )/(Ctrl+Shift+w)

这个操作会扩大(或者缩小)当前的选择范围。比如当前选中一个变量,此操作会把选中范围扩展至句子,方法等。具体效果见下图:

12-expand_shrink_selection

包裹代码段

Mac OS : Command+Alt+t
Windows & Linux : Ctrl+Alt+t

此操作会选中一段代码段,然后使用if语句或者循环等结构包裹这段代码。当然,如果你什么都没选择,那么它会默认选择当前行代码。效果如下图:13-surround_with

查询最近编辑文件

Mac OS : Command+e
Windows & Linux : Ctrl+e

此操作将会调出最近编辑文件列表。

14-recents

代码模板

Mac OS : Command+j
Windows & Linux : Ctrl+j

此快捷键可以让你迅速地在文件中插入预留的代码片段。更有趣的是你还可以通过模板定制这些代码片段,即可以为这些代码模板设置参数。15-live_templates

 

方法整体移动

Mac OS : Command+Alt+/
Windows & Linux : Ctrl+Shift+/

这个快捷键和之前介绍过的代码行移动有点相似,只不过这个是移动整个方法体。这样就可以免掉剪切复制的累赘操作了。

15-movemethods

代码补全

Mac OS : Command+Shift+Enter
Windows & Linux : Ctrl+Shift+Enter

这个操作会将还未完成的代码结构补全。一般可以补全的情形如下:

  • 在代码行后面添加分号;
  • 为 if, while 或者 for 语句添加括号
  • 为方法声明添加括号。

16-completestatement

 

回到上次最后编辑位置

Mac OS : Command+Shift+backspace
Windows & Linux : Ctrl+Shift+backspace

让光标自动移到上次最后编辑的位置,具体效果如图:17-navigate-previous-changes

 

代码行合并

Mac OS : Ctrl+Shift+j
Windows & Linux : Ctrl+Shift+j

按照语法规则将可以合并的代码行进行合并,保持代码精简。它主要适用以下规则:

  • 合并两行评论代码时,评论变成一行,会去掉另一行的“//”,
  • 合并多行字符串的时候,会移掉“+“和双引号
  • 将变量声明定义和赋值合并。

18-joinlines

 

操作当前文件信息

Mac OS : Alt+F1
Windows & Linux : Alt+F1

此快捷键可以让你迅速地查询文件结构,存储位置,项目视图等等。一般用来查询文件的存储位置。

19-select-in

 

移除包裹代码

Mac OS : Command+Shift+Delete

Windows & Linux : Ctrl+Shift+Delete

移除代码结构中的包裹代码,比如 if 语句,  while 循环, 或者 try/catch 语句20-unwrap

 

关于 Android Dex 方法限制的一些总结

Android的编译过程

在了解这个问题之前我们先要来看看Android 应用编译的过程:

build

  1. IDE中的资源打包工具 (Android Asset Packaging Tool ,即图中的aapt) 会将应用中的资源文件进行编译,这些资源文件包括 AndroidManifest.xml文件,为Activity定义的 XML 文件等等。在这个编译过程中也会产生一个 R.java 文件,这样你就可以在你的Java代码中引用这些资源了。
  2. aidl 工具会将你项目中的所有 .aidl 接口转换成Java接口。
  3. 项目中的所有的Java代码,包括 R.java.aidl 文件,都会被Java编译器编译,然后输出 .class 文件。
  4. 接着 dex 工具就会把上一步骤产生的 .class 文件转成 Dalvik 字节码,也就是 .dex 文件。同时项目中包含的所有第三方类库和 .class 文件也会被转换成 .dex 文件,这样讲方便下一步被打包成最终的 .apk 文件。
  5. 所有的不能编译的资源(比如图片等等)、编译后的资源文件和 .dex 文件会被 apkbuilder 工具打包成一个 .apk 文件。
  6. 一旦 .apk 文件被构建好之后,如果要把把它安装到设备上面去的话,它就必须用一个debug 或者发行key来对这个apk文件签名。
  7. 最后,如果应用程序已经被签名成为发行模式的apk,你还需要使用 aipalign工具对 .apk 进行对齐优化。这样的话可以减少应用程序在设备上的内存消耗。

 

为什么会有这个Dex 方法限制

内部原因:

我们注意到在第四步的时候,会产生一个.dex 文件。Android 从之前的Dalvik 到现在Android 5.0 默认的ART 运行时环境都能够执行这个.dex 文件,它们还使用同一套指令集,即Dalvik 指令集。通过这篇关于Android 指令集格式的介绍文章中,我可以知道Dalvik 指令集是使用16位寄存器来保存项目中所有的方法引用,包括第三方的方法:

invoke-kind {vC, vD, vE, vF, vG}, meth@BBBB
B: method reference index (16 bits)

这就意味着 Android的单个.dex 文件最能引用65536个方法,在这之后的方法就无法引用了。这就是Android Dex 方法限制异常出现的原因,同时因为ART和Dalvik使用同一套指令集,这个限制在ART 运行时环境中也会存在。

外部原因:

第三方库里面包含太多的方法。这里就拿Google Play Service和Guava来举例。很多Android开发者都会用到Google Play Service库和Guava库,而你知道它们提供了多少了方法吗?Google Play Service 5.0里面就差不多包含了将近20k+方法,Guava提供了将近14k个方法。这个两个库就将近占了方法限制数目65536的半壁江山。

那么如何解决Android Dex 方法限制这个问题呢?

老方法

对于内部原因:

  1. 从上面的描述中我们知道,Android Dex 方法限制是出现在单个.dex 文件中的,那么我们可以在一个apk中使用多个.dex 文件吗?可以,Android 官方博客就给出了这个方案。(在Android5.0之前,由于大部分使用的是Dalvik 运行时环境,Dalvik 运行时环境限制一个apk只能包含一个classes.dex文件。)

对于外部原因:

  1. 使用配置脚本对第三方库中的方法进行清除;
  2. 使用ProGuard清除项目中无用的方法,不过效果不如上面的。

 

新方法(官方动作)

主要思路:使用multidex support library 让Android5.0之前的版本也能在一个apk里面包含多个.dex 文件。具体使用方法请参看这篇文章

Google不仅在工具上面做出了改进,还把自己的Google Play Service库也做了一番改动——从Google Play Service 6.5开始开始支持更细精度的依赖管理,也就是说你只需要Google Drive的api,而不需要google game,maps或者wallet等api的支持,那你就可以只引入Google Drive的api即可。这样可以在很大程度上减少Dex 方法限制出现的几率。

 

参考链接:

  • http://developer.android.com/tools/building/index.html
  • http://android-developers.blogspot.com/2011/07/custom-class-loading-in-dalvik.html
  • https://medium.com/@rotxed/dex-skys-the-limit-no-65k-methods-is-28e6cb40cf71
  • http://developer.android.com/tools/building/multidex.html
  • jakewharton.com/play-services-is-a-monolith/
  • http://www.keysolutions.com/blogs/kenyee.nsf/d6plinks/KKYE-9LP5ND
  • http://www.alittlemadness.com/2010/06/07/understanding-the-android-build-process/
  • http://source.android.com/devices/tech/dalvik/dalvik-bytecode.html
  • http://stackoverflow.com/questions/21490382/does-the-android-art-runtime-have-the-same-method-limit-limitations-as-dalvik/21492160#21492160
  • https://android.googlesource.com/platform/dalvik/+/froyo/vm/analysis/ReduceConstants.c

Android Studio开发技巧集锦一

本文翻译自:http://www.developerphil.com/android-studio-tips-of-the-day-roundup-1/

 

关于Android Studio的键位映射

Android Studio 提供了不同的键位映射(即快捷键和它对应的操作之间的映射),你可以在“Settings->Keymap”菜单里面查看当前所使用的键位映射。

高亮所有相同变量

OS X : Command+Shift+F7
Windows Or Linux:Ctrl+Shift+F7

 

01-highlight这个快捷键将会高亮当前文件中选择字符所有的出现之处。当然这个快捷键不仅仅只是一些简单地模式匹配,它还会理解当前的变量所处范围,只高亮相关的字符。

高亮之后你就可以使用“Edit → Find → Find Next/Previous”处定义的快捷方式来选择你要操作的高亮字符。

相关提示:

  • 高亮代码方法中的“return” 或者 “throw” 也会高亮这个方法的所有出口。
  • 高亮Java类的“extends” 或者 “implements” 的定义部分也会高亮对应的重写或者实现的方法。
  • 高亮import 语句也会高亮它被使用的地方。
  • Esc可以取消高亮。

在方法和内部类之间跳转

OS X: Ctrl + / ↓
Windows Or Linux: Alt+ / ↓

这个快捷键可以让你很方便的在当前文件的方法或者类上面跳转。

如果你当前处于一个方法中,此快捷键(向上)可以让你的光标跳至方法名处。这对你重构代码或者找到这个代码的使用之处很有帮助。

02-move_between_methods

类文件结构弹窗

OS X: Command+F12
Windows Or Linux: Ctrl+F12

03-filestructure这个快捷键可以帮助你展示当前类文件的方法结构。你可以使用这个快捷键弹出弹窗,查找你想要的方法名。

相关提示:

  • 你可以使用驼峰字符来过滤候选方法列表。例如:输入 “oCr” 就可以找到 “onCreate”方法。
  • 你可以选择是否展示匿名类。如果你勾选了“是”就可以很方便的查找 OnClickListener里面的OnClick方法了。

方法调用层级弹窗

OS X: Ctrl+Alt+H
Windows Or Linux: Ctrl+Alt+H

这个快捷键可以帮助你展示方法是如何被调用的。

04-callinghierarchy

定义快速查找

OS X: Alt+Space
Windows Or Linux: Ctrl+Shift+i

如果你想立马知道某个类或者方法的来源同时又不想丢掉当前的编码环境,你可以试试这个快捷键。

05-quickdefinition

收缩或者展开代码块

OS X : Alt++/-
Windows Or Linux: Ctrl+Shift++/-

06-codefolding具体效果见右图。

相关提示:

  • 你可以在“Editor → Code Folding”自定义你的代码折叠范围。

书签,保存你的代码现场

默认标签
OS X : F3
Windows Or Linux : F11
按下此快捷键就可以在你的当前光标所在行打上标签
带字母或者数字的标签
OS X : Alt+F3
Windows Or Linux : Ctrl+F11

按下此快捷键就可以在你的当前光标所在行打上标签,并提供字母或者数字标记。如果你选择的是数字标记,那么可以通过ctrl+对应的数字直接跳转至相应位置。

展示标签
OS X : Command+F3
Windows Or Linux : Shift+F11

07-bookmarks

查找Android Studio的菜单选项

OS X :   Command+Shift+A
Windows Or Linux : Ctrl+Shift+A

你可以通过输入菜单名来查找任意的菜单选项,这对那些使用频率低的操作很有效果。

相关提示:

  • 如果这个选项有对应的快捷方式,那么这个快捷键会在列表旁边显示。

08-findaction

代码行级移动

OS X : Alt+Shift+ / ↓
Windows Or Linux: Alt+Shift+ / ↓

使用此快捷键可以以一行的形式移动代码,可上可下。效果如下图。

09-movelines

删除行

OS X : Command + Backspace
Windows Or Linux : Ctrl + Y

删除当前行或者当前选择的代码。

10-deleteline