Android开发十五《综合技术》

一、使用CrashHandler来获取Crash信息

通过设置Thread. setDefaultUncaughtExceptionHandler;

public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {}

二、使用Multidex来解决方法数越界

compile 'com.android.support:multidex:1.0.2'

public class MyApplication extends MultiDexApplication { ... }

三、Android Apk编译打包流程

编译打包流程

编译打包步骤:

1. 打包资源文件,生成R.java文件

打包资源的工具是aapt(The Android Asset Packaing Tool(Android\sdk\build-tools\25.0.0\aapt.exe)。在这个过程中,项目中的AndroidManifest.xml文件和布局文件XML都会编译,然后生成相应的R.java,另外AndroidManifest.xml会被aapt编译成二进制。
存放在APP的res目录下的资源,该类资源在APP打包前大多会被编译,变成二进制文件,并会为每个该类文件赋予一个resource id。对于该类资源的访问,应用层代码则是通过resource id进行访问的。Android应用在编译过程中aapt工具会对资源文件进行编译,并生成一个resource.arsc文件,resource.arsc文件相当于一个文件索引表,记录了很多跟资源相关的信息。

2. 处理aidl文件,生成相应的Java文件

这一过程中使用到的工具是aidl(Android Interface Definition Language),即Android接口描述语言(Android\sdk\build-tools\25.0.0\aidl.exe)。
aidl工具解析接口定义文件然后生成相应的Java代码接口供程序调用。如果在项目没有使用到aidl文件,则可以跳过这一步。

3. 编译项目源代码,生成class文件

项目中所有的Java代码,包括R.java和.aidl文件,都会变Java编译器(javac)编译成.class文件,生成的class文件位于工程中的bin/classes目录下。

4. 转换所有的class文件,生成classes.dex文件

dx工具生成可供Android系统Dalvik虚拟机执行的classes.dex文件,该工具位于(Android\sdk\build-tools\25.0.0\dx.bat)。
任何第三方的libraries和.class文件都会被转换成.dex文件。dx工具的主要工作是将Java字节码转成成Dalvik字节码、压缩常量池、消除冗余信息等。

5. 打包生成APK文件

所有没有编译的资源,如images、assets目录下资源(该类文件是一些原始文件,APP打包时并不会对其进行编译,而是直接打包到APP中,对于这一类资源文件的访问,应用层代码需要通过文件名对其进行访问);编译过的资源和.dex文件都会被apkbuilder工具打包到最终的.apk文件中。
打包的工具apkbuilder位于 android-sdk/tools目录下。apkbuilder为一个脚本文件,实际调用的是(E:\Documents\Android\sdk\tools\lib)文件中的com.android.sdklib.build.ApkbuilderMain类。

6. 对APK文件进行签名

一旦APK文件生成,它必须被签名才能被安装在设备上。
在开发过程中,主要用到的就是两种签名的keystore。一种是用于调试的debug.keystore,它主要用于调试,在Eclipse或者Android Studio中直接run以后跑在手机上的就是使用的debug.keystore。
另一种就是用于发布正式版本的keystore。

7. 对签名后的APK文件进行对齐处理

如果你发布的apk是正式版的话,就必须对APK进行对齐处理,用到的工具是zipalign(E:\Documents\Android\sdk\build-tools\25.0.0\zipalign.exe)
对齐的主要过程是将APK包中所有的资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问apk文件时的速度会更快。对齐的作用就是减少运行时内存的使用。

四、Android Apk安装过程

Apk安装的主要步骤:

  1. 将apk文件复制到data/app目录
  2. 解析apk信息
  3. Dalvik虚拟机执行dexopt操作(优化.dex文件成odex文件到data/dalvik-cache);如果是ART虚拟机执行dex2oat操作(将.dex文件翻译成.oat文件)
  4. 更新权限信息
  5. 完成安装,发送Intent.ACTION_PACKAGE_ADDED广播
安装过程

五、Android的动态加载技术

动态加载技术也叫插件化技术,通过插件化来减轻应用的内存和CPU占用。
主要解决三个基础性问题:

1、资源访问

ContextImpl中有两个抽象方法:getAssets和getResources方法

2、Activity生命周期管理

将Activity生命周期方法提取出来作为一个接口,通过代理Activity去调生命周期方法。

3、ClassLoader的管理

同一个插件采用同一个ClassLoader加载类。

六、Android权限机制

Android将安全设计贯穿系统架构的各个层面,覆盖系统内核、虚拟机、应用程序框架层以及应用层各个环节,力求在开放的同时,也最大程度地保护用户的数据、应用程序和设备的安全。Android安全模型主要提供以下几种安全机制:


安全机制

1、权限的本质

在 Android 中,一个权限,本质上是一个字符串,一个可以表示执行特定操作的能力的字符串。访问 SD 卡的能力,访问通讯录的能力,启动或访问一个第三方应用中的组件的能力。

pm list permissions -f 命令可以详细查看 Android 所有预定义的权限

权限的信息包括:定义的包名、标签、描述和保护级别

+ permission:android.permission.DELETE_PACKAGES
package:android
label:null
description:null
protectionLevel:signature|privileged

2、权限的级别

  • normal 级别:
    权限保护级别的默认值,无须用户确认,只要声明了,就自动默默授权。如:ACCESS_NETWORK_STATE。

  • dangerous 级别:
    赋予权限前,会弹出对话框,显式请求权限。如:READ_SMS。因为 Android 需要在安装时赋予权限,所以安装的确认对话框,也会显示列出权限清单。

  • signature 级别:
    signature 级别的权限是最严格的权限,只会赋予与声明权限使用相同证书的应用程序。

以系统内置 signature 级别权限为例,Android 系统应用的签名由平台密钥签发,默认情况下源码树里有 4 个不同的密钥文件:platform、shared、media 和 testkey。所有核心平台的包(如:设置、电话、蓝牙)均使用 platform 密钥签发;搜索和通讯录相关的包使用 shared 签发;图库和媒体相关的包使用 media 密钥签发;其他的应用使用 testkey 签发。定义系统内置权限的 framework-res.apk 文件是使用平台密钥签发的,因此任何试图请求 signature 级别内置权限的应用程序,需要使用与框架资源包相同的密钥进行签名。

  • signatureOrSystem 级别:
    可以看做是一种折中的级别,可被赋予与声明权限具有相同签名证书密钥的应用程序(同 signature 级别)或者系统镜像的部分应用,也就是说这允许厂商无须共享签名密钥。Android 4.3 之前,安装在 system 分区下的应用会被自动赋予该保护级别的权限,而 Android 4.4 之后,只允许安装在 system/priv-app/ 目录下的应用才能被主动赋予。

3、权限的管理

在每个应用安装时,权限就已经赋予了,系统使用包管理服务来管理权限。打开我们系统目录下的 /data/system/packages.xml,可以看到文件包含了所有已定义的权限列表和所有 apk 的包信息,这可以看做是包管理服务维护的一个已安装程序的核心数据库,这个数据库,随着每次应用安装、升级或卸载而进行更新。

注意:
6.0以下权限在/data/system/packages.xml
6.0以上权限在/data/system/users/0/runtime-permissions.xml

4、权限的赋予

我们知道,Android 应用安装时,会被分配一个唯一的 UID,应用启动时,包管理器会设置新建进程的 UID 和 GID 为应用程序的 UID。如果应用已经被赋予了额外的权限,就把这些权限映射成一组 GID,作为补充 GID 分配给进程。低层就可以依赖于进程的 UID、GID 和补充 GID 来决定是否赋予权限了。

内置权限到 GID 的映射是定义在 /etc/permission/platform.xml ;
6.0以上动态添加到以上位置
系统进程的权限配置信息在
Android\system\core\include\private\android_filesystem_config.h

注意:
PID:表示应用的进程 ID,PPID 表示父进程 ID;
UID:UID代表一个应用;应用安装时分配一个唯一的;

5、权限的检查

1. 系统内核层权限检查

内核代码中current_has_network(void) 方法检查了进程的所在组。如果不在 inet 组,则直接返回错误。设置申请权限,经过解析,逐步映射到内核层的组 ID 和用户 ID,最终才能通过内核层的检查。

2. 框架层权限检查

Android 6.0 之前组件不能在运行时改变权限,所以系统的权限检查执行过程是静态的

  • 动态权限执行:
    通过IPC:Android 的核心系统服务统一会注册到服务管理器,系统服务可以直接检查调用者的 UID,通过限定 UID 来控制访问权限;不适合非固定UID的应用,适合只允许以 root(UID:0) 或 system(UID:1000) 运行的进程访问的服务检查。
  • 静态权限执行
    跨应用组件交互
    我们使用隐式 Intent 来表达意图,搜索匹配的组件,如果有多个,弹出选择框,目标组件被选定后,会由 ActivityManagerService 执行权限检查,检查目标组件是否有相应的权限要求,如果有,则把权限检查的工作交给 PMS,去检查调用者有没有被授权这些权限。
    接下来的总体的流程和动态执行流程大致相同:Binder.getCallingUid()和Binder.getCallingPid()获取调用者的 UID 和 PID,然后利用 UID 映射包名,再获得相关权限集合。如果权限集合中含有所需权限即启动,否则抛出 SecurityException 异常。静态权限执行这里,我们可以详细了解下,每种组件的权限检查时机和具体顺序是怎么样的。
  • 组件权限执行
Activity

会在 startActivity() 和 startActivityForResult() 里解析到声明权限的 Activity 时,就执行权限检查。

Service

startService()、stopService() 和 bindService(),这 3 个方法被调用时都会进行权限检查。

BroadCastReceiver

发送广播除了常用的 sendBroadcast(Intent intent),还有个 sendBroadcast(Intent intent, String receiverPermission),该方法可以要求广播接受者具备特定的权限,但是,调用 sendBroadcast 是不会进行权限检查的,因为广播是异步的,所以权限检查会在 intent 传递到已注册的广播接受者时进行,如果接收者不具备特定的权限,则不会接收到该广播,也不会收到 SecurityException 异常。

反过来,接收者可以要求广播发送者必须具备的权限,所要求的权限在 manifest 文件中设置 标签的 permission 属性,或者动态注册时指定 registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler),权限检查也是在广播传递时执行。

所以,收发广播可以分开指定权限。值得一提的是,一些系统广播被声明为 protected,并且只能由系统进程发送,比如 PACKAGE_INSTALLED。只能由系统进程发送,这个限制会在内核层进行检查,对调用者的 UID 进行匹配,只能是 SYSTEM_UID、PHONE_UID、SHELL_UID、BLUETOOTH_UID 或 root。如果其他 UID 的进程试图发送系统广播,则会收到 SecurityException 异常。

ContentProvider

ContentProvider 可以为读写分别指定不同的权限,即:调用目标 provider、query() 方法 和 insert()、update()、delete() 都会进行权限检查。

总结:
Android 的权限的检查会在各个层次上实施。
1、高层的组件,例如应用和系统服务,通过包管理器查询应用程序被赋予的权限,并决定是否准予访问。
2、低层的组件,通常不访问包管理器,比如本地守护进程,依赖于进程的 UID、GID 和补充 GID 来决定赋予。
3、访问系统资源时,如设备文件、UNIX 域套接字和网络套接字,则由内核根据所有者、目标资源的访问权限和访问进程的进程属性或者 packages.list 来进行控制。

共享 UID
最后简单说下共享 UID,填一下前面挖的坑。虽说 Android 会为每一个应用分配唯一的 UID,但如果应用使用相同的密钥签发,就可以使用相同 UID 运行,也就是运行在同一个进程中。
这个特性被系统应用和核心框架服务广泛使用,比如:Google Play 和 Google 定位服务,请求同一进程内的 Google 登录服务,从而达到静默自动同步用户数据的体验。
值得注意的是:Android 不支持将一个已安装的应用,从非共享 UID 切换到共享状态,因为改变了已安装应用的 UID,会导致应用失去对自己文件的访问权限(在一些早期 Android 版本中),所以如果使用共享 UID 必须从一开始就设计好。

参考:你真的了解Android权限机制吗

七、Android刘海屏适配

1、AndroidP刘海屏的适配:

Android P 支持最新的全面屏以及为摄像头和扬声器预留空间的凹口屏幕。通过全新的 DisplayCutout 类,可以确定非功能区域的位置和形状,这些区域不应显示内容。要确定这些凹口屏幕区域是否存在及其位置,使用 getDisplayCutout() 函数。

  1. 设置LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES模式
  2. 设置沉浸式布局模式
  3. 计算状态栏高度,进行布局;如果有特殊UI要求,则可以使用DisplayCutoutDemo类去获取刘海屏的坐标,完成UIAndroid P 中 WindowManager.LayoutParams 新增了一个布局参数属性layoutInDisplayCutoutMode:
DisplayCutout 类方法 说明
getBoundingRects() 返回Rects的列表,每个Rects都是显示屏上非功能区域的边界矩形
getSafeInsetLeft () 返回安全区域距离屏幕左边的距离,单位是px
getSafeInsetRight () 返回安全区域距离屏幕右边的距离,单位是px
getSafeInsetTop () 返回安全区域距离屏幕顶部的距离,单位是px
getSafeInsetBottom() 返回安全区域距离屏幕底部的距离,单位是px
View decorView = mAc.getWindow().getDecorView();
        if(decorView != null){
            Log.d("hwj", "**controlView**" + android.os.Build.VERSION.SDK_INT);
            Log.d("hwj", "**controlView**" + android.os.Build.VERSION_CODES.P);
            WindowInsets windowInsets = decorView.getRootWindowInsets();
            if(windowInsets != null){
                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
                    DisplayCutout displayCutout = windowInsets.getDisplayCutout();
                    //getBoundingRects返回List,没一个list表示一个不可显示的区域,即刘海屏,可以遍历这个list中的Rect,
                    //即可以获得每一个刘海屏的坐标位置,当然你也可以用类似getSafeInsetBottom的api
                    Log.d("hwj", "**controlView**" + displayCutout.getBoundingRects());
                    Log.d("hwj", "**controlView**" + displayCutout.getSafeInsetBottom());
                    Log.d("hwj", "**controlView**" + displayCutout.getSafeInsetLeft());
                    Log.d("hwj", "**controlView**" + displayCutout.getSafeInsetRight());
                    Log.d("hwj", "**controlView**" + displayCutout.getSafeInsetTop());
                }
            }
        }

Android P 中 WindowManager.LayoutParams 新增了一个布局参数属性 layoutInDisplayCutoutMode:

模式 模式说明
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 只有当DisplayCutout完全包含在系统栏中时,才允许窗口延伸到DisplayCutout区域。 否则,窗口布局不与DisplayCutout区域重叠。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 该窗口决不允许与DisplayCutout区域重叠。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 该窗口始终允许延伸到屏幕短边上的DisplayCutout区域。Android P 之前的刘海屏适配

2、AndroidP之前刘海屏的适配

不同厂商的刘海屏适配方案不尽相同,需分别查阅各自的开发者文档。

3、刘海屏判断

/**
 * 判断是否是刘海屏
 * @return
 */
public static boolean hasNotchScreen(Activity activity){
    if (getInt("ro.miui.notch",activity) == 1 || hasNotchAtHuawei(activity) || hasNotchAtOPPO()
            || hasNotchAtVivo(activity) || isAndroidP(activity) != null){ //TODO 各种品牌
        return true;
    }
 
    return false;
}
 
/**
 * Android P 刘海屏判断
 * @param activity
 * @return
 */
public static DisplayCutout isAndroidP(Activity activity){
    View decorView = activity.getWindow().getDecorView();
    if (decorView != null && android.os.Build.VERSION.SDK_INT >= 28){
        WindowInsets windowInsets = decorView.getRootWindowInsets();
        if (windowInsets != null)
            return windowInsets.getDisplayCutout();
    }
    return null;
}
 
/**
 * 小米刘海屏判断.
 * @return 0 if it is not notch ; return 1 means notch
 * @throws IllegalArgumentException if the key exceeds 32 characters
 */
public static int getInt(String key,Activity activity) {
    int result = 0;
    if (isXiaomi()){
        try {
            ClassLoader classLoader = activity.getClassLoader();
            @SuppressWarnings("rawtypes")
            Class SystemProperties = classLoader.loadClass("android.os.SystemProperties");
            //参数类型
            @SuppressWarnings("rawtypes")
            Class[] paramTypes = new Class[2];
            paramTypes[0] = String.class;
            paramTypes[1] = int.class;
            Method getInt = SystemProperties.getMethod("getInt", paramTypes);
            //参数
            Object[] params = new Object[2];
            params[0] = new String(key);
            params[1] = new Integer(0);
            result = (Integer) getInt.invoke(SystemProperties, params);
 
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    return result;
}
 
/**
 * 华为刘海屏判断
 * @return
 */
public static boolean hasNotchAtHuawei(Context context) {
    boolean ret = false;
    try {
        ClassLoader classLoader = context.getClassLoader();
        Class HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil");
        Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen");
        ret = (boolean) get.invoke(HwNotchSizeUtil);
    } catch (ClassNotFoundException e) {
        AppLog.e("hasNotchAtHuawei ClassNotFoundException");
    } catch (NoSuchMethodException e) {
        AppLog.e("hasNotchAtHuawei NoSuchMethodException");
    } catch (Exception e) {
        AppLog.e( "hasNotchAtHuawei Exception");
    } finally {
        return ret;
    }
}
 
public static final int VIVO_NOTCH = 0x00000020;//是否有刘海
public static final int VIVO_FILLET = 0x00000008;//是否有圆角
 
/**
 * VIVO刘海屏判断
 * @return
 */
public static boolean hasNotchAtVivo(Context context) {
    boolean ret = false;
    try {
        ClassLoader classLoader = context.getClassLoader();
        Class FtFeature = classLoader.loadClass("android.util.FtFeature");
        Method method = FtFeature.getMethod("isFeatureSupport", int.class);
        ret = (boolean) method.invoke(FtFeature, VIVO_NOTCH);
    } catch (ClassNotFoundException e) {
        AppLog.e( "hasNotchAtVivo ClassNotFoundException");
    } catch (NoSuchMethodException e) {
        AppLog.e(  "hasNotchAtVivo NoSuchMethodException");
    } catch (Exception e) {
        AppLog.e(  "hasNotchAtVivo Exception");
    } finally {
        return ret;
    }
}
/**
 * OPPO刘海屏判断
 * @return
 */
public static boolean hasNotchAtOPPO(Context context) {
    return  context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
}

你可能感兴趣的:(Android开发十五《综合技术》)