美文网首页Android开发之散记AndroidAndroid
Android开发十五《综合技术》

Android开发十五《综合技术》

作者: 独自闯天涯的码农 | 来源:发表于2022-03-28 20:57 被阅读0次

一、使用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 文件中设置 <receiver> 标签的 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<Rect>,没一个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开发十五《综合技术》

    本文链接:https://www.haomeiwen.com/subject/mxnxrrtx.html