AAD-20–2

Android kotlin fundamentals 3–4

https://codelabs.developers.google.com/android-kotlin-fundamentals/

Android KTX

KTX是Google给Android提供的一些kotlin扩展,让我们调用起来在语法上更加kotlin,本质上是没有增加新的API,只是让代码更加简洁。
但是会产生一个混用的问题:使用了至少两种方法调用同样的代码
全部列表
dependencies {
implementation "androidx.core:core-ktx:1.3.1"
implementation "androidx.collection:collection-ktx:1.1.0"
implementation "androidx.fragment:fragment-ktx:1.2.5"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
implementation "androidx.navigation:navigation-fragment-ktx:2.3.0"
implementation "androidx.navigation:navigation-ui-ktx:2.3.0"
implementation "androidx.palette:palette-ktx:1.0.0"
implementation "androidx.work:work-runtime-ktx:2.4.0" implementation "com.google.android.play:core-ktx:1.8.1"
}
还包括firebase等

Fragment in Activity

Fragment放到Activity的方法1: 
在Activity的layout中添加对应的Fragment类,这个Fragment类在其onCreateView中load自己的xml:
<fragment
android:id="@+id/titleFragment"
android:name="com.example.android.navigation.TitleFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
这种方法下在AS中不能preview的:因为AS不够强加?其实也没有必要,否则电脑就不够用了。使用data binding:
class TitleFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val binding = DataBindingUtil.inflate<FragmentTitleBinding>(inflater,
R.layout.fragment_title,container,false)
return binding.root
}
}
这种方式相当于:
- 在Activity的layout中定义Fragment的占位符,一个Activity可以包括多个Fragment
- Android layout文件中有很多地方使用 name:这并不是label,而是对应的class

Fragment in Activity

添加Fragment到Activity,手动方式
- layout 相互之间独立,无关
- 在Activity中通过supportFragmentManager来load fragment
- R.id.container 相当于一个占位,通常是一个layout,单独的文件layout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, MainFragment.newInstance())
.commitNow()
}
}

ShareCompat: Share to other Apps

val shareIntent = ShareCompat.IntentBuilder.from(this)
.setText(“YourString”).setType("text/plain")
.intent
try {
startActivity(shareIntent) // 仅仅是启动share
}
具体的多媒体类型见各种set,一些读写例子
share 图片: .setType("image/png").setStream(imageUri)
关于Uri
- "URI" is defined in RFC 2396,A Uniform Resource Identifier
- android.net.Uri
- 主要用在Activity或者App之间共享数据, Content providers
问题: 我想share一个内存中的图片,比如一个正在编辑但没有存的图片?

Android app data

App的数据:/data/data/com.xx.yy/一般包含的目录:
- database
- lib
- files
- shared_prefs
- cache
Context:
getFilesDir() or getCacheDir()
getExternalFilesDir() or getExternalCacheDir()
很多函数在class Environment更广泛的话题:app data & files问题:Android一个app如何访问其他目录的数据?

ask-for-permission model

- from Android 6.0(Marshmallow, API 23)
- add permissions to manifest
- ContextCompat.checkSelfPermission() to check
- ActivityCompat.shouldShowRequestPermissionRationale( // 给理由
- ActivityCompat.requestPermissions // 请求权限
- callback onRequestPermissionsResult(
详细过程见这个例子,以及官方代码
过程还是挺麻烦的。
问题:如何连续请求多个权限?以及被用户拒绝后如何处理
问题:给理由那个地方貌似要自己弹对话框,这有点奇怪?还是iOS的好一些

Activity Lifecycle vs Fragment Lifecycle

Activity的对象和onCreate是绑在一起的,当调用onCreate的时候,这个java object已经重新create了,可以查看到对象的内存改变了。Fragment多了两个过程:
onCreateView:
onActivityCreated: 这个名字很让人疑惑。其实整个fragment的lifecycle都让我觉得不自然。
- onAttach/onDetach 居然是最开始最后,感觉更应该放在靠中间的位置
- onCreate/onCreateView都含有参数Bundle savedInstanceState
这样好的话,意味着每个callback流程都不应该有长时间,否则就会出现闪烁
Activity lifecycle:
- onCreate (onRestoreInstanceState is after onCreate if not nil)
- onStart(<- onRestart)
- onResume: It is on top of an activity stack and visible to user
- onPause
- onStop(-> onRestart)
- onDestroy(onSaveInstanceState is before onDestroy)
Fragment lifecycle:
- onAttach
- onCreate
- onCreateView
- onActivityCreated
- onStart
- onResume
- onPause
- onStop
- onDestroyView
- onDestroy(onSaveInstanceState is at any time before onDestroy)
- onDetach

call activity.finish():
- in onCreate -> then onDestroy, no other callbacks
- in onStart -> then onStop, no onResume/onPause

Activity Configurations

对于Activity来说,当旋转屏幕时,这个Activity会被destroy立即重新create一个,而且java对象也重新来一个。两者的联系由developer自己定义:
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(KEY_REVENUE, revenue) //...
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0) // ...
}
// ...
}
这种destroy和re-create是由Android OS来操作的,因此OS是知道。
我一直觉得Jetpack里viewModel那个能一直关联到某个activity/fragment的行为比较诡异,某种程度上还是Android OS里面做了特殊的实现,而不是开放给developer。
另外的是:当使用back键退出app之后再重新进入,和kill是一样的。这种情况下和configuration发生变化完全不同。The most commonly used values are "orientation", "screenSize", "screenLayout", and "keyboardHidden": 我没有注意到keyboardHidden也属于config。- 在Menifest中对应的Activity:
android:configChanges="orientation|keyboardHidden"
- 会callback: onConfigurationChanged(),只有在声明的config才会在这儿,其实大部分时候不需要实现这个函数
所有的config:比如colorMode,不知道darkMode是不是也用这个,貌似是uiMode

android:supportsRtl

Right-to-Left
我们的layout默认是从左到右的坐标体系,但是如果该项为true,也就当系统属性选择支持从 右到左 时,layout做相应的调整。
比如 accessibility,以及阿拉伯语等情况下。

Activity finish in onCreate

You can call finish() from within this function, in which case onDestroy() will be immediately called after onCreate(Bundle) without any of the rest of the activity lifecycle (onStart(), onResume(), onPause(), etc) executing.
onCreate里面可以调用finish,然后直接就onDestroy()

Jetpack Lifecycle-aware components

使用这个最直接的好处就是代码分离,不再直接依赖onCreate等函数callback
androidx.lifecycle
OnLifecycleEvent:
public enum Event {
ON_CREATE
,
ON_START
,
ON_RESUME
,
ON_PAUSE
,
ON_STOP,
ON_DESTROY
,
ON_ANY
}
use annotation:
@OnLifecycleEvent(Lifecycle.Event.ON_START)
LifecycleOwner
A class that has an Android lifecycle. These events can be used by custom components to handle lifecycle changes without implementing any code inside the Activity or the Fragment. 主要是Activity和Fragment,
LifecycleObserver
Marks a class as a LifecycleObserver. It does not have any methods, instead, relies on OnLifecycleEvent annotated methods.
LifecycleEventObserver
If a class implements this interface and in the same time uses OnLifecycleEvent, then annotations will be ignored. 只有一个方法:
onStateChanged(LifecycleOwner source, Lifecycle.Event event)
和LifecycleObserver是一个意思,同时实现的时候优先
DefaultLifecycleObserver
采用显示interface(6个方法)的方式而不是annotation
这儿有三种不同的实现方式,看自己的需要:
- 继承LifecycleObserver,并使用OnLifecycleEvent 属性
- 实现LifecycleEventObserver,并使用onStateChanged,一个方法
- 实现DefaultLifecycleObserver,override6个方法
几个不同的角色:
- lifecycle: 抽象类, 管理observers
- LifecycleOwner: interface能够返回一个Lifecycle的class,如Activity/Fragment,
- LifecycleObserver: interface,也就是我们想要分离的代码和类具有生命周期
Activity/Fragment是LifecycleOwner,能否返回一个lifecycle对象,我们需要用lifecycle添加(删除)addObserver/removeObserver某个具体的observer
- 可以实现一个owner,但很少这样做。

java: Runnable vs Thread

Runnable is interface
Thread is class
public void run() {} //
都是无参函数run,一般而言Runnable更好:因为无需一个类继承

android: Looper & Handler

官方文档说得明白:一般线程Thread是没有消息循环和MessageQueue的,我们可以通过Looper来给Thread增加消息循环的功能,然后就可以post/receive消息了。一般而言UI线程是有消息循环的。A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler it is bound to a Looper. It will deliver messages and runnables to that Looper's message queue and execute them on that Looper's thread.给一个普通线程增加消息循环:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
如果你有一个线程的handler,你就可以给这个线程发送消息。一些相关:
Looper.getMainLooper(): 静态方法获取主线程的looper
Looper.myLooper(): 静态方法获取当前线程的looper
Looper.loop(): 静态方法,在当前的线程中开始消息循环
looper.quit(): 我估计这方法非常重要,释放不需要的资源
looper.getThread()
looper.getQueue()
looper.isCurrentThread(): 这个方法有趣
handler.getLooper(): 获取一个消息循环

Handler

Handler有一些常见的方法:
- 获得一个线程的looper,然后就可以attach一个handler到这个looper上
- 获得一个线程的handler就可以向某个线程发送message,或者是Runnable
-
如上代码:void handleMessage(Message msg),派生类必须实现的方法,处理收到的消息
private val handler: Handler
= object: Handler(Looper.getMainLooper()) {
override fun handleMessage(inputMessage: Message) {
val photoTask = inputMessage.obj as? PhotoTask//?
...
}
}
这个handler是从主线程获取消息。而且可以注册多个,每个handler收到的消息都是一样的。官方详细的例子和说明
-
handler.obtainMessage() 以及重载函数: 只是create一个Message而不是接收消息,使用这个函数的好处是更快(内部有cache或pool)。
sendMessage (Message msg):发送消息
post(Runnable r)
postDelayed (Runnable r, long delayMillis)
直接发送一个Runnable而不是Message到MessageQueue,这其实很有用处,意味着在这个线程里面执行这个Runnable,比如主线程。
可以删除消息或Runnable,
接收消息就是 handleMessage
--
removeCallbacks (Runnable r): remove队列中的Runnable,难道一个r可以添加多次?

Message

is a android.os.Parcelable
最常用是what,
如果只是简单对象,可以使用 obj/arg1(int)/arg2(int)
复杂对象可以使用setData
不是Bundle

Timber

It's a simple wrapper of android.util.Log;
支持输出ClassName+methodName+Log
以及不定参数

varargs

只是使用起来更加自然一些。Java中如何使用
public String formatWithVarArgs(String... values) {
// ...
}
formatWithVarArgs("a", "b", "c", "d");
Java中使用Array和...某种程度上是等价的,main函数就可以。
kotlin直接有vararg关键字
fun sum(vararg ts: Int): Int{ for (t in ts) }
sum(1, 2, 3)

Bundle/Intent/Context

Bundle extends BaseBundle implements Cloneable, Parcelable. A mapping from String keys to various Parcelable values.Bundle是一个标准的参数传递方式。Context: Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc. 虽然Context是这里面最简单的,但是后面隐藏的东西可能是最多的,类似于unix/linux的进程环境信息。Context本身是个抽象类,常用的有三种,Application,Activity 和Service及其派生类。Intent主要用来启动其他App/Activity/Service。An intent is an abstract description of an operation to be performed. It can be used with Context#startActivity(Intent) to launch an Activity, broadcastIntent to send it to any interested BroadcastReceiver components, and Context.startService(Intent) or Context.bindService(Intent, ServiceConnection, int) to communicate with a background Service.
而Intent里面有一个extra参数,这个参数就是一个Bundle
- Bundle用于传递参数,也可以用在Intent上
- Intent是Android标准的启动一个App/Activity/Service的方式,包含一些参数和Action
- Context指环境信息,通常我们有三种常用Context,App/Activity/Service

Option Menu

override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.main_menu, menu)
return super.onCreateOptionsMenu(menu)
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.shareMenuButton -> onShare()
}
return super.onOptionsItemSelected(item)
}
<item
android:id="@+id/shareMenuButton"
android:enabled="true"
android:icon="?android:attr/actionModeShareDrawable" //内置图标
android:title="@string/share"
android:visible="true"
android:orderInCategory="1" // menu order
app:showAsAction="ifRoom" /> // on menu or bar
几个问题:
- onCreateOptionsMenu的调用时机
- Activity/Fragment的menu是组合的,
- onPrepareOptionsMenu 用于增减menu,或者控制整个menu(返回false)
- activity.invalidateOptionsMenu() -> onCreateOptionsMenu: 主动刷新
- onPrepareOptionsMenu感觉这个函数用处不是很大?

Other

sealed class

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr() // 没有属性的class只能声明为object
sealed class Expr {
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
}
必须定义在一个文件里面,kotlin1.1之前只能使用后一种。

android:windowSoftInputMode

manifest: activityShow the input method when the activity starts 
- 打开activity就显示软键盘
- 另外有人有需求正好相反:要求隐藏键盘

android:parentActivityName

manifest: activity这是up-action的实现: up返回健 或者 左上的返回箭头Activity显示左上返回箭头:    supportActionBar?.setDisplayHomeAsUpEnabled(true)

AppCompatEditText vs EditText

当使用AppCompatActivity的时候,我们使用的EditText其实是AppCompatEditText
其他的也是一样的。
androidx.appcompat.widget.AppCompatEditText

EditText: digital keyboard

check here
<EditText
android:inputType="number"
android:digits="0123456789."
/>
使用代码:input.setKeyListener(DigitsKeyListener.getInstance("0123456789."))
但我们还可以再继续定制,比如拨号键盘等添加“#*”比如输入电话号码,推荐的做法,虽然会显示括号(),但是并不要求用户去输入,而是代码处理的-自动添加()

Activity Deeplink

How:
- add data in manifest
<activity
android:name="com.example.android.GizmosActivity">
<intent-filter android:label="@string/filter_view_http_gizmos">
<data android:scheme="http"
android:host="www.example.com"
android:pathPrefix="/gizmos" />
<data android:scheme="https" android:host="www.example.com" />
<data android:scheme="app" android:host="open.my.app" />
比较宽泛的是,scheme不限于http/https/file等,而是可以任意字符串
而且可以写多个. app://是常用的
而 yourCompanyName://这种schema被称为 universal link- how to call from JS:(不是很确定这个是否起作用)
window.location = “imdb://title/tt3569230”;
- browsers
直接在chrome等浏览器里面输入地址 imdb://title/tt3569230 是不好用的,会被导向搜索引擎
- how to call from Android
作为一个Uri放到intent里面去
- use adb to test
adb shell am start
-W -a android.intent.action.VIEW
-d "example://gizmos" com.example.android
-----
Chrome官方的表述
- 早期的chrome可以使用<iframe src="yourschema://page1">
- 现在可以如下使用intent schema
<a href="intent://scan/#Intent;scheme=zxing;package=com.google.zxing.client.android;end"> Take a QR code </a>
-----
问题:如何阻止恶意的schema?
也就是在一个app恶意注册很多schema或者专门针对某些特定的app的deeplink,或者说钓鱼。

--

--

--

Love podcasts or audiobooks? Learn on the go with our new app.

#5 Floating Windows on Android: Moving Window

Cloud Face Recognition for Mobile Applications: Overview

Performance comparison: building Android Layout with XML vs By Code vs Jetpack Compose

Improving testability in Android MVVM with Dagger 2

Stop using Gradle buildSrc. Use composite builds instead

Test your Android App using Firebase TestLab easily

Don’t let players leave your Unity game with Google Play In-App Reviews

Creating Generic Standalone Bottom Sheets in Android

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
XollXoll

XollXoll

More from Medium

KUY Token Verified on BSCSCAN

LIDO BECAME THE FIRST ETHEREUM “AVENGER” IN DEPOCKET “UNIVERSE”

bookmaker.XYZ Launches The First Betting Frontend on Azuro

Weekly Update (week №34)