Appearance
2017年5月,挺久没写 Android 应用了,技术都落后了,感觉像是重新开始学 Android 开发一样。记录一些期间遇到的问题。
Kotlin
配置 Kotlin
groovy
// 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"
}
}
groovy
// 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-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
的时候,属性委托的getValue
和setValue
就会被调用。
属性委托通过 by 关键字来指定委托对象。
Kotlin 消除空指针异常
类型默认为不可空类型,这样就可以放心调用该变量的各种方法。
也可以在类型后加 ?
变为可空类型,对于可空类型又提供了几种方式避免 NPE:
- 使用 if null 包裹
- 安全的调用:
b?.length()
若 b 为空则返回 null - Elvis 操作符:
b?.length() ?: -1
若 b 为空则返回 -1 - 对于 NPE 爱好者:
b!!.length()
若 b 为空则抛出 NPE - 安全转型: `val aInt: Int? = a as? Int
其他问题
AnkoLogger 无法使用?
需要先实现 AnkoLogger 接口
kotlin
class MainActivity : AppCompatActivity(), AnkoLogger {}
依旧打印不出来??
AnkoLogger 使用 isLoggable 决定是否打印日志,默认是 INFO 级别(实测好像是 WARN 级别)。
bash
setprop log.tag.LifecycleActivity VERBOSE
Realm + Kotlin 数据类
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.
正确姿势
kotlin
// 或者给所有的参数都加默认值——保证有一个无参构造函数
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 项目包结构
bash
* 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
抽屉出现在状态栏下面
xml
<item name="android:windowTranslucentStatus">true</item>
获取 Drawable 的正确姿势
kotlin
ContextCompat.getDrawable(this, R.drawable.ic_label_black_24dp)
动态添加抽屉菜单项
kotlin
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 上?
kotlin
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
if (!hidden) {
(activity as SoraActivity).setSupportActionBar(toolbar)
}
}
macOS AVD 无法启动
使用命令行启动
bash
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,这时候就搞事情了!!!
bash
sudo nano `/usr/libexec/java_home`/../Info.plist
xml
<key>JVMCapabilities</key>
<array>
<string>CommandLine</string>
</array>
修改为
<key>JVMCapabilities</key>
<array>
<string>CommandLine</string>
<string>JNI</string>
<string>BundledApp</string>
</array>
bash
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