Android Dev Summit 2019 笔记
RT
USB
- MAC:
system-Profiler SPUSBDataType
- Linux:
lsusb -vvv
- Windows:
USBView.exe
Gradle
settings.gradle
: project(":core").projectDir = new File(rootDir, "libraries/core")
1 | def androidx = [:] |
ADB
1 | adb emu fold |
Drawing Behind System Bar
Change system bar colors
values-v29/themes.xml:
1
<item name="android:navigationBarColor">@android:color/transparent</item>
values/themes.xml:
#B3FFFFFF
(70% white)values-night/themes.xml:
#B3000000
(70% black)Request to be laid out fullscreen
1
2
3
4
5
6
7view.systemUiVisibility =
// We wish to be laid out as if the navigation bar was hidden
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
// We wish to be laid out fullscreen, behind the status bar
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
// We wish to be laid out at the most extreme scenario of any other flags
View.SYSTEM_UI_FLAG_LAYOUT_STABLEAvoid overlays with system UI
Always use the ViewCompat method:
1
2
3
4
5
6
7
8
9
10
11ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
// Do something with the insets
v.updatePadding(
bottom = insets.systemWindowInsets.bottom
)
v.updateLayoutParams<MarginLayoutParams>(
bottomMargin = insets.systemWindowInsets.bottom
)
// return the insets
insets
}1
2
3WindowInsetsCompat.getSystemWindowInsets(): Insets
WindowInsetsCompat.getSystemGestureInsets(): Insets
WindowInsetsCompat.getMandatorySystemGestureInsets(): InsetsonLayout/onDraw:
View.setSystemGestureExclusionRects(List<Rect> rects)
Scopes
Fragment, Fragment ViewModel, Activity, Activity ViewModel, Application & its ViewModel
One way to handle back
1 | val dispatcher by lazy { requireActivity().onBackPressedDispatcher } |
Intent Handling
- A lot of intent handlers couble be disabled.
- Check if an intent handler exists, don’t assume it will just work.
1 | if (intent.resolveActivity(packageManager) == null) { |
Scoped Storage
Read only video/image/audio files
MediaStore + READ_EXTERNAL_STORAGE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41data class Video(val uri: Uri, val name: String, val duration: Int, val size: Int)
val videoList = mutableListOf<Video>()
val projection = arrayOf(
MediaStore.Video.Media._ID,
MediaStore.Video.Media.DISPLAY_NAME,
MediaStore.Video.Media.DURATION,
MediaStore.Video.Media.SIZE
)
val selection = "Video.Media.DURATION) >= ?"
val selectionArgs = arrayOf(
TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES).toString()
)
val query = contentResolver.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
projection,
selection,
selectionArgs,
"${MediaStore.Video.Media.DISPLAY_NAME} ASC"
)
query?.use { cursor ->
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)
val durationColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DDRATION)
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE)
while(cursor.moveToNext()) {
// we 11 use the column indexes that we found above
val id = cursor.getLong(idColumn)
val name = cursor.getString(nameColumn)
val duration = cursor.getInt(durationColumn)
val size = cursor.getInt(sizeColumn)
val contentUri = ContentUris.withAppendedId(
MediaStore Video Media EXTERNAL_CONTENT_URI. id
)
videolist += Video(contentUri, name, duration, size)
}Read non-media files
SAF(No Permission): ACTION_OPEN_DOCUMENT, ACTION_OPEN_DOCUMENT_TREE + takePersistableUriPermission
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22fun intentPickDocument() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/pdf"
}
startActivityForResult(intent, PICK_PDF_FILE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
super.onActivityResult(requestCode, resultCode, resultData)
if (requestCode == PICK_PDF_FILE && resultCode == Activity.RESULT_OK) {
resultData?.data?.let { uri ->
renderPdf(uri)
contentResolver.query(uri, null, null, null, null, null)?.use {
val name = it.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
val size = it.getString(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE)) // May throw
}
}
}
}Add & Edit image/video/audio files
MediaStore + WRITE_EXTERNAL_STORAGE(READ?) + ACCESS_MEDIA_LOCATION to access EXIF data
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20val selectedImageUri = Uri.parser("content://media/external/images/media/44")
fun editImage() {
try {
contentResolver.openFileDescriptor(selectedtmageUri, "w")?.use {
setGrayscaleFilter(it)
}
} catch (securityException: SecurityException) {
if (Build.VERSIN.SDK_INT >= Build.VERSION_CDDES.Q) {
val recoverableSecurityException = securityException as? RecoverableSecurityException ?: throw securityException
val intentSender = recoverableSecurityException.userAction.actionIntent.intentSender
intenSender?.let {
startIntentSenderForResult(intentSender, EDIT_IMAGE_REQUEST, null, 0, 0, 0, null)
}
} else {
throw securityException
}
}
}File management or Backup features need to approved in Play Console.
ANR
- Background Services => MediaBrowserServer(API 26)
- WorkManager
- Reduced use of SharedPreferences
- Reorganized the app graph to perform less object instantiation in application’s onCreate()
Theme
AppTheme
=> Theme.MaterialComponents.Light
=> Theme.AppCompat.Light
=> Theme.Material.Light
1 |
|
1 | val themedContext = ContextThemeWrapper(context, R.style.ThemeOverlay_Owl_Blue) |
Use literal names, relavant to the value(brand_blue > color_primary)
Name
Theme.AppTheme.Blue
Widget.AppTheme.Toolbar.Green
StyleType.GroupName.SubGroupName.VariantName
<View style="@style/Theme.AppTheme.Foo" />
Files
- themes.xml: Themes and ThemeOverlays
- types.xml: TextApperances, Text size dimens
- styles.xml: Widget styles
- dimens.xml, colors.xml, strings.xml
?textApperanceHeandline1
>@style/TextApperance.MaterialComponts.Headline1
Permissions
- Mimimum permissions
- Request in context
- Libraries
- Mimimize localtion and background location