美文网首页
Android部分技术点目录(3)

Android部分技术点目录(3)

作者: 蓝灰_q | 来源:发表于2017-12-12 23:24 被阅读243次

    目录

    Android插件化原理
    oom_adj的具体计算方法
    ActivityStack/ActivityRecord/TaskRecord关系
    Binder深入
    把XML文件inflate到界面上的全过程
    try catch finally的几个坑
    关于Thread
    Android中有哪些解析xml的类
    SIM卡的EF文件是什么?有何作用
    段式和页式存储的区别
    P、V原语
    Android虚拟机、apk、aapt、dex、odex、deodex、smali
    JVM、Dalvik和ART的区别
    JVM基于栈,Dalvik基于寄存器
    ART的GC优化

    Android插件化原理

    插件化就是动态加载apk,两个核心问题是加载代码和加载资源,但真正棘手的问题在于组件的生命周期和资源id冲突。

    1.加载代码

    需要一个ClassLoader来加载类。
    1.1 ClassLoader
    DexClassLoader和双亲委派。
    利用当前的ClassLoader,新建DexClassLoader:

        ClassLoader localClassLoader = ClassLoader.getSystemClassLoader();
        DexClassLoader dexClassLoader = new DexClassLoader(dexPath,
            dexOutputPath, null, localClassLoader);
    

    新的DexClassLoader以现有的ClassLoader为双亲,根据双亲委派机制,两个apk的代码就都能访问了。

    2.加载资源

    需要通过ContextImpl来获取资源,具体是通过getAssets和getResources来获取资源的。
    2.1 AssetManager
    Asset需要add新apk的AssetPath:

    Method addAssetPathMethod = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
    addAssetPathMethod.invoke(instance, dexPath);
    

    2.2Resources
    Resource需要使用增加过的Asset:

    private Resources getResources(Context ctx, AssetManager selfAsset) {
                    DisplayMetrics metrics = ctx.getResources().getDisplayMetrics();
                    Configuration con = ctx.getResources().getConfiguration();
                    return new Resources(selfAsset, metrics, con);
    }
    

    3.生命周期管理

    通过DexClassLoader通过反射创建出来的Activity是一个普通的java对象,并没有生命周期,这个问题有多个解决思路:
    3.1 代理
    在主应用的manifest中注册一个代理activity,并将proxy的生命周期,触摸按键回调等与插件Activity同步,实现插件Activity的生命周期管理。
    3.2 替换ClassLoader
    App的ClassLoader可以这样找到:ActivityThread-->LoadedApk-->mClassLoader。
    然后,用反射,setFiledObject替换android.app.LoadedApk中的mClassLoader实例,这个实例需要mPackages,mPackages又需要currentActivityThread:

            Object currentActivityThread = RefInvoke.invokeStaticMethod(  
                    "android.app.ActivityThread", "currentActivityThread",  
                    new Class[] {}, new Object[] {});//获取主线程对象  
            String packageName = this.getPackageName();//当前apk的包名  
            ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(  
                    "android.app.ActivityThread", currentActivityThread,  
                    "mPackages");  
            WeakReference wr = (WeakReference) mPackages.get(packageName);  
            RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",  
                    wr.get(), dLoader);  
    

    这种方法下,声明组件,还是要在宿主App中声明。
    而且,没有apk的资源包,不能再用setContentView,要额外做一个设置视图的功能。
    3.3 插入dex
    DexClassLoader和PathClassLoader(只能加载已安装的app)的父类BaseDexClassLoader,有一个Element[] dexElements。
    将新的DexClassLoader中的Elements合并到PathClassLoader的数组中,也可以实现。

    4.资源冲突

    在apk编译过程中,资源文件会由aapt工具生成对应的ID,并在打包的时候组织成resources.arsc文件,resources.arsc文件是用来描述资源ID和资源位置配置信息,从18个维度描述了一个资源ID的配置信息(语言、分辨率等),就是资源ID和资源的索引表。资源的ID生成是有规则的,规则:0xPPTTNNNN,由8位16进制组成,其中:
    PP段:表示资源的包空间:0x01表示系统资源空间,0x7f表示应用资源空间。
    TT段:表示资源类型。
    NNNN段:4个16进制表示资源id,一个apk中同一类型资源从0000开始递增。

    因为宿主apk和插件apk是独立编译出来的两个独立的apk,那么其中就有资源ID相同的情况出现,从而产生资源ID冲突。Sophix的解决方法是把补丁中的resource用0x66替换0x7f。

    Android中插件开发篇之----动态加载Activity(免安装运行程序)
    Android动态加载技术三个关键问题详解
    Android插件化系列第(二)篇---动态加载技术之apk换肤

    oom_adj的具体计算方法

    Linux有个OOM Killer,会监控那些占用内存过大,而且会瞬时消耗大量内存的进程,为防止内存耗尽,会强制杀死进程。
    Linux内核会通过特定的算式计算每个进程的oom_score的值,oom_score为2的n次方,n为oom_adj的值,范围是-16~15,-17表示禁用OOM。

    ActivityStack/ActivityRecord/TaskRecord关系

    图片来自从源码角度看Activity launchMode与Stack/Task
    从源码角度看Activity launchMode与Stack/Task

    Binder深入

    在App端,Binder操作时通过Activity-->ContextImpl-->LoadedApk$ServiceDispatcher来操作的,最终操作者是最后面这个LoadedApk的子类ServiceDispacher,最关键的代码为:

    public void doConnected(ComponentName name, IBinder service, boolean dead) {
                ...
                // If there was an old service, it is now disconnected.
                if (old != null) {
                    mConnection.onServiceDisconnected(name);//断开连接
                }
                if (dead) {
                    mConnection.onBindingDied(name);//死亡通知(binder服务意外死亡)
                }
                // If there is a new service, it is now connected.
                if (service != null) {
                    mConnection.onServiceConnected(name, service);//连接
                }
    }
    

    连接/连接断开通知
    对于Binder来说,Binder的Service是一个存在于某个进程里的对象,系统用红黑树管理这些对象的引用,并提供给client。所以,如果进程尚未启动,没有注册这个Binder服务,系统就找不到该服务,无法提供给client。
    因为建立服务的时长是不确定,所以需要在ServiceConnection中用回调来处理连接结果:

            ServiceConnection connection=new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                }
    
                @Override
                public void onServiceDisconnected(ComponentName name) {
    
                }
            };
    

    死亡通知
    服务进程关闭时,会释放进程资源,关闭文件等,此时调用Binder驱动的binder_release-->binder_Died,如果AIDL客户端注册过DeathRecipient接口,调用linkToDeath方法,就能收到死亡通知:

    public class XX implements Binder.DeathReceipient{//注册接口
    
      observer.asBinder.linkToDeath();//调用方法
    
      @Override
      public void binderDied(){}//收到回调
    }
    

    如果是BindService的用法,可以直接在ServiceConnection中,用IBinder去注册死亡通知:

            ServiceConnection connection=new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    //注册死亡通知
                    try {
                        service.linkToDeath(new IBinder.DeathRecipient() {
                            @Override
                            public void binderDied() {
        
                            }
                        }, IBinder.FLAG_ONEWAY);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
    
                @Override
                public void onServiceDisconnected(ComponentName name) {
    
                }
            };
    

    异常通知
    DeadObjectException和RemoteException。
    服务端进程死亡或缓冲内存区满,都会报DeadObjectException。
    writeStrongBinder/readStrongBinder
    为了实现双工通信,用来传送Binder对象的,例如startActivity时,会向AMS发送ApplicationThread Proxy对象:

    data.writeStrongBinder();//data是Parcel对象
    

    writeStrongBinder是调用native实现的
    线程池
    每个进程的binder线程池上限为15个,外加一个主binder线程。
    如果线程都满,会在todo队列中阻塞等待
    缓冲内存区
    linux层对mmap的上限为4M,Binder申请的mmap更低,同步空间为1016K,异步空间为508K(一半)。
    如果缓冲内存区满,会抛DeadObjectException。
    传递Bitmap
    如果通过广播传递Bitmap,是受1016K限制的,实际中,Bundler超过800K就会报TransactionTooLargeException。
    但是如果通过AIDL传递Bitmap,只要超过128K,就会使用ashmem来传图片,mmap只传递ashmem的fd。

    把XML文件inflate到界面上的全过程

    整个过程分三部分:
    1.连接系统服务,利用WMS获取Window的位置、层级、生命周期等;利用SurfaceFlinger获取Surface以便绘制界面;界面内容的定义是在Activity中实现的;
    2.创建Activity界面,在attach时创建PhoneWindow,在setContent时创建DecorView,调用动画、生成状态栏、活动栏、以及内容控件mContentParent。(所以,动画、状态栏等属性,需要在setContent前设置);
    3.填充DecorView,主要是把XML的layout文件,inflate到mContentParent中,把xml装进一个XmlResourceParser对象中,然后while解析xml结构,找到根节点(所以layout只能有一个最外层控件),用createViewFromTag创建根节点,用rInflateChildren遍历剩余的xml,最终生成xml对应的ViewTree,填充到DecorView的R.id.content里面。

    try catch finally的几个坑

    try catch finally中,四种情况:
    1.try里system.exit时不走finally
    2.在前面return或throw时,会先走finally,
    3.但是finally不能改return的值,因为return的机制是个传值,把变量值赋给不知名变量,finally不会改变这个不知名变量
    4.在finally里不能return,会导致数据不一致,会导致catch里throw不被执行

    关于Thread

    ActivityThread作为主线程,启动时最大的不同就是prepare(false),这个false表示线程不可以取消:

       //ActivityThread源码
        public static void prepareMainLooper() {
            prepare(false);//quitAllowed参数为false,不允许主线程退出
    

    Thread是线程类,并不是线程,线程需要交给虚拟机去调用本地方法(VMThread.create),从CPU上切割线程资源。

    Thread既继承了Runnable,又可以构造传参Runnable,所以它最终都是启动一个线程,去运行用户指定的Runnable,所以Thread相当于一个中介。

    线程的wait、sleep、join和interrupt
    wait不是java.lang.Thread的方法,是java.lang.Object的,wait会释放对象锁。
    sleep是抱着对象等时间(CPU不释放)
    join是插入到当前线程,让自己先执行
    yield是自己先暂停一下,看看是否有其他优先级更高的线程需要执行,此时锁和CPU都没有释放,如果直接回来,不用切换上下文
    interrupt是中断前面的执行过程,比如被block在wait,sleep或join上时,interrupt可以立即唤醒并抛出InterruptedException

    Thread.interrupt:中断
    Thread.interrupted:返回是否中断,并清除中断状态
    Thread.isInsterrupted:返回是否中断,不清除中断状态

    Android中有哪些解析xml的类

    SAX,流式处理,事件驱动,解析时为每种xml调用处理函数。
    PULL,Android系统内置,也是流式处理,但解析返回的是数字,根据数字自己写解析方式,可以自己控制解析到哪里。
    DOM,树状结构解析,可以直接访问文件的各个部分

    SIM卡的EF文件是什么?有何作用

    sim卡里有MF、DF、EF三种文件,前两种用于检索,只有EF可以存放数据。
    MF,主文件Master File,只有文件头,sim卡唯一,存放sim卡的控制信息和管理信息。
    DF,专用文件Dedicated File,只有文件头,相当于目录的根,存放整个目录的控制信息和管理信息。
    EF,基本文件Elementary File,既有文件头,又有文件体,文件头存放文件位置和控制信息,文件体存放数据

    段式和页式存储的区别

    基础:
    页和块,逻辑地址是连续的页,页对应在物理地址上是大小一致(4K)位置分散的块,用页表存放对应的页号和块号即可。
    段和基址,每个段对应一个连续的物理内存,段的大小不固定,通过段号和段表,找到物理基址。
    页式是一维的,块号是地址高位,块号&偏移量,即可拼出完整的物理地址。
    段式是二维的,根据段号找到基址(一个维度),加偏移量(另一个维度)即可找到对应的物理地址。
    段页式:先按逻辑分段,每个段内部又分页,地址结构是:段号+段内页号+页内地址
    段式/页式需访问两次内存,段页式需访问三次内存。

    P、V原语

    P、V原语解决资源争用的问题,主要依靠S信号量,S代表可用的资源数,P代表取用资源,V代表返还资源

    P原语时,S=S-1,表示准备取走一份资源,此时如果S>=0,代表不会取成负的,那么就可以正常取,如果S<0,就意味着没有资源了,那么就需要等待资源释放

    V原语时,S=S+1,表示准备还回一份资源,此时如果S>0,代表还之前不是负的,那么没有别人在等;如果S<=0,代表之前是负的,有别人在等资源,那么就需要通知一个等待资源的人。

    Android虚拟机、apk、aapt、dex、odex、deodex、smali

    虚拟机,Android的虚拟机先有Dalvik,后有Art,这两者都为移动平台做了特殊优化
    apk,apk其实是个rar,里面有这样几个主要对象:
    1.META-INF:文件夹,应用签名
    2.res:文件夹,资源文件
    3.classes.dex:执行程序
    4.resources.arsc:二进制资源文件
    aapt,Android的apk安装包是由aapt(Android Asset Packaging Tool)打包的
    dex,Dalvik VM Executors,其中最大的改变就是dex文件取代了jar文件,安卓系统第一次执行dex时,会用dexopt工具做优化,使更适合在当前设备上运行。
    odex,Optimized Dalvik VM Executors,优化的dex文件,这是安卓系统做的,把dex优化为odex,并删除旧的dex,优化的odex在data/dalvik-cache目录下。
    odex的优点:
    1.避免第一次启动时加载速度慢(已经优化过)
    2.减少RAM占用(不需要保存优化前的dex)
    3.防反编译(系统里只有本地优化过的odex)
    odex的缺点:
    1.升级时容易失败导致强制关闭
    2.占rom
    3.rom不方便修改
    deodex,第三方rom里基本都是deodex,apk里直接是dex,启动时从apk里读dex,并优化为odex
    deodex的优点:
    1.减少rom体积(不需要在data/dalvik-cache里寸odex)
    2.方便rom的定制和移植
    3.方便apk反编译和修改
    缺点:
    1.启动耗时,每次启动需要读出dex,优化为odex
    smali,odex需要用baksmali转换为dex,再把dex转换成jar包,直接从odex转jar包的话,大部分会有编译错误,只能简单的阅读分析。

    JVM、Dalvik和ART的区别

    Dalvik和ART并不是Java虚拟机,因为他们并没有完全遵循Java虚拟机规范。
    JVM运行class字节码文件,Dalvik运行dex文件(JIT模式),ART在安装时转换为oat机器码。
    最主要的差别是,JVM是基于栈,而Dalvik是基于寄存器,指令集不同。
    odex和oat都是保存在data/dalvik-cache目录下,不过还都是同名文件(dex后缀名)。
    odex是dey字节码,oat是机器码。

    JVM基于栈,Dalvik基于寄存器

    首先,无论哪种虚拟机,都需要以下功能:
    取指令
    译码
    执行
    存结果

    JVM是基于栈的,使用指令来载入和执行栈上的操作,需要大量的指令。
    基于栈的时候,真正的运算都在操作数栈上运行,不在非栈的内存中执行,这样虚拟机可以无视物理结构,但是处理速度慢。

    Dalvik改为基于寄存器,使用空间小。
    基于寄存器时,没有操作数栈的概念,而是有很多虚拟寄存器,执行引擎从寄存器中找到操作数,取出操作数,进行运算(直接传入CPU运算,不需要先进操作数栈)。
    基于寄存器与基于栈的虚拟机

    ART的GC优化

    Dalvik:Dalvik虚拟机的内存管理并不好,有三大问题:
    1.GC时挂起所有进程,查找活动对象时,会挂起所有的线程,stop the world,引起中断和阻塞
    2.内存碎片化严重(标记清除)
    3.缺少大块的连续空间

    ART:
    1.划分了四个堆空间(参考G1分堆垃圾回收器),各自的回收策略不同:
    永不回收:ImageSpace
    Sticky Mark Sweep:AllocationSpace
    Partial Mark Sweep:AllocationSpace、Large Object Space
    (力度最大)Mark Sweep:AllocationSpace、Large Object Space、ZygoteSpace
    其中,Large Object Space是不连续的空间。
    2.可以并行处理GC(过程参考CMS并发标记扫描垃圾回收器):
    GC过程分成多个子任务,交给线程池处理,只在标记脏数据时stop the world。
    并行GC基本过程:
    --获取堆锁
    --并行标记
    --释放堆锁
    --挂起ART,标记脏数据(并行标记期间被修改的)
    --恢复ART
    --获取堆锁
    --回收内存
    --释放堆锁

    ART不一定会并行GC,如果是分配内存时发现内存不足,执行GC_FOR_ALLOC,不能并行,最耗时;如果分配堆后,剩余空间低于阈值,并行GC。
    3.定期压缩活动对象,避免内存碎片化(标记整理)

    ART运行时垃圾收集(GC)过程分析
    【原创】【Android】揭秘 ART 细节 ---- Garbage collection

    相关文章

      网友评论

          本文标题:Android部分技术点目录(3)

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