vault backup: 2025-07-22 22:14:08
Affected files: Too many files to list
This commit is contained in:
48
21.01. OS/21.04. Android/ADB tcpip.md
Normal file
48
21.01. OS/21.04. Android/ADB tcpip.md
Normal file
@@ -0,0 +1,48 @@
|
||||
Use `adb tcpip` to enable ADB over network.
|
||||
|
||||
# Setup Android Device
|
||||
## Connect Device via USB
|
||||
Connect Android device to a PC via USB, and make sure ADB recognize your device.
|
||||
```bash
|
||||
$ adb devices
|
||||
List of devices attached
|
||||
0123456789ABCDEF device
|
||||
```
|
||||
|
||||
## Enable network of ADB
|
||||
Use below command to enable network of ADB:
|
||||
```bash
|
||||
adb tcpip 5555
|
||||
```
|
||||
|
||||
## Get IP of Device
|
||||
We have to get IP of Device, because another PC need this for connection.
|
||||
|
||||
If you connect your device to router with ethernet, pass `eth0` to below command to get IP address of device.
|
||||
```bash
|
||||
adb shell "ifconfig eth0 | grep 'inet addr' | cut -d: -f2 | awk '{print \$1}'"
|
||||
```
|
||||
|
||||
If you connect your device to router with WIFI, change `eth0` to `wlan0`, like:
|
||||
```bash
|
||||
adb shell "ifconfig wlan0 | grep 'inet addr' | cut -d: -f2 | awk '{print \$1}'"
|
||||
```
|
||||
|
||||
Above command will return IP address of eth0, like:
|
||||
![[20250115_100240_WindowsTerminal_1305x87.png]]
|
||||
|
||||
**Remember your IP address.**
|
||||
|
||||
# Setup PC
|
||||
Now we have to setup our PC that in the same network with our Android device. Please note this PC **doesn't connect to your Android device via USB**.
|
||||
So we have to enable connection between PC and your Android devicem, and we need IP address of Android device, you had this at step [[ADB tcpip#Get IP of Device|Get IP of Device]].
|
||||
|
||||
Pass your IP of Android device to below command to enable connection of ADB:
|
||||
```
|
||||
adb connect 192.168.1.108:5555
|
||||
```
|
||||
|
||||
Remember to change `192.168.1.108` to your really IP address.
|
||||
|
||||
Now we can use `adb devices` to check if we can recognize Android device by IP adress:
|
||||
![[20250115_101133_WindowsTerminal_496x107.png|400]]
|
||||
12
21.01. OS/21.04. Android/ADB 取得 APK 的 icon.md
Normal file
12
21.01. OS/21.04. Android/ADB 取得 APK 的 icon.md
Normal file
@@ -0,0 +1,12 @@
|
||||
所有 apk 在安裝之後必須要向 launcher 註冊,並將 icon 存在 launcher 的 `app_icons.db` 裡面。
|
||||
launcher 的 package 是 `com.android.launcher3`,所以 `app_icons.db` 的位置在 `/data/data/com.android.launcher3/databases/app_icons.db`。
|
||||
|
||||
用 `SQLiteDatabaseBrowserPortable.exe` 之類的工具可以打開 `app_icons.db`,其內容如下:
|
||||
![[Pasted image 20220712100904.png|800]]
|
||||
|
||||
icon 欄位的blob就是icon的圖檔,看來是PNG檔。
|
||||
|
||||
## 參考
|
||||
- [Can i get the icon image of an app through adb](https://stackoverflow.com/questions/39170162/can-i-get-the-icon-image-of-an-app-through-adb)
|
||||
- [只是简单看下Launcher_Jason_Lee155的博客-CSDN博客](https://blog.csdn.net/Jason_Lee155/article/details/125096966)
|
||||
- [Android Launcher3中微信联系人快捷方式无法卸载的解决方案 - 简书](https://www.jianshu.com/p/8ba912ad537e)
|
||||
73
21.01. OS/21.04. Android/ADB.md
Normal file
73
21.01. OS/21.04. Android/ADB.md
Normal file
@@ -0,0 +1,73 @@
|
||||
## am
|
||||
### start
|
||||
用`am start`來打開一個activity,例:
|
||||
```
|
||||
adb shell am start -S com.logitech.sentineliq/.MainActivity --es cameraId 0
|
||||
```
|
||||
其中`-S`是指先停止app再打開app。還有其他的命令如下:
|
||||
```
|
||||
-D: enable debugging
|
||||
-W: wait for launch to complete
|
||||
--start-profiler <FILE>: start profiler and send results to <FILE>
|
||||
-P <FILE>: like above, but profiling stops when app goes idle
|
||||
-R: repeat the activity launch <COUNT> times. Prior to each repeat,
|
||||
the top activity will be finished.
|
||||
-S: force stop the target app before starting the activity
|
||||
--opengl-trace: enable tracing of OpenGL functions
|
||||
```
|
||||
|
||||
### 參考
|
||||
- [Android activity manager "am" command help](https://gist.github.com/tsohr/5711945)
|
||||
|
||||
## pm
|
||||
### list packages
|
||||
列出所有安裝的apk
|
||||
```bash
|
||||
adb shell pm list packages
|
||||
```
|
||||
|
||||
只列出 user 自己安裝的 apk:
|
||||
```bash
|
||||
adb shell "pm list packages -3"
|
||||
```
|
||||
|
||||
## Forward
|
||||
ADB forward用來把PC端收到的TCP轉到Android去,這樣就可以透過USB ADB達到網路的功能。
|
||||
例:
|
||||
```
|
||||
adb forward tcp:6100 tcp:7100
|
||||
```
|
||||
|
||||
上述等於:
|
||||
```
|
||||
PC Android
|
||||
http://127.0.0.1:6100 APP(port: 8080)
|
||||
| |
|
||||
| |
|
||||
V----------> ADB(USB)----------->
|
||||
|
||||
```
|
||||
|
||||
Android端所回應的HTTP封包也會經由原路回來,但是如果Android端要發一個request的話,PC端就收不到了,必須經由[[ADB#Reverse]]才行。
|
||||
|
||||
### 參考
|
||||
* [Android Debug Bridge (adb) | Android Developers](https://developer.android.com/studio/command-line/adb#forwardports)
|
||||
|
||||
## Reverse
|
||||
ADB reverse用來監聽Android端收到的request,假設今天在Android APP上寫一個`http://127.0.0.1:8080/say/message`,並希望PC端可以收到的話,就可以用reverse來操作,例:
|
||||
```
|
||||
adb reverse tcp:8081 tcp:8080
|
||||
```
|
||||
也就是說Android那邊發過來的封包會送交給PC這邊的port 8081。因此,PC端還需要寫一個Server來監聽8081才行。
|
||||
|
||||
### 參考
|
||||
* [adb命令-adb reverse的研究-有解無憂](https://www.uj5u.com/qita/277742.html)
|
||||
|
||||
## Logcat
|
||||
- Enlarge logcat buffer to 16M: `adb logcat -G 16M`
|
||||
|
||||
## Get foreground activity
|
||||
#android #foreground #activity
|
||||
```
|
||||
adb shell "dumpsys activity activities | grep ResumedActivity"
|
||||
```
|
||||
2
21.01. OS/21.04. Android/AOSP.md
Normal file
2
21.01. OS/21.04. Android/AOSP.md
Normal file
@@ -0,0 +1,2 @@
|
||||
## AOSP framework jar
|
||||
To use AOSP jar file in Android Studio: [Import AOSP framework jar file](https://medium.com/@chauyan/import-aosp-framework-jar-file-f0c2ac979a8a)
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
## 參考資料
|
||||
- [Android External Storage - Read, Write, Save File | DigitalOcean](https://www.digitalocean.com/community/tutorials/android-external-storage-read-write-save-file)
|
||||
- [Environment.getExternalStorageDirectory() is deprecated过时的替代方案_Mr_tigerchou的博客-CSDN博客](https://blog.csdn.net/shving/article/details/101057082)
|
||||
164
21.01. OS/21.04. Android/Android programming.md
Normal file
164
21.01. OS/21.04. Android/Android programming.md
Normal file
@@ -0,0 +1,164 @@
|
||||
## Build AOSP
|
||||
### Build compile environment
|
||||
1. Install Ubuntu 18.04
|
||||
2. Install packages: `sudo apt-get install git-core gnupg flex bison build-essential zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig`
|
||||
- https://source.android.com/setup/build/initializing
|
||||
3. Install Repo
|
||||
```
|
||||
mkdir ~/bin
|
||||
PATH=~/bin:$PATH
|
||||
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo ;\
|
||||
chmod a+x ~/bin/repo ;\
|
||||
gpg --recv-key 8BB9AD793E8E6153AF0F9A4416530D5E920F5C65 ;\
|
||||
curl https://storage.googleapis.com/git-repo-downloads/repo.asc | gpg --verify - ~/bin/repo
|
||||
```
|
||||
- https://source.android.com/setup/develop#installing-repo
|
||||
4. Download AOSP source
|
||||
1. Create folder for AOSP
|
||||
```
|
||||
mkdir -p ~/codes/aosp ; cd ~/codes/aosp
|
||||
```
|
||||
2. Setup git
|
||||
```
|
||||
git config --global user.name AwinHuang ;\
|
||||
git config --global user.email awinhuang@gmail.com
|
||||
```
|
||||
3. Download source code
|
||||
```
|
||||
repo init -u https://android.googlesource.com/platform/manifest ;\
|
||||
repo sync -j8
|
||||
```
|
||||
- 如果要切換某一個特定版本可以使用`-b`,例如:`repo init -u https://android.googlesource.com/platform/manifest -b android-10.0.0_r47`。
|
||||
- 要知道版本tag可以查看:https://source.android.com/setup/start/build-numbers#source-code-tags-and-builds
|
||||
5. Build code
|
||||
```
|
||||
source build/envsetup.sh ;\
|
||||
lunch aosp_arm-eng ;\
|
||||
make clobber ;\
|
||||
make -j16
|
||||
```
|
||||
- `make clobber`用來刪除build資料夾
|
||||
|
||||
### Reference
|
||||
- [GitHub - henrymorgen/android-knowledge-system: Android应用开发最强原创知识体系](https://github.com/henrymorgen/android-knowledge-system)
|
||||
- [Android AOSP基础(二)AOSP源码和内核源码下载 | BATcoder - 刘望舒](http://liuwangshu.cn/framework/aosp/2-download-aosp.html)
|
||||
- [Android AOSP基础(三)Android系统源码的整编和单编 | BATcoder - 刘望舒](http://liuwangshu.cn/framework/aosp/3-compiling-aosp.html)
|
||||
|
||||
## Build kernel
|
||||
1. Download the code
|
||||
```
|
||||
mkdir -p ~/codes/kernel ;\
|
||||
cd ~/codes/kernel ;\
|
||||
repo init -u https://android.googlesource.com/kernel/manifest ;\
|
||||
repo sync -j16
|
||||
```
|
||||
|
||||
2. Compile
|
||||
```
|
||||
build/build.sh
|
||||
```
|
||||
- 如果遇到`Command 'java' not found, but can be installed with:`
|
||||
- 依序安裝
|
||||
- `sudo apt install default-jre`
|
||||
- `sudo apt install openjdk-11-jre-headless`
|
||||
- `sudo apt install openjdk-8-jre-headless`
|
||||
- 執行 `sudo update-alternatives --config java`
|
||||
- 選擇 `/usr/lib/jvm/java-11-openjdk-amd64/bin/java`
|
||||
- 再次compile
|
||||
- `source build/envsetup.sh`
|
||||
- `mm idegen`
|
||||
|
||||
3. 產生android.iml和android.ipr
|
||||
在source code跟目錄下執行:`development/tools/idegen/idegen.sh`
|
||||
|
||||
### Reference
|
||||
- [Android kernel源码下载与编译](https://blog.csdn.net/u010164190/article/details/106561022)
|
||||
|
||||
|
||||
## Android App programming
|
||||
- R的全名:`<PACKAGE_NAME> + .R`,例如package name是`com.awin.testapp`,那全名是`com.awin.testapp.R`。
|
||||
- AndroidX = Android eXtension
|
||||
- Layout
|
||||
- layout_margin: 物件與其他物件的距離
|
||||
- layout_gravity: 物件在容器內的位置(靠左、靠右、置中...)
|
||||
- textApperance: 字型大小
|
||||
- Extensions
|
||||
- Android 4.1 沒有自動加入的extension
|
||||
- 打開build.gradle,在`plugins`區塊中加入:
|
||||
```
|
||||
id 'kotlin-kapt'
|
||||
id 'kotlin-android-extensions'
|
||||
```
|
||||
|
||||
- 使用ViewModel & LiveData
|
||||
- 確認有 `kotlin-kapt` 這個plugin。
|
||||
![[Pasted image 20210330102148.png]]
|
||||
- [Android jetpack所有library](https://developer.android.com/jetpack/androidx/explorer)
|
||||
- [Android jetpack - Lifecycle](https://developer.android.com/jetpack/androidx/releases/lifecycle)
|
||||
- 從`Declaring dependencies`這區塊複製必要的module
|
||||
![[Pasted image 20210330110411.png]]
|
||||
```
|
||||
def lifecycle_version = "2.3.1"
|
||||
|
||||
// ViewModel
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
// LiveData
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
// Annotation processor
|
||||
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
|
||||
```
|
||||
|
||||
- Create a ViewModel
|
||||
```kotlin
|
||||
viewModel = ViewModelProvider(this).get(GuessViewModel::class.java)
|
||||
```
|
||||
|
||||
- Observe a live data in ViewModel
|
||||
```kotlin
|
||||
viewModel.counter.observe(this, Observer {
|
||||
counter.setText(it.toString())
|
||||
})
|
||||
```
|
||||
counter這個變數是包含在ViewModel裡面的live data,我們的資料則是放在counter裡面的「value」。
|
||||
所以如果要取用我們的data,則是:`viewModel.counter.value`。
|
||||
|
||||
- 使用LiveData
|
||||
- `val counter = MutableLiveData<Int>()`
|
||||
|
||||
### Use ViewBinding
|
||||
ViewBinding is used to replace Kotlin Synthetics.
|
||||
1. 在`app/build.gradle`中加入:
|
||||
```
|
||||
plugins {
|
||||
...
|
||||
id 'kotlin-parcelize' <-- Add this
|
||||
}
|
||||
|
||||
android {
|
||||
...
|
||||
buildFeatures { <-- Add this
|
||||
viewBinding true
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
2. 在你的activity裡面
|
||||
1. `import <APP_DOMAIN_NAME>.databinding.<ACTIVITY_NAME>Binding`
|
||||
假如:APP_DOMAIN_NAME是`com.example.testmultisectioncyclerview`,ACTIVITY_NAME是`ActivityMain`,那就是:
|
||||
`import com.example.testmultisectioncyclerview.databinding.ActivityMainBinding`
|
||||
2. 用`lateinit`宣告一個變數,變數名稱是activity的名字加上binding,例如`ActivityMain`就是:
|
||||
`private lateinit var activityBinding: ActivityMainBinding`
|
||||
3. 在`onCreate()`中,就可以用`activityBinding`來取得view與其他元件了:
|
||||
```
|
||||
activityBinding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(activityBinding.root) <-- root就是view
|
||||
```
|
||||
|
||||
## ADB usage
|
||||
- [如何透過 adb command line 指令啟動 Android App](https://kkboxsqa.wordpress.com/2014/08/20/%E5%A6%82%E4%BD%95%E9%80%8F%E9%81%8E-adb-command-line-%E6%8C%87%E4%BB%A4%E5%95%9F%E5%8B%95-android-app/)
|
||||
|
||||
# MISC
|
||||
## 教學文
|
||||
- [Android Template 小技巧 及 寫程式常見的問題](https://www.eeaseries.com/2021/01/android-template.html?m=1)
|
||||
- [Jetpack Compose 基础知识](https://developers.google.com/codelabs/jetpack-compose-basics?hl=zh-cn#0)
|
||||
- [一文带你了解适配Android 11分区存储](https://zhuanlan.zhihu.com/p/354632087)
|
||||
58
21.01. OS/21.04. Android/AudioTrack.md
Normal file
58
21.01. OS/21.04. Android/AudioTrack.md
Normal file
@@ -0,0 +1,58 @@
|
||||
`AudioTrack` 和 `MediaPlayer` 都可以播放聲音,主要差別是 `AudioTrack` 沒有 decode 的能力,只能播放 PCM。`MediaPlayer` 除了可以 demux、decode 以外,也可以播放video。
|
||||
|
||||
## 底層原理
|
||||
每一個 audio stream 對應著一個 `AudioTrack` 類的一個實例,每個 `AudioTrack` 會在建立時會註冊到 `AudioFlinger` 中,由 `AudioFlinger` 把所有的 `AudioTrack` 進行混合(Mixer),然後輸送到 AudioHardware 中 進行播放,目前 Android 同時最多可以創建32個音頻流,也就是說,Mixer 最多會同時處理32個 `AudioTrack` 的資料。
|
||||
|
||||
## 建立 AudioTrack 物件
|
||||
```kotlin
|
||||
const val SAMPLE_RATE = 44100
|
||||
const val CHANNEL_OUT_FORMAT = AudioFormat.CHANNEL_OUT_STEREO
|
||||
const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT
|
||||
var audioBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_OUT_FORMAT, AUDIO_FORMAT)
|
||||
|
||||
audioTrack = AudioTrack(
|
||||
AudioManager.STREAM_MUSIC,
|
||||
SAMPLE_RATE,
|
||||
CHANNEL_OUT_FORMAT,
|
||||
AUDIO_FORMAT,
|
||||
audioBufferSize,
|
||||
AudioTrack.MODE_STREAM)
|
||||
audioTrack?.play()
|
||||
```
|
||||
|
||||
其中需要注意的是最後一個參數,`AudioTrack` 有兩種模式,分別是 `AudioTrack.MODE_STREAM` 與 `AudioTrack.MODE_STATIC`。
|
||||
|
||||
## AudioTrack.MODE_STREAM
|
||||
這個模式會邊讀邊播,必須不停的使用 `AudioTrack.write()` 來將資料寫入,若是來不及寫入會造成斷音,先呼叫 `AudioTrack.play()`,然後開始填資料,透過 `AudioTrack.write()`。
|
||||
|
||||
## AudioTrack.MODE_STATIC
|
||||
這個模式中,audioBufferSize 就是你要播放的聲音長度,一樣要透過 `AudioTrack.write()` 來寫入資料,寫完之後呼叫 `AudioTrack.play()` 開始播放。
|
||||
|
||||
## 狀態判斷
|
||||
### `getState()`
|
||||
用 `getState() : Int` 來取得目前的狀態。
|
||||
- `STATE_INITIALIZED` 表示 `AudioTrack` 已經是可以使用了。
|
||||
- `STATE_UNINITIALIZED` 表示 `AudioTrack` 創建時沒有成功地初始化。
|
||||
- `STATE_NO_STATIC_DATA` 表示當前是使用 `MODE_STATIC` ,但是還沒往緩衝區中寫入數據。當接收數據之後會變為 `STATE_INITIALIZED` 狀態。
|
||||
|
||||
### `getPlayState()`
|
||||
用 `getPlayState() : Int` 來取得目前的播放狀態。
|
||||
- `PLAYSTATE_STOPPED` 停止
|
||||
- `PLAYSTATE_PAUSED` 暫停
|
||||
- `PLAYSTATE_PLAYING` 正在播放
|
||||
|
||||
## 暫停
|
||||
`pause()` 可以暫停播放,但是暫存區不會被清空
|
||||
|
||||
## 停止
|
||||
如果是 `AudioTrack.MODE_STREAM` mode,需要先呼叫 `pause()` 再呼叫 `flush()` 才能馬上停止,否則會等暫存區清空才停止。
|
||||
`AudioTrack.MODE_STATIC` mode 直接使用 `stop()` 即可。
|
||||
|
||||
## 釋放
|
||||
使用 `release()` 來結束資源。
|
||||
|
||||
## 參考
|
||||
- [AudioTrack](https://developer.android.com/reference/android/media/AudioTrack)
|
||||
- [音视频开发之旅(三)AudioTrack播放PCM音频](https://zhuanlan.zhihu.com/p/265804902)
|
||||
- [深入剖析Android音頻之AudioTrack](https://blog.csdn.net/yangwen123/article/details/39989751)
|
||||
- [Android音频开发之AudioTrack](https://www.jianshu.com/p/c67fd0c2b379)
|
||||
82
21.01. OS/21.04. Android/Ktor.md
Normal file
82
21.01. OS/21.04. Android/Ktor.md
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
tags:
|
||||
aliases:
|
||||
date: 2024-06-05
|
||||
time: 19:06:00
|
||||
description:
|
||||
---
|
||||
|
||||
Ktor是由Kotlin提供的一個framwork。
|
||||
要在Android使用Ktor,需要在build.gradle加入以下的dependency:
|
||||
```
|
||||
implementation "io.ktor:ktor-server-core:2.0.1" // Ktor的核心包
|
||||
implementation "io.ktor:ktor-server-jetty:2.0.1" // 供Ktor使用的引擎包,另外有Jetty, Tomcat, CIO可用
|
||||
implementation "io.ktor:ktor-gson:1.2.5"
|
||||
// implementation "io.ktor:ktor-server-call-logging:2.0.1" // 用於印出Request及Response的log用
|
||||
// implementation "io.ktor:ktor-server-partial-content:2.0.1" // 用於支援PartialContent用
|
||||
// implementation "io.ktor:ktor-server-cors:2.0.1" // 用於支援CORS用
|
||||
// implementation "io.ktor:ktor-server-html-builder:2.0.1" // 用於回傳客製html用
|
||||
```
|
||||
|
||||
在`packagingOptions`裡,也需要加入以下的設定來必面編譯問題[[Ktor#^68d958 ]] :
|
||||
```
|
||||
packagingOptions {
|
||||
exclude 'META-INF/*'
|
||||
}
|
||||
```
|
||||
|
||||
在`AndroidManifest.xml`中,記得加入internet的權限:
|
||||
```
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
```
|
||||
|
||||
然後就是 HTTP Server 的 code 了,注意 Netty 在 Android 上不能用,要改用 Jetty 或是 CIO:
|
||||
```kotlin
|
||||
import io.ktor.server.jetty.Jetty
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.routing.routing
|
||||
import io.ktor.server.routing.get
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.response.respondText
|
||||
|
||||
embeddedServer(Jetty, 9000) {
|
||||
routing {
|
||||
get("/") {
|
||||
call.respondText("Hello, world!")
|
||||
}
|
||||
}
|
||||
}.start(wait = false)
|
||||
```
|
||||
|
||||
但是這段code會block,所以需要一個thread把它包起來:
|
||||
```kotlin
|
||||
Thread {
|
||||
httpEngine = embeddedServer(Netty, 8080) {
|
||||
install(ContentNegotiation) {
|
||||
gson {}
|
||||
}
|
||||
routing {
|
||||
get("/") {
|
||||
call.respond(mapOf("message" to "Hello world"))
|
||||
}
|
||||
|
||||
get("/say/{something}") {
|
||||
call.respond(mapOf("message" to "You say: " + call.parameters["something"]))
|
||||
|
||||
activity?.findViewById<TextView>(R.id.textView)?.text = "You say: " + call.parameters["something"]
|
||||
}
|
||||
}
|
||||
}.start(wait = false)
|
||||
}.start()
|
||||
```
|
||||
|
||||
|
||||
# 參考來源
|
||||
|
||||
- 如果沒有這一段會產生如下錯誤 ^68d958
|
||||
```
|
||||
13 files found with path 'META-INF/INDEX.LIST'.
|
||||
Adding a packagingOptions block may help, please refer to
|
||||
https://developer.android.com/reference/tools/gradle-api/7.4/com/android/build/api/dsl/ResourcesPackagingOptions
|
||||
for more information
|
||||
```
|
||||
108
21.01. OS/21.04. Android/MediaCodec.md
Normal file
108
21.01. OS/21.04. Android/MediaCodec.md
Normal file
@@ -0,0 +1,108 @@
|
||||
## 一般流程
|
||||
1. 使用者從MediaCodec請求一個空的輸入buffer(ByteBuffer),填充滿數據後將它傳遞給MediaCodec處理。
|
||||
2. MediaCodec處理完這些數據並將處理結果輸出至一個空的輸出buffer(ByteBuffer)中。
|
||||
3. 使用者從MediaCodec獲取輸出buffer的數據,消耗掉裡面的數據,使用完輸出buffer的數據之後,將其釋放回編解碼器。
|
||||
|
||||
流程如下圖所示:
|
||||
![[android_mediacodec_flow.png]]
|
||||
|
||||
## 生命週期
|
||||
MediaCodec的生命週期有三種狀態:Stopped、Executing、Released。
|
||||
- Stopped,包含三種子狀態:Uninitialized、Configured、Error。
|
||||
- Executing,包含三種子狀態:Flushed、Running、End-of-Stream。
|
||||
![[android_mediacodec_life_cycle.png]]
|
||||
|
||||
**Stopped** 的三種子狀態:
|
||||
1. Uninitialized:當創建了一個MediaCodec對象,此時處於Uninitialized狀態。可以在任何狀態調用reset()方法使MediaCodec返回到Uninitialized狀態。
|
||||
2. Configured:使用configure(…)方法對MediaCodec進行配置轉為Configured狀態。
|
||||
3. Error:MediaCodec遇到錯誤時進入Error狀態。錯誤可能是在隊列操作時返回的錯誤或者異常導致的。
|
||||
|
||||
**Executing** 的三種子狀態:
|
||||
1. Flushed:在調用start()方法後MediaCodec立即進入Flushed子狀態,此時MediaCodec會擁有所有的緩存。可以在Executing狀態的任何時候通過調用flush()方法返回到Flushed子狀態。
|
||||
2. Running:一旦第一個輸入緩存(input buffer)被移出隊列,MediaCodec就轉入Running子狀態,這種狀態佔據了MediaCodec的大部分生命週期。通過調用stop()方法轉移到Uninitialized狀態。
|
||||
3. End-of-Stream:將一個帶有end-of-stream標記的輸入buffer入隊列時,MediaCodec將轉入End-of-Stream子狀態。在這種狀態下,MediaCodec不再接收之後的輸入buffer,但它仍然產生輸出buffer直到end-of-stream標記輸出。
|
||||
|
||||
**Released**
|
||||
1. 當使用完MediaCodec後,必須調用release()方法釋放其資源。調用release()方法進入最終的Released狀態。
|
||||
|
||||
## API
|
||||
### createEncoderByType
|
||||
- [createEncoderByType](https://developer.android.com/reference/android/media/MediaCodec#createEncoderByType(java.lang.String))
|
||||
|
||||
### createDecoderByType
|
||||
- [createDecoderByType](https://developer.android.com/reference/android/media/MediaCodec#createDecoderByType(java.lang.String))
|
||||
|
||||
### configure
|
||||
- [configure]([MediaCodec | Android Developers](https://developer.android.com/reference/android/media/MediaCodec#configure(android.media.MediaFormat,%20android.view.Surface,%20android.media.MediaCrypto,%20int)))
|
||||
|
||||
### start
|
||||
- [start](https://developer.android.com/reference/android/media/MediaCodec#start())
|
||||
|
||||
### dequeueInputBuffer
|
||||
- [dequeueInputBuffer](https://developer.android.com/reference/android/media/MediaCodec#dequeueInputBuffer(long))
|
||||
|
||||
### queueInputBuffer
|
||||
- [queueInputBuffer](https://developer.android.com/reference/android/media/MediaCodec#queueInputBuffer(int,%20int,%20int,%20long,%20int))
|
||||
|
||||
### getInputBuffer
|
||||
- [getInputBuffer](https://developer.android.com/reference/android/media/MediaCodec#getInputBuffer(int))
|
||||
|
||||
### dequeueOutputBuffer
|
||||
- [dequeueOutputBuffer](https://developer.android.com/reference/android/media/MediaCodec#dequeueOutputBuffer(android.media.MediaCodec.BufferInfo,%20long))
|
||||
|
||||
### getOutputBuffer
|
||||
- [getOutputBuffer](https://developer.android.com/reference/android/media/MediaCodec#getOutputBuffer(int))
|
||||
|
||||
### releaseOutputBuffer
|
||||
- [releaseOutputBuffer](https://developer.android.com/reference/android/media/MediaCodec#releaseOutputBuffer(int,%20boolean))
|
||||
|
||||
### stop
|
||||
- [stop](https://developer.android.com/reference/android/media/MediaCodec#stop())
|
||||
|
||||
### release
|
||||
- [release](https://developer.android.com/reference/android/media/MediaCodec#release())
|
||||
|
||||
## 使用
|
||||
1. 根據需求使用 [[#createEncoderByType]]或是 [[#createDecoderByType]] 建立codec。以下以 encode 為例。
|
||||
2. 呼叫 [[#configure]],傳入相應的 MediaFormat。
|
||||
3. 呼叫 [[#start]],開始 encode。
|
||||
4. 建立一個迴圈,不斷的傳入要 encode 的 buffer,也不斷的拿出已經 encode 的 buffer。
|
||||
5. 在迴圈內,要傳入的 buffer 處理方法:
|
||||
1. 呼叫 [[#dequeueInputBuffer]],試探是否有能用的 buffer,如果有,回傳值將大於等於0(>= 0)。這裡假設回傳值的變數叫做 index。
|
||||
2. 如果 index 合法,用 [[#queueInputBuffer]],像是 `inputBuffer = queueInputBuffer(index)` 來取得可用的 buffer。這裡假設 buffer 的變數叫做 inputBuffer。
|
||||
3. 將要 encode 的資料 copy 到 inputBuffer。
|
||||
4. 若要停止 encode,送出 `codec.queueInputBuffer(inputBufIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM)`。
|
||||
6. 在迴圈內,要取得已經encode buffer的方法:
|
||||
1. 呼叫 [[#dequeueOutputBuffer]],試探是否有 encoded buffer,如果有,回傳值將大於等於0(>= 0)。這裡假設回傳值的變數叫做 index。
|
||||
2. 如果 index合法,用 [[#getOutputBuffer]],像是`outputBuffer = getOutputBuffer(index)` 來取得可用的 buffer。這裡假設 buffer 的變數叫做 outputBuffer。
|
||||
3. outputBuffer 就是已經 encode 好的,就看你怎麼處理。
|
||||
4. 重要!呼叫 [[#releaseOutputBuffer]] 來回收剛剛那一塊 buffer。
|
||||
|
||||
Psuedo code 如下:
|
||||
```kotlin
|
||||
while (true)
|
||||
// send buffer to encode
|
||||
index = dequeueInputBuffer()
|
||||
if (index >= 0)
|
||||
if (!end)
|
||||
inputBuffer = queueInputBuffer(index)
|
||||
copy(inputBuffer, srcBuffer)
|
||||
else
|
||||
queueInputBuffer(inputBufIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM)
|
||||
// Get encoded buffer
|
||||
index = dequeueOutputBuffer()
|
||||
if (index >= 0)
|
||||
outputBuffer = getOutputBuffer(index)
|
||||
releaseOutputBuffer(index)
|
||||
```
|
||||
|
||||
## 參考資料
|
||||
- [Android音视频之使用MediaCodec编解码AAC - 简书](https://www.jianshu.com/p/14daab91b951)
|
||||
- [MultiMediaLearning/app/src/main/java/com/richie/multimedialearning/mediacodec at master · isuperqiang/MultiMediaLearning](https://github.com/isuperqiang/MultiMediaLearning/tree/master/app/src/main/java/com/richie/multimedialearning/mediacodec)
|
||||
- [初识MediaCodec - 知乎](https://zhuanlan.zhihu.com/p/45224834)
|
||||
- [MediaCodec的使用介绍 - 简书](https://www.jianshu.com/p/f5a1c9318524)
|
||||
- [Android原生编解码接口MediaCodec详解 - 掘金](https://juejin.cn/post/7086297619764346887)
|
||||
- [AndroidMediaCodecDemo/AudioDecoder.kt at main · king-ma1993/AndroidMediaCodecDemo](https://github.com/king-ma1993/AndroidMediaCodecDemo/blob/main/app/src/main/java/com/myl/mediacodedemo/decode/audio/AudioDecoder.kt)
|
||||
- [Android使用系统API进行音视频编码_key_max_input_size_blueberry_mu的博客-CSDN博客](https://blog.csdn.net/a992036795/article/details/54286654)
|
||||
- [MediaCodec 完成PCM编码成AAC - 知乎](https://zhuanlan.zhihu.com/p/564759685)
|
||||
- [MediaCodec 同步方式完成AAC硬解成PCM - 知乎](https://zhuanlan.zhihu.com/p/564734700)
|
||||
205
21.01. OS/21.04. Android/Service.md
Normal file
205
21.01. OS/21.04. Android/Service.md
Normal file
@@ -0,0 +1,205 @@
|
||||
## Service的生命週期
|
||||
![[Pasted image 20220307103552.png]]
|
||||
|
||||
## Service的啟動方式
|
||||
Service由`startService()`啟動之後,便獨立動作,啟動者(例如某個activity)無法取得Service的intance,也無法呼叫Service的API。Service可以被多次呼叫`startService()`,但是只要一旦`stopService()`被呼叫了,Service就會結束。所以需要確保Service的管理者是誰,統一管理者來呼叫`startService()`與`stopService()`才不會造成混亂。
|
||||
|
||||
`bindService()`則是像是典型的Server-Client架構,第一次`bindService()`的時候會建立Service的instance,然後可以多次`bindService()`與`unbindService()`,當Service沒也任何人跟它"Bind"的時候,Service才會結束。
|
||||
|
||||
### startService
|
||||
要建立自己的Service,需要繼承`Service()`類別,然後複寫4個成員函式:
|
||||
```kotlin
|
||||
class MyService : Service() {
|
||||
|
||||
override fun onCreate() {
|
||||
Log.i("Awin","onCreate - Thread ID = " + Thread.currentThread().id)
|
||||
super.onCreate()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Log.i("Awin", "onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().id)
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
override fun onBind(p0: Intent?): IBinder? {
|
||||
Log.i("Awin", "onBind - Thread ID = " + Thread.currentThread().id)
|
||||
return null // 因為目前沒有要支援bindService(),所以這裡直接return null
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.i("Awin", "onDestroy - Thread ID = " + Thread.currentThread().id)
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
假設現在由MainActivity呼叫`startService()`,因為是第一次呼叫,所以MyService的callback被呼叫的順序是:`onCreate()` -> `onStartCommand()`。
|
||||
第二次之後的呼叫就只會執行`onStartCommand()`。
|
||||
當MainActivity呼叫`stopService()`時,則會執行MyService的`onDestroy()`。
|
||||
|
||||
所以,必須將「長期任務」開個 Thread 並且執行在 `onStartCommand()` 方法內。
|
||||
|
||||
Client端的code:
|
||||
```kotlin
|
||||
val intent = Intent(this, ServiceDemo.class)
|
||||
startService(intent)
|
||||
```
|
||||
|
||||
### bindService
|
||||
使用`bindService()`來建立Service的一個好處就是可以取得Service的instance,然後藉由這個instance來使用Service的API。
|
||||
要使用`bindService()`的話,Service類別必須實做`onBind()`與`onUnbind()`至兩個override function。
|
||||
也必須提供一個Binder class(繼承自`Binder()`),來讓client呼叫。例:
|
||||
```kotlin
|
||||
class MyService : Service() {
|
||||
|
||||
//client 可以通过Binder获取Service实例
|
||||
inner class MyBinder : Binder() {
|
||||
val service: MyService
|
||||
get() = this@MyService
|
||||
}
|
||||
|
||||
//通过binder实现调用者client与Service之间的通信
|
||||
private val binder = MyBinder()
|
||||
|
||||
private val generator: Random = Random()
|
||||
|
||||
override fun onCreate() {
|
||||
Log.i("xiao", "MyService - onCreate - Thread = " + Thread.currentThread().name)
|
||||
super.onCreate()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param intent 啟動時,啟動組件傳遞過來的Intent,如Activity可利用Intent封裝所需要的參數並傳遞給Service
|
||||
* @param flags 表示啟動請求時是否有額外數據,可選值有 0,START_FLAG_REDELIVERY,START_FLAG_RETRY
|
||||
* 0: 在正常創建Service的情況下,onStartCommand傳入的flags為0。
|
||||
*
|
||||
* START_FLAG_REDELIVERY:
|
||||
* 這個值代表了onStartCommand()方法的返回值為 START_REDELIVER_INTENT,
|
||||
* 而且在上一次服務被殺死前會去調用stopSelf()方法停止服務。
|
||||
* 其中START_REDELIVER_INTENT意味著當Service因記憶體不足而被系統kill後,
|
||||
* 則會重建服務,並透過傳遞給服務的最後一個 Intent調用 onStartCommand(),此時Intent時有值的。
|
||||
*
|
||||
* START_FLAG_RETRY
|
||||
* 該flag代表當onStartCommand()調用後一直沒有返回值時,會嘗試重新去調用onStartCommand()。
|
||||
*
|
||||
* @param startId 指明當前服務的唯一ID,與stopSelfResult(int startId)配合使用,stopSelfResult()可以更安全地根據ID停止服務。
|
||||
*
|
||||
* @return
|
||||
* START_STICKY:
|
||||
* 當Service因記憶體不足而被系統kill後,一段時間後記憶體再次空閒時,
|
||||
* 系統將會嘗試重新創建此Service,一旦創建成功後將回調onStartCommand方法,
|
||||
* 但其中的Intent將是null,除非有掛起的Intent,如pendingintent,
|
||||
* 這個狀態下比較適用於不執行命令、但無限期運行並等待作業的媒體播放器或類似服務
|
||||
*
|
||||
*
|
||||
* START_NOT_STICKY:
|
||||
* 當Service因記憶體不足而被系統kill後,即使系統記憶體再次空閒時,
|
||||
* 系統也不會嘗試重新創建此Service。除非程序中再次調用startService啟動此Service,
|
||||
* 這是最安全的選項,可以避免在不必要時以及應用能夠輕鬆重啟所有未完成的作業時運行服務。
|
||||
*
|
||||
* START_REDELIVER_INTENT:
|
||||
* 當Service因記憶體不足而被系統kill後,則會重建服務,
|
||||
* 並透過傳遞給服務的最後一個 Intent 調用 onStartCommand(),任何掛起 Intent均依次傳遞。
|
||||
* 與START_STICKY不同的是,其中的傳遞的Intent將是非空,是最後一次調用startService中的intent。
|
||||
* 這個值適用於主動執行應該立即恢復的作業(例如下載文件)的服務。
|
||||
*/
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Log.i("xiao", "MyService - onStartCommand - startId = $startId, Thread = " + Thread.currentThread().name)
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder{
|
||||
Log.i("xiao", "MyService - onBind - Thread = " + Thread.currentThread().name)
|
||||
return binder
|
||||
}
|
||||
|
||||
override fun onUnbind(intent: Intent): Boolean {
|
||||
Log.i("xiao", "MyService - onUnbind - from = " + intent.getStringExtra("from"))
|
||||
return super.onUnbind(intent)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.i("xiao", "MyService - onDestroy - Thread = " + Thread.currentThread().name)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
//getRandomNumber是Service暴露出去供client调用的公共方法
|
||||
fun getRandomNumber(): Int {
|
||||
return generator.nextInt()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Client端要做的事:
|
||||
1. 建立ServiceConnection類型實例,並覆載`onServiceConnected()`方法和`onServiceDisconnected()`方法。
|
||||
2. 當執行到onServiceConnected callback時,可通過IBinder instance得到Service的instance,這樣可實現client與Service的連接。
|
||||
3. onServiceDisconnected callback被執行時,表示client與Service已經斷開,在此可以寫一些斷開後需要做的處理。
|
||||
```kotlin
|
||||
class AActivity : AppCompatActivity() {
|
||||
|
||||
private var service: MyService? = null
|
||||
private var isBind = false
|
||||
|
||||
private var conn = object : ServiceConnection{
|
||||
override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
|
||||
isBind = true
|
||||
val myBinder = p1 as MyService.MyBinder
|
||||
service = myBinder.service
|
||||
Log.i("xiao", "ActivityA - onServiceConnected")
|
||||
val num = service!!.getRandomNumber()
|
||||
Log.i("xiao", "ActivityA - getRandomNumber = $num");
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(p0: ComponentName?) {
|
||||
isBind = false
|
||||
Log.i("xiao", "ActivityA - onServiceDisconnected")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_a)
|
||||
|
||||
Log.i("xiao", "ActivityA - onCreate - Thread = " + Thread.currentThread().name)
|
||||
|
||||
btn_bind_service_a.setOnClickListener {
|
||||
val intent = Intent(this,MyService::class.java)
|
||||
intent.putExtra("from","ActivityA")
|
||||
Log.i("xiao", "----------------------------------------")
|
||||
Log.i("xiao", "ActivityA 执行 bindService");
|
||||
bindService(intent, conn, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
btn_unbind_service_a.setOnClickListener {
|
||||
if(isBind){
|
||||
Log.i("xiao", "----------------------------------------")
|
||||
Log.i("xiao", "ActivityA 执行 unbindService");
|
||||
unbindService(conn)
|
||||
}
|
||||
}
|
||||
btn_a_start_b.setOnClickListener {
|
||||
val intent = Intent(this,BActivity::class.java)
|
||||
Log.i("xiao", "----------------------------------------")
|
||||
Log.i("xiao", "ActivityA 启动 ActivityB");
|
||||
startActivity(intent)
|
||||
}
|
||||
btn_finish_a.setOnClickListener {
|
||||
Log.i("xiao", "----------------------------------------")
|
||||
Log.i("xiao", "ActivityA 执行 finish");
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Log.i("xiao", "ActivityA - onDestroy")
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 參考
|
||||
1. [如何使用Service(kotlin)](https://givemepass.blogspot.com/2015/10/service.html)
|
||||
2. [Android kotlin service使用简析 - 簡書](https://www.jianshu.com/p/f9712b470b42)
|
||||
3. [《Android》『Service』- 背景執行服務的基本用法 - 賽肥膩膩の娛樂生活誌](https://xnfood.com.tw/android-service/#skill_02)
|
||||
17
21.01. OS/21.04. Android/Tools.md
Normal file
17
21.01. OS/21.04. Android/Tools.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# screenrecord
|
||||
## 螢幕錄影
|
||||
用`screenrecord `可以把目前的螢幕錄製下來,一個簡單的例子:
|
||||
```
|
||||
adb shell screenrecord --size 1200x1920 /storage/sdcard0/demo.mp4
|
||||
```
|
||||
|
||||
### 指定錄製時間
|
||||
```
|
||||
adb shell screenrecord --time-limit 10 /storage/sdcard0/demo.mp4
|
||||
```
|
||||
|
||||
### 指定bit rate
|
||||
```
|
||||
adb shell screenrecord --bit-rate 6000000 /storage/sdcard0/demo.mp4
|
||||
```
|
||||
沒有指定時間的話,就必須手動`Ctrl+c`來中止錄影。
|
||||
8
21.01. OS/21.04. Android/UI.md
Normal file
8
21.01. OS/21.04. Android/UI.md
Normal file
@@ -0,0 +1,8 @@
|
||||
## 在thread更新UI
|
||||
Android framework只能在main thread裡更新UI,若需要在其他的thread更新UI的話,需要呼叫activity的`runOnUiThread()`。
|
||||
例:
|
||||
```kotlin
|
||||
activity?.runOnUiThread {
|
||||
activity?.findViewById<TextView>(R.id.textView)?.text = "You say: " + call.parameters["something"]
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user