SufaceView详解
WindowManager详解
简介
Window是一个抽先类,代表一个窗口,具体实现类是PhoneWindow,Android中所有的View的展示都是依赖于Window的,Window是View的管理者,事件分发也是由window传递给DecorView,在由DecorView分发给子View。可以通过WindowManager来获取一个Window,WindowManager是访问Window的入口。
添加Window
|
|
Window Type
Type包含三种类型,application_window、sub_window、system_window,每种类型都包含自己的子类型,每个类型都有自己的层级范围,层级高的会覆盖在层级低的上面。
- application_window:应用级别窗口,对应层级1-99,对应着一个Activity,在离开页面的时候需要销毁
- sub_window:对应层级1000-1999,子窗口是指该窗口必须要有一个父窗口,这个窗口可以是应用级别的PopupWindow、Dialog就是一个子窗口,Dialog不能用application context的原因就是这。
- system_window:对应层级2000-2999,系统级别窗口不需要对应的Activity有,也不需要父窗口,展示范围是整个手机,需要申请权限
|
|
Window Flag
- FLAG_NOT_FOCUSABLE:表示window不需要获取焦点,也不需要接收各种输入事件。此标记会同时启用FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的window;
- FLAG_NOT_TOUCH_MODAL:在此模式下,系统会将window区域外的单击事件传递给底层的window,当前window区域内的单击事件则自己处理,一般都需要开启这个标记;
- FLAG_SHOW_WHEN_LOCKED:开启此模式可以让Window显示在锁屏的界面上。
Window的使用
WindowManager是Window的唯一操作入口,它实现了ViewManager,只有三个方法addView()
,updateViewLayout()
,removeView()
,我们可以通过这三个方法来对Window进行操作
但WindowManager也是一个interface,具体实现类是WindowManagerImpl
,通过源码可以看到,WindowManager的add、remove、update操作都交由WindowManagerGlobal
来做,它是一个单例,所以我们就知道了所有Window的管理其实都是有WindowManagerGlobal
来做的,具体如下图。
,
常用adb 命令
adb shell dumpsys activity [package-name] 可以查看手机里所有四大组件的信息 或者查看指定包名的
adb shell dumpsys meminfo [package-name] 可以查看手机的内存信息,或指定包名的信息
Android依赖管理
在接入第三方SDK的时候经常会遇到一些依赖的冲突等问题,需要解决,主要有以下的几种方式:
gradle dependencies
通过gradle dependencies
可以找出整个应用的依赖,按照层级展示
- 可以看出每一个依赖包都是由:group-moudle-version组成的
- 后面带
(*)
的代表该Library或者aar包下存在相同的依赖 ->
代表最终使用的版本,可以看到如果存在相同的依赖包时,会优先使用高版本的依赖包,这可能就存在冲突
transitive
Transitive用于自动处理子依赖项。默认为true,gradle自动添加子依赖.设置为false,则需要手动添加每个依赖项。例如:
对com.meituan.mapsdk2d
来说,当transitive为true时会自动添加其下的依赖,当transitive为false时,如下图:
你需要手动添加它所需要的依赖。
通过下面的可以统一指定transitive
如果你所依赖的包比较少,希望自己管理每个包时,适合这种方式,但是当引入的sdk很多时,这种方式就有些力不从心了。
版本覆盖
当存在相同的依赖时,会用a->b
的这种形式告诉你a版本的被b版本的覆盖了,通过上图可以看到高版本的会覆盖低版本的依赖。这时就可能会存在问题,如果高版本的不兼容低版本的,引入低版本依赖的sdk就会报错,存在冲突。
force
force可以强制指定某个版本的依赖
我们可以看到support包被强制指定成23.4.0版本了
exclude
exclude规则可以帮我们排除指定的依赖。例如所有依赖里匹配的或者指定排除某个依赖里的,有group和moudle参数,可以分别使用。
例如:
可以看到v7包都没有了。
但我们也可以单独给某个模块下添加exclude
可以看到mapsdk下的v7包没了,下面的还是存在的
不同compile下的冲突
相同compile下的依赖会自动改成依赖高版本,不同compile则会产生冲突
扩展阅读
https://docs.gradle.org/current/userguide/dependency_management.html
Android内存分析
内存指标概念
故内存的大小关系:VSS >= RSS >= PSS >= USS
内存分析命令
adb shell dumpsys meminfo
命令输出包括4部分:
可以 adb shell dumpsys meminfo --package [packagename]
查看某一进程下的:
使用Debug的getMemoryInfo(Debug.MemoryInfo memoryInfo)或ActivityManager的MemoryInfo[] getProcessMemoryInfo(int[] pids)
该方式得到的MemoryInfo所描述的内存使用情况比较详细.数据的单位是KB.MemoryInfo的Field如下:
dalvikPrivateDirty: The private dirty pages used by dalvik。
dalvikPss :The proportional set size for dalvik.
dalvikSharedDirty :The shared dirty pages used by dalvik.
nativePrivateDirty :The private dirty pages used by the native heap.
nativePss :The proportional set size for the native heap.
nativeSharedDirty :The shared dirty pages used by the native heap.
otherPrivateDirty :The private dirty pages used by everything else.
otherPss :The proportional set size for everything else.
otherSharedDirty :The shared dirty pages used by everything else.
Android和Linux一样有大量内存在进程之间进程共享。某个进程准确的使用好多内存实际上是很难统计的。
因为有paging out to disk(换页),所以如果你把所有映射到进程的内存相加,它可能大于你的内存的实际物理大小。
dalvik:是指dalvik所使用的内存。
native:是被native堆使用的内存。应该指使用C\C++在堆上分配的内存。
other:是指除dalvik和native使用的内存。但是具体是指什么呢?至少包括在C\C++分配的非堆内存,比如分配在栈上的内存。puzlle!
private:是指私有的。非共享的。
share:是指共享的内存。
PSS:实际使用的物理内存(比例分配共享库占用的内存)
Pss:它是把共享内存根据一定比例分摊到共享它的各个进程来计算所得到进程使用内存。网上又说是比例分配共享库占用的内存,那么至于这里的共享是否只是库的共享,还是不清楚。
PrivateDirty:它是指非共享的,又不能换页出去(can not be paged to disk )的内存的大小。比如Linux为了提高分配内存速度而缓冲的小对象,即使你的进程结束,该内存也不会释放掉,它只是又重新回到缓冲中而已。
SharedDirty:参照PrivateDirty我认为它应该是指共享的,又不能换页出去(can not be paged to disk )的内存的大小。比如Linux为了提高分配内存速度而缓冲的小对象,即使所有共享它的进程结束,该内存也不会释放掉,它只是又重新回到缓冲中而已。
为什么会OOM
这个是因为Android系统对dalvik的vm heapsize作了硬性限制,当java进程申请的java空间超过阈值时,就会抛出OOM异常(这个阈值可以是48M、24M、16M等,视机型而定),可以通过adb shell getprop | grep dalvik.vm.heapgrowthlimit查看此值。
也就是说,程序发生OMM并不表示RAM不足,而是因为程序申请的java heap对象超过了dalvik vm heapgrowthlimit。也就是说,在RAM充足的情况下,也可能发生OOM。
如何绕过dalvikvm heapsize的限制
- 创建子进程
- 使用jni在native heap上申请空间,nativeheap的增长并不受dalvik vm heapsize的限制,只要RAM有剩余空间,程序员可以一直在native heap上申请空间,当然如果 RAM快耗尽,memory killer会杀进程释放RAM
Lint学习
Android Studio User Guide
开发工作流
创建Android Library
一个Android Library和普通的Android工程一样都包含source code、resource、manifest文件等,但它并不编译进apk,而是编译成一个aar文件,你可以对它进行依赖。
如何创建
可以直接通过Android Studio的New Module来进行创建一个新的Module.它和普通的application的主要差别在与所依赖的Gradle Android插件。application下面的是apply plugin: 'com.android.application'
而Moudle下的是apply plugin: 'com.android.library'
,因此你也通过更改这个还完成相互的转换。
添加Library依赖
添加Library到project目录下
- 可以通过new Moudle
- import Moudle
看项目根目录下的setting。gradle是否包含该Moudle
1include ':app', ':mylibrary'在主app下的build.gradle设置对其的依赖,如果不设置则不会生效
123dependencies {compile project(":mylibrary")}
4.进行同步
通过上面的步骤就可以使一个项目依赖一个Library了,如果只想让其在指定的build variant
,可以用build variant
的名字加Compile来取代compile,例如下面的就可以只在release环境下依赖mylibrary moudle:
对Library进行依赖后,你就有可以访问该Library下的的code和resources,在build的时候会被一起装到apk,同时你也可以在project-name/module-name/build/outputs/aar/
里找到生成的aar包,也可以通过Build > Make Project重新生成。
发布Library以非默认的variants
默认的,library最终只以release的方式在Android项目里引用,即使你指定它的BuildVariant,但是我们通过添加标识来指定默认的发布方式:
如果包含了flavors,你就必须指定它的完整的名字,例如。
在发布release时,你需要针对不同的build varients改变defaultPublishConfig,很麻烦,所以你可以告诉gradle,去生成一个Library所有的build varients,然后指定就可以了:如下:
指定当执行demoDebug
时,Library也用demoDebug
:
开发考虑
当Library添加到app中时,Library集会按照一次一个的形式与应用合并,具体的规则如下:
- 资源合并冲突
- 当Library和app中同时存在相同的资源id时,使用app的
- 当两个aar包存在冲突时用dependencies块中靠前的
- 为了避免资源冲突可以根据module给资源id添加独一无二的前缀,
- library module可以包含jar包
- 你可以开发一个带有Jar包的Library module,但是你需要手动的在app moudle的buld path中添加这jar包的path
- Library modules不能包含原始assets
- 在library module中不支持使用asset文件(保存在assets目录下的),任何asset资源必须放在app module下
- app module下的minSdkVersion必须大于等于 library下的
因为library最后会被编译进app module,所以library module必须能够兼容app module的minSdkVersion - 每个library都会创建自己的R类文件
- 在您构建相关应用模块时,库模块将先编译到 AAR 文件中,然后再添加到应用模块中。因此,每个库都有其自己的 R 类,并根据library的package命名。主要在build/generated/source
- 当app moudle依赖了两个相同的library,但是不同版本,优先依赖高版本
- 两个library都依赖了相同的一个库,但是库的版本不同,优先依赖高版本的,可以通过gradle dependencies查看被更改的
- 可以在Library中添加相应的Proguard配置文件来启动代码压缩,build工具会将这个文件嵌入到library moudle生成的aar包中,当你添加library到app中,library的proguard文件会附加到app的proguard文件后面。
当Proguard在app模块中运行时,它会执行app下的和library下的命令,所以你不需要独自在library下运行proguard。要指定您的库的配置文件名称,请将其添加到consumerProguardFiles
方法中,此方法位于您的library的 build.gradle 文件的 defaultConfig 块内。例如,以下片段会将 lib-proguard-rules.txt 设置为库的 ProGuard 配置文件:123456android {defaultConfig {consumerProguardFiles 'lib-proguard-rules.txt'}...}
AAR文件详解
AAR( Android Archive )文件的文件扩展名为,文件本身是一个包含以下强制性条目的 zip 文件:
- /AndroidManifest.xml
- /classes.jar
- /res/
/R.txt
此外,AAR 文件可能包含以下可选条目中的一个或多个:/assets/
- /libs/name.jar
- /jni/abi_name/name.so(其中 abi_name 是 Android 支持的 ABI 之一)
- /proguard.txt
- /lint.jar
编写应用
改变resource 目录
默认的resource在 module-name/src/source-set-name/res/
目录下,一般是src/main/res
,但是可以通过gradle来设置
或者为一个source set指定多个资源目录
在最终的apk文件中包含以下目录下的resource文件:
- main source set
- Build variant source set
- Android libraries(AARs)
如果一个resource包含多个,但只有一个被包含进最终的APK,build工具按照如下优先级选择:build variant > build type > product flavor > main source set > library dependencies
Lint检查
从命令行构建app
gradle wrapper
主要通过gradle来进行构建,AS中会为每个项目配置一个gradle wrapper
,针对windows提供了相应的.bat文件,针对linux和mac os提供了shell脚本,它们会自动下载在gradle-wrapper.properties
中指定的版本。这样一来我们就不必考虑操作系统因素,直接通过wrapper执行gradle task
执行gradle task
切到项目的根目录下,执行./gradlew task-name
,也可以下载gradle为其设置环境变量,然后通过gradle task-name
来执行,可以通过gradle tasks
看项目里的task列表.
build type
默认情况下,存在两个Build type: release 和 debug,其中debug并不会在gradle文件里显示,但其实是存在的,我们点击AS的Build Variants时会看见,平时开发时默认的就是这个模式,而且它的debuggable是为true的。无论哪种build都需要通过密钥进行sign,才能安装到手机上,debug build通过SDK tools提供的debug key,这个key是不安全的而且不能在google store上发布APK,在release buildd则需要自己的key
Android
为了提高Android系统的安全性,Google从Android 7.0开始增加一种新的增强签名模式,从Android Gradle Plugin 2.2.0开始,构建系统在打包应用后签名时默认使用APK signature scheme v2,该模式在原有的签名模式上,增加校验APK的SHA256哈希值,如果签名后对APK作了任何修改,安装时会校验失败,提示没有签名无法安装,解决办法是在 signingConfigs 里增加 v2SigningEnabled false
Android数据存储和Android7适配
Android提供了几种选项用于应用数据的持久化,你可以根据自己的需要来选择合适的方式来进行数据存储,比如你的数据是否是私有的,你的数据需要多大的空间,以及你的数据格式是什么样的。目前的存储方式有以下几种:
- SharedPerferences
- 内部存储
- 外部存储
- SQLite数据库
- 网络连接
SharedPerferences
SP主要适用于读取K-V结构的。你还可以用PreferenceActivity来创建一个设置用户偏好的页面,它会自动帮你处理K-V,定制型较差。SP也可以设置读取的权限,包含privateprivate是只允许对本应用访问创建的文件,readable、writeable则是其他应用对创建的文件只有可读、写权限,一般很少用这个。主要位于应用包下的shared_prefs文件下
用内部存储
内部存储是通过对文件的读写来实现的,默认是private的,即操作该文件时会直接覆盖,MODE_APPEND
是追加,MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE在API17后废弃掉了,同时在Android7.0时不能够通过文件名分享私有文件也不能通过file://来分享,否则会直接crash。如果App要分享私有文件给其他app,需要通过FLAG_GRANT_READ_URI_PERMISSION
的FileProvider
。主要位于应用包名下的files文件下
|
|
存储缓存文件
可以通过getCacheDir()
来获取一个内部的目录,主要用于存储一些临时的缓存文件。当设备的内部存储空间不够时,Android会优先删除该目录下的文件,然而我们不能依赖于系统去清理,应该自己来维护。该文件主要位于应用包名下的cache文件下
用外部存储
每个Android设备都支持一个共享的外部存储,这个外部存储可以是一个sd卡,也可以是内部的存储,保存在外部存储的是全局可读的,并且可以在插上usb移动到电脑上。
为了能够读取或写入外部存储的文件,需要获取 READ_EXTERNAL_STORAGE
或者WRITE_EXTERNAL_STORAGE
权限
在对外部存储的文件做任何操作前都需要先检查下存储媒介的状态是否是可写或者可读的,不然会出问题。
文件能够被分享给其他app
一般来说,用户通过app产生的一些新文件应当保存在设备的公共的地方,比如说用户产生的图片、音频等等。执行该操作时需要使用共享的公共目录,比如Music/、Pictures等,这些是系统推荐的,通过将您的文件保存到相应的媒体类型目录,系统的媒体扫描程序可以在系统中正确地归类您的文件(例如铃声在系统设置中显示为铃声而不是音乐)。也可以自己添加合适的type。
保存应用的私有文件
如果要保存的文件不希望被其他应用访问,可以通过调用getExternalFilesDir
来使用外部存储来进行私有存储,此方法也可以通过type来指定子目录的类型。已分配某个内部存储器分区用作外部存储的设备可能还提供了 SD 卡槽。在使用运行 Android 4.3 和更低版本的这类设备时,getExternalFilesDir() 方法将仅提供内部分区的访问权限,而您的应用无法读取或写入 SD 卡。不过,从 Android 4.4 开始,可通过调用 getExternalFilesDirs() 来同时访问两个位置,该方法将会返回包含各个位置条目的 File 数组。 数组中的第一个条目被视为外部主存储;除非该位置已满或不可用,否则应该使用该位置。 如果希望在支持 Android 4.3 和更低版本的同时访问两个可能的位置,请使用支持库中的静态方法 ContextCompat.getExternalFilesDirs()。 在 Android 4.3 和更低版本中,此方法也会返回一个 File 数组,但其中始终仅包含一个条目。
保存缓存文件
与内部存储类似,外部存储也包含了一个用于存储缓存文件的目录,可以通过getExternalCacheDir()
来得到,也可以通过与前述 ContextCompat.getExternalFilesDirs() 相似,您也可以通过调用 ContextCompat.getExternalCacheDirs() 来访问辅助外部存储(如果可用)上的缓存目录。
Android7权限适配
在Android7中强制使用了StrictMode,带来的影响就是禁止向自己App外的应用公开file://
的URI,否则将会报FileUriExposedException
异常,该异常是在startActivity的时候做的,所以如果你的App的target api是低于24的话,你的App并不会崩溃,如果你启动的应用尝试用你传过去的URI向其他应用(包括自己的应用)公开的话,也是会Crash的。正确的做法是发送一项content://URI
,并授予URI临时访问权限。系统为我们封装了FileProvider
类来简化操作。
比如下面的这段代码如果在Android7上且,target api是24时就会Crash:
FileProvider
解决方案是官方提供了一个FileProvider
类来将文件的路径转换.具体的使用方法:
- 在manifest中添加provider12345678910<providerandroid:name="android.support.v4.content.FileProvider"android:authorities="com.example.kevin.fileprovider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/wm_file_path" /></provider>
为了防止manifest文件merge冲突,可以用自己实现的FileProvider。
- 在res目录下创建xml目录,然后创建一个路径文件(如wm_file_path.xml)12345678<?xml version="1.0" encoding="utf-8"?><resources><paths><external-pathname="camera_photos"path="Pictures/waimai2" /></paths>ß</resources>
<path>
结点下可以包括