2017年5月 Android 开发遇到的问题

2017年5月,挺久没写 Android 应用了,技术都落后了,感觉像是重新开始学 Android 开发一样。记录一些期间遇到的问题。

# Kotlin

# 配置 Kotlin

// project/build.gradle
buildscript {
    ext.support_version = '25.3.1'
    ext.kotlin_version = '1.1.2'
    ext.anko_version = '0.9.1'

    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}
// module/build.gradle
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

dependencies {
	compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    compile "org.jetbrains.anko:anko-common:$anko_version"
    compile "org.jetbrains.anko:anko-sdk15:$anko_version"
    compile "org.jetbrains.anko:anko-support-v4:$anko_version"
    compile "org.jetbrains.anko:anko-appcompat-v7:$anko_version"
}

# Anko

Anko 是 JetBrains 搞的一个强大的库,主要用来替代 XML 生成 UI 布局,同时使用扩展函数在 Android 框架中增加新功能让你少写很多模版代码。

扩展函数并不是真正地修改了原来的类,它是以静态导入的方式来实现的。扩展函数可以被声明在任何文件中,因此有个通用的实践是把一系列有关的函数放在一个新建的文件里。

Anko 所有的包

anko-common
anko-design
anko-sqlite
anko-stubs
anko-percent

anko-sdk15
anko-sdk19
anko-sdk21
anko-sdk23

anko-support-v4
anko-appcompat-v7
anko-recyclerview-v7
anko-cardview-v7
anko-gridlayout-v7

# 委托属性

我们可以通过委托这个属性的值给另外一个类,这个就是我们知道的委托属性

当我们使用属性的get或者set的时候,属性委托的getValuesetValue就会被调用。

属性委托通过 by 关键字来指定委托对象。

# Kotlin 消除空指针异常

类型默认为不可空类型,这样就可以放心调用该变量的各种方法。

也可以在类型后加 ? 变为可空类型,对于可空类型又提供了几种方式避免 NPE:

  1. 使用 if null 包裹
  2. 安全的调用:b?.length() 若 b 为空则返回 null
  3. Elvis 操作符:b?.length() ?: -1 若 b 为空则返回 -1
  4. 对于 NPE 爱好者: b!!.length() 若 b 为空则抛出 NPE
  5. 安全转型: `val aInt: Int? = a as? Int

# 其他问题

# AnkoLogger 无法使用?

需要先实现 AnkoLogger 接口

class MainActivity : AppCompatActivity(), AnkoLogger {}

依旧打印不出来??

AnkoLogger 使用 isLoggable 决定是否打印日志,默认是 INFO 级别(实测好像是 WARN 级别)。

setprop log.tag.LifecycleActivity VERBOSE

# Realm + Kotlin 数据类

data class Dog(@PrimaryKey var id: String, var name: String, var age: Int) : RealmObject()
Error:Class "Dog" must declare a public constructor with no arguments if it contains custom constructors.

正确姿势

// 或者给所有的参数都加默认值——保证有一个无参构造函数
open class Dog : RealmObject() {
    open var name: String? = null
    open var age: Int? = null
}

# Kotlin 插件配置问题

Unresolved reference: kotlinx

需要启用 kotlint 插件,AS 配置的并没有启用。

apply plugin: 'kotlin-android-extensions'

# 获取文件路径

getPath: 文件构造时传入的字符串

getAbsolutePath: 文件的绝对路径

getCanonicalPath(): 在 absolutePath 的基础上再解析 . .. 等符号,得到唯一规范的表示。

# 一言不合搞个新名词

RxJava

RxAndroid

Retrofit

Realm

Glide

Fresco

Kotlin

Anko

# Gradle

Gradle 是 Android Studio 默认使用的构建工具,用来进行编译、运行、签名、打包、依赖管理等功能。

Gradle 和 Android Studio 其实并没有关系,有了 Android Gradle Plugin 才能在 AS 上使用 Gradle。

为了能让每个项目都能配置一个指定版本的 Gradle,Google 推出了 Gradle Wrapper 的概念——每个项目本地的一个小型的 Gradle。

# Android 项目包结构

* app
	* (class) Constant 常量
	* (class) Application
* ui
	* fragment
	* activity
	* base
	* dialog
	* adapter
	* custom
* service
	* (which servie) ...
* entity 
	* local
	* remote
* manager
	* (which manager) ...
* util
	* (which util)...

mkdir app ui service entity manager util extention \
ui/fragment ui/activity ui/base ui/dialog ui/adapter ui/custom \
entity/local entity/remote 

# Activity 生命周期

onCreate -> onStart -> onResume -> onPause -> onStop -> onRestart -> onDestroy

3种生存期:

onCreate 完整生存期 onDestroy

onStart 可见生存期 onStop

onResume 前台生存期 onPause

# 微小的工作

# AS 切换标签页

Window->Editor tabs->Select Next Tab

shift+cmd+[]

# AS 删除 module

Android Studio 中删除一个 module 要现在 Project Structure 中移除 module 配置。

# XML 什么时候转换为 Java 代码的?

是在运行时通过 LayoutInflater 解析的

# [rxjava 编译失败](https://github.com/hotchemi/tiamat/pull/10/files/f02b90b35541022a74b7754136b067ac5#### 9b939c7#diff-a0e411807c850ecfa7a01b0be5678cae)

Error:Execution failed for task ':app:transformResourcesWithMergeJavaResForDebug'.
> com.android.build.api.transform.TransformException: com.android.builder.packaging.DuplicateFileException: Duplicate files copied in APK META-INF/rxjava.properties
android {
    packagingOptions {
        exclude 'META-INF/rxjava.properties'
    }
}

Error:Execution failed for task ':hawkins:transformResourcesWithMergeJavaResForDebug'.

com.android.build.api.transform.TransformException: com.android.builder.packaging.DuplicateFileException: Duplicate files copied in APK org/bytedeco/javacpp/macosx-x86_64/libusb-1.0.dylib File1: /Users/keng42/.gradle/caches/modules-2/files-2.1/org.bytedeco.javacpp-presets/libfreenect/0.5.3-1.3/736d65a3ef042258429d8e7742128c411806b432/libfreenect-0.5.3-1.3-macosx-x86_64.jar File2: /Users/keng42/.gradle/caches/modules-2/files-2.1/org.bytedeco.javacpp-presets/libdc1394/2.2.4-1.3/f1498dacc46162ab68faeb8d66cf02b96fe41c61/libdc1394-2.2.4-1.3-macosx-x86_64.jar

# 抽屉出现在状态栏下面

<item name="android:windowTranslucentStatus">true</item> 

# 获取 Drawable 的正确姿势

ContextCompat.getDrawable(this, R.drawable.ic_label_black_24dp)

# 动态添加抽屉菜单项

颜色未解决

fun tintDrawable(drawable: Drawable, colors: ColorStateList): Drawable {
    val wrappedDrawable = DrawableCompat.wrap(drawable)
    DrawableCompat.setTintList(wrappedDrawable, colors)
    return wrappedDrawable
}

private fun addLabels() {
    val colors = listOf(R.color.white, R.color.red, R.color.orange, R.color.yellow,
            R.color.green, R.color.teal, R.color.blue, R.color.grey)
    val icon = ContextCompat.getDrawable(this, R.drawable.ic_label_black_24dp)

    val titles = listOf("white", "red", "orange", "yellow", "green", "teal", "blue", "grey")
    val sm = navView.menu.addSubMenu(R.string.label)
    titles.forEachIndexed { index, title ->
        val item = sm.add(title)
        item.icon = tintDrawable(icon.mutate(), ColorStateList.valueOf(colors[index]))
    }
}

# @android:是什么意思?

android:theme="@style/Theme.AppCompat.Dialog"
android:theme="@android:style/Theme.Holo.Dialog"

# 一个 Activity 管理多个 Fragment

采用一个 Activity 管理多个 Fragment 的情况,有些需要 fitSystemWindows 有些不需要该怎么实现?

目前只能手动把 AppBarLayout 的 paddingTop 设置28dp。

fitSystemWindows 只有根节点才有效?

# ConstraintLayout 包裹 DrawerLayout 会出现

java.lang.IllegalArgumentException: DrawerLayout must be measured with MeasureSpec.EXACTLY.

# kotlinx.android.synthetic fragment

https://stackoverflow.com/questions/34541650/kotlin-android-extensions-and-fragments

# Fragment 菜单

https://stackoverflow.com/questions/8308695/android-options-menu-in-fragment

# 4种启动模式

standard: 默认,创建新实例入栈

singleTop: 仅栈顶非当前活动的时候创建新实例入栈

singleTask: 栈内仅保留一个该活动的实例,并将这个活动之上的所有活动都出栈

singleInstance: 为互动创建一个新栈

# xmlns

xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"

xmlns 是使用专门用来声明命名空间的保留字

android, app, tools 是命名空间的前缀

http://schemas.android.com/xxx 是命名空间的唯一标识符

命名空间的声明就是将一个前缀与一个URI关联起来

# view.animate().withEndAction() 仅支持 API16 以上?

ViewCompat.animate(view).withEndAction(runnable)

# Android Studio 各种卡?

首先在设置里打开 Show memory indicator 查看当前设置的最大内存

然后在 Help -> Edit Custom VM Options 编辑

-Xms4096m
-Xmx4096m
-XX:MaxPermSize=4096m
-XX:ReservedCodeCacheSize=2048m

# 多个 Fragment 共存的情况下,菜单全显示到第一个 Fragment 的 Toolbar 上?

override fun onHiddenChanged(hidden: Boolean) {
  super.onHiddenChanged(hidden)
  if (!hidden) {
    (activity as SoraActivity).setSupportActionBar(toolbar)
  }
}	

# macOS AVD 无法启动

使用命令行启动

cd ~/Library/Android/sdk/emulator
./emulator -list-avds
./emulator @Nexus_5_API_24

报错

Failed to sync HAX vcpu contextInternal error: Initial hax sync failed

最终,关闭 docker 之后顺利启动。

# macOS Android Device Monitor 无法启动

这货应该是通过 /usr/libexec/java_home 来寻找 JDK 的,所以要确保 JDK 有正确安装。

这货继续要求要安装 java6,这时候就搞事情了!!!

sudo nano `/usr/libexec/java_home`/../Info.plist
<key>JVMCapabilities</key>
<array>
    <string>CommandLine</string>
</array>

修改为

<key>JVMCapabilities</key>
<array>
    <string>CommandLine</string>
    <string>JNI</string>
    <string>BundledApp</string>
</array>
sudo mkdir -p /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk\
/Contents/Home/bundle/Libraries

sudo ln -s /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk\
/Contents/Home/jre/lib/server/libjvm.dylib\
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk\
/Contents/Home/bundle/Libraries/libserver.dylib