Learning


  • 首页

  • 归档

  • 标签

  • 关于

Android PackageManager Service 学习笔记

发表于 2017-02-13

PMS 主要包含三部分的内容

  • 提供一个能够根据Intent 匹配找到Activity,Provider, Service ,即通过intent来找到具体的组件
  • 进行权限的检查,当应用程序需要调用一个需要权限的函数的时候,系统能够判断当前调用者是否具有改该权限。
  • 提供安装删除应用程序的接个口

PMS服务运行时使用两个目录下的xml文件保存相关的包管理信息,
第一个目录是/system/etc/premissions该目录下的xml用于permission的管理,具体包含两个,第一个是定义系统中都包含了那些featrue, 应用程序可以在AndroidManifest.xml中使用 use-featrue 声明程序需要哪些freatrue
例如:
该标签表示应用程序需要使用这个功能,如果required = true, 但是设备上没有wifi,那么程序将无法安装

设备都包含了那些feature就是在该目录下声明的,如果用户给系统添加了GPS或者蓝牙的功能,则必须在该目录下声明相关的xml文件来告诉framework。

该目录下还有一个platform.xml文件,该文件为一些特别的uid和gid分配了一些默认的权限,

第二个目录是/data/system/packages.xml,该文件保存了所有安装程序的基本信息,类似注册表。
下面是从文件里面拷贝出来的一段

<package name="xxxxxxx.xxx" codePath="/data/app/xxxxxxx" nativeLibraryPath="/data/app/com.tools.freereminder-1/lib" primaryCpuAbi="armeabi-v7a" flags="4767300" ft="15a1c7833a0" it="15a1c783548" ut="15a1c783548" version="170208" userId="10223">
    <sigs count="1">
        <cert index="12" key="xxxxxx" />
    </sigs>
    <perms>
        <item name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
        <item name="android.permission.WRITE_SETTINGS" />
        <item name="android.permission.SYSTEM_ALERT_WINDOW" />
        <item name="android.permission.RECEIVE_BOOT_COMPLETED" />
        <item name="android.permission.GET_TASKS" />
        <item name="android.permission.INTERNET" />
        <item name="android.permission.READ_EXTERNAL_STORAGE" />
        <item name="android.permission.ACCESS_COARSE_LOCATION" />
        <item name="android.permission.READ_PHONE_STATE" />
        <item name="android.permission.ACCESS_NETWORK_STATE" />
        <item name="android.permission.DISABLE_KEYGUARD" />
        <item name="android.permission.GET_ACCOUNTS" />
        <item name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <item name="android.permission.VIBRATE" />
        <item name="android.permission.ACCESS_WIFI_STATE" />
        <item name="com.android.launcher.permission.INSTALL_SHORTCUT" />
        <item name="android.permission.WAKE_LOCK" />
    </perms>
    <proper-signing-keyset identifier="322" />
    <signing-keyset identifier="322" />
</package>

PMS 在启动的时候,会从这两个目录中解析相关的xml文件,从而建立一个包信息树,应用程序可以间接从这个信息树中查询所有所需的包信息。

除了PMS外,还有两个辅助系统服务用于程序安装,一个是DefaultContainerService,该服务主要把安装程序复制到程序目录中;另一个是installer服务,该服务实际上不是一个binder,而是一个socket客户端,PMS直接和Socket客户端交互,Socket的服务端主要完成程序文件的解压工作及数据目录的创建,比如从APK文件中提取出dex文件,删除delvik-cache目前下的dex文件,创建程序专属的数据目录。

就像所有的操作系统一样,Android中的程序文件也由相关的程序文件组成,这些程序文件可以分为三个部分。
第一部分:程序文件,所有的系统程序文件保存在/system/app下,所有第三方的应用程序保存在/data/app下。/data/dalvik-cache目录保存了程序中执行的代码,PMS会从APK中提取出dex保存在该目录下,以便以后能快速的运行。
第二部分:framework 库文件,这些库文件位于/system/framework/ 目录下,库文件类型是apk或者jar,系统开机之后,dalvik虚拟机会加载这些库文件,而在PMS启动的时候如果这些jar或者APK还没有被转换为dex文件,PMS会将这些库文件转为dex文件,并且保存到/data/dalvik-cache目录下。(目前为止,我将dex导出之后,从反编译工具去打开这个dex文件,提示不是一个dex文件)
第三部分:程序自身的数据文件.

系统安装一个apk的过程分析

入口方法在于PMS的installPackageAsUser 方法。

在方法的开始会进行一系列的权限检查,然后相关的信息封装成一个message 通过自定义的packagehandler 发送出去,关键代码如下

final File originFile = new File(originPath);
final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile);
final Message msg = mHandler.obtainMessage(INIT_COPY);
final VerificationInfo verificationInfo = new VerificationInfo(
        null , null, -1, callingUid);
final InstallParams params = new InstallParams(origin, null, observer,
        installFlags, installerPackageName, null, verificationInfo, user,
        null, null,null);
params.setTraceMethod("installAsUser").setTraceCookie(System.identityHashCode(params));
msg.obj = params;
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "installAsUser",
        System.identityHashCode(msg.obj));
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
        System.identityHashCode(msg.obj));
mHandler.sendMessage(msg);

处理消息的代码可以在PackgetHanlder当中找到,定义在doHandler当中,之前发送的消息类型是INIT_COPY。

处理的代码为下面一段

HandlerParams params = (HandlerParams) msg.obj;
                int idx = mPendingInstalls.size();
                if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
                // If a bind was already initiated we dont really
                // need to do anything. The pending install
                // will be processed later on.
                if (!mBound) {
                    Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
                            System.identityHashCode(mHandler));
                    // If this is the only one pending we might
                    // have to bind to the service again.
                    if (!connectToService()) {
                        Slog.e(TAG, "Failed to bind to media container service");
                        params.serviceError();
                        Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
                                System.identityHashCode(mHandler));
                        if (params.traceMethod != null) {
                            Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, params.traceMethod,
                                    params.traceCookie);
                        }
                        return;
                    } else {
                        // Once we bind to the service, the first
                        // pending request will be processed.
                        mPendingInstalls.add(idx, params);
                    }
                } else {
                    mPendingInstalls.add(idx, params);
                    // Already bound to the service. Just make
                    // sure we trigger off processing the first request.
                    if (idx == 0) {
                        mHandler.sendEmptyMessage(MCS_BOUND);
                    }
                }
                break;

首先会去检查一个mBound的状态,这个状态表示和系统service的connect结果,PackageHanlder在内部定义了一个方法,connectToService(), 这个service是DefaultContainerService. 在这个类的说明上可以知道这个类主要是做了关于移动存储设备上文件检查和复制的工作。如果这个时候已经成功绑定了服务,那么可以将安装的请求添加到 mPendingInstalls 这个集合当中去。

接下来发送的消息类型是MSC_BOUND
在这个消息的处理中还是先判断绑定服务的状态,如果判断当前的状态是没有连接,那么这个时候调用prams的serviceError方法,表示安装失败,这个时候也会清空 mPendingInstalls
如果这个时候服务绑定成功,并且mPendingInstalls中有安装请求,这个时候会从集合中拿到第一个安装请求参数,然后开始调用startCopy方法

final boolean startCopy() {
        boolean res;
        try {
            if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
            if (++mRetries > MAX_RETRIES) {
                Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
                mHandler.sendEmptyMessage(MCS_GIVE_UP);
                handleServiceError();
                return false;
            } else {
                handleStartCopy();
                res = true;
            }
        } catch (RemoteException e) {
            if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
            mHandler.sendEmptyMessage(MCS_RECONNECT);
            res = false;
        }
        handleReturnCode();
        return res;
    }

里面加了重试的逻辑,如果重试之后还是失败,那么将进行安装失败通知,核心copy的逻辑在handleStartCopy,这个方法是一个抽象方法,具体的实现看InstallParams。

// If we're already staged, we've firmly committed to an install location
       if (origin.staged) {
           if (origin.file != null) {
               installFlags |= PackageManager.INSTALL_INTERNAL;
               installFlags &= ~PackageManager.INSTALL_EXTERNAL;
           } else if (origin.cid != null) {
               installFlags |= PackageManager.INSTALL_EXTERNAL;
               installFlags &= ~PackageManager.INSTALL_INTERNAL;
           } else {
               throw new IllegalStateException("Invalid stage location");
           }
       }

方法之中首先开始检查安装的位置,如果找到不到将抛出异常。
在检查完安装位置之后,开始安装的的空间是否足够,如果不够,系统会尝试清除一些缓存,再来判断是否可以安装。

如果在上面的检查当中没有出现问题,那么接下来获取安装的位置,并且在这个过程中判断安装是否有版本的回退,如果这个情况也没有,那么将会再设置一次installFlags.

接下来会去检查package verification 是否开启了,如果没有开启将会调用远程服务开始拷贝文件的过程。
关于拷贝的过程可以看InstallArgs其中一个子类的实现,FileInstallArgs, copyApk的真正过程在doCopyApk方法当中。

在doCopyApk的方法当中,首先调用了

File tempDir = mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);

这个方法用来指定res和code存放的临时目录,在这个步骤中创建了一些临时文件,之所以这么做是因为内部文件系统会为临时文件分配内存,提高效率。

imcs.copyPackage(origin.file.getAbsolutePath(), target);

这个imcs的实现在DefaultContainerService当中,安装apk的时候第一步就是检查到这个service的连接。

所以实际上imcs就是 IMediaContainerService.Stub的一个引用,copyPackage 之中又调用了copyPackageInner方法,方法的代码如下

private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target)
        throws IOException, RemoteException {
    copyFile(pkg.baseCodePath, target, "base.apk");
    if (!ArrayUtils.isEmpty(pkg.splitNames)) {
        for (int i = 0; i < pkg.splitNames.length; i++) {
            copyFile(pkg.splitCodePaths[i], target, "split_" + pkg.splitNames[i] + ".apk");
        }
    }
    return PackageManager.INSTALL_SUCCEEDED;
}

在copyFile这个方法当中看到了一个熟悉的名字“base.apk”,我们在/data/app/包名下同样有这个文件名的存在,在这里就完成了apk的拷贝。

接下来就是拷贝二进制文件的过程了。

C++动态内存

发表于 2017-02-13

C++ 支持动态分配对象,动态分配对象的生存周期和他们在哪里创建的无关,这有当显示的释放时,这些对象才会被销毁。

静态内存用来保存局部static变量,类static的成员和任何定义在函数之外的变量。栈内存用来保存定义在函数内的非static对象。分配在静态内存或者栈内存中的对象由编译器自动创建和销毁,对于栈对象,仅在其定义的程序块运行时才存在,static对象在使用前被分配,在程序结束的时候销毁。

除了静态内存和栈内存,每个程序还拥有一个内存池,这部分内存被称作自由空间或者堆,程序用堆来存储动态分配的对象,动态对象的生存周期由程序来控制,也就是说当动态对象不再使用的时候,我们的代码必须显示的销毁

动态内存和智能指针
在C++中,动态内存的管理是通过一对运算符来实现的,
new: 在动态内存中为对象分配空间并返回一个指向该对象的指针,可以选择对对象进行初始化,
delete: 接受一个动态对象指针,销毁该对象,并释放与之关联的内存。

新的标准库提供了两种智能指针,两种智能指针的区别在于管理底层指针的方式,shared_ptr 允许多个指针指向同一个对象,unique_str 则是 “独占”所指向的对象.

shared_ptr 的拷贝和赋值
当进行拷或者赋值操作的时候,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。
可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数,当我们通过任何手段导致指向同一个对象的shared_ptr增加时都会导致引用计数器的递增。当改变了shared_ptr指向的对象,或者shared_ptr被销毁,计数器会递减。一旦一个shared_ptr的计数器为0,它就会自动释放自己所管理的对象。

动态生存使用的目的

  • 程序自己不知道需要使用多少对象
  • 程序不知道所需对象的准确类型
  • 程序需要在多个对象间共享数据

使用new动态分配和初始化对象
建议的做法是对动态分配的对象进行初始化

动态创建的对象的时候,任然需要保持构造参数的匹配。

一个动态分配的const对象必须进行初始化,对于定义了默认构造函数的类型,其const对象可以隐式的初始化,而其他的类型必须显式的初始化,

内存耗尽
在默认情况下如果new不能分配所要求的内存空间,它会抛出一个类型为bad_alloc的异常。
但是可以改变new 的使用方式来变异抛出异常

int* p = new (nothrow) int;

这个时候如果分配失败,将返回一个空指针。

这种形式的new称为定位new,定位new表达式将允许向new传达额外的参数

释放内存
delete表达式接受一个指针,销毁给定指针指向的对象,释放对应的内存。

传递给delete的指针必须只向动态分配的内存,或者是一个空指针。释放一块非new分配的内存其行为是未定义的。通常情况下编译器不能分辨一个指针指向的内存是静态内存还是动态内存,所以这些问题在编译的时候很难被发现。

在指针delete之后,指针就成了悬空指针,即指向一块曾经保存数据但是现在已经无效的内存的指针。如果我们还需要继续使用这个指针,可以先将其值设为nullptr

当两个指针指向同样的内存,delete了其中一个,此时另外一个也将变得无效。

shared_ptr和new结合使用
必须使用直接初始化的方式来初始化一个智能指针,同时不能进行内置指针到隐智能指针的隐式准换。

shared_ptr<int> p1 = new int(1034); // 错误的用法
shared_ptr<int> p2(new int(1024)); // 正确的用法

默认情况下用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用delete释放它所关联的对象。

当将一个shared_ptr绑定到一个普通指针的时候,我们就将内存管理的责任交给了这个shared_ptr,一旦这样做了,就不应该在使用再使用内置指针来访问shard_ptr所指向的内存了。

考虑下面一段程序:

void process(shared_ptr<int> ptr) {

} // 离开了这个方法之后,ptr所指向对象的引用计数器会 -1;

调用

int* x = new int(88);
process(share_ptr<int>(x)); 
int xValue = *x;

当执行完了了process之后,再去访问x实际上会造成未知的结果,因为调用了process方法的时候,我们构建了一个智能指针对象,这个时候为对象的引用计数器 +1,但是离开了process方法之后,智能指针所指对象的引用计数器会 -1,此时对象的引用数最终为0,所以其指向的对象会被释放掉,因此x 这个时候就变成了悬空指针,xValue 去访问x所指的内存就会出现未知的操作。

从上面的例子也可以看出来,智能指针的引用计数和内置指针是完全分开的,只要智能指针计算的引用计数为零,这个指针指向的内存就会被释放掉,而并没有在乎是否有内置的指针还指向这块内存

智能指针类型定义了一个名为get的函数,它返回一个内置指针,指向智能指针管理的对象,此函数是为了这样一种情况而设计:我们需要向不能使用智能指针的代码传递一个内置指针,使用get返回指针的代码不能delete此指针。

get用来将指针的访问权限传递给代码,只有在确保代码不会delete指针的情况下才能使用get,特别是,永远不要用get初始化一个智能指针或者为另一个智能指针赋值(这是为了避免将一块动态内存绑定到多个独立创建的智能指针上,因为这些独立的智能指针不知道彼此的存在,很可能造成A释放了B正在使用的内存)。

智能指针和异常
函数的退出有两种可能,正常处理结束或者发生了异常,无论哪种情况,局部对象都会被销毁。与之相对的是,发生异常时,我们直接管理的内存不会被直接释放掉。

void f() {
    int* x = new int(0);
    //抛出异常,切未在f中处理
    delete x;
}

在上面的函数中,在new之后,delete之前出现了异常,且异常尚未在f中捕获,则内存永远都无法释放掉,因为在函数f之外没有指针指向这块内存,因此也就无法释放了。

使用自己的释放操作
默认情况下,shared_ptr在销毁一个对象的时候,它对它管理的指针进行delete操作,但是我们可以设定自己的释放对象操作,来代替默认的操作。例如我们需要释放一个数据库的连接connection,当我们创建创建一个shard_ptr的时候,可以传递一个指向删除器函数的参数,如下

void end_connection(connection* con) {
    //do something to release connection
}  

//调用
shared_ptr<connection> con_p(connction,end_connection);

这样当con_p被销毁的时候,它不会对保存的指针执行delete操作,而是调用end_connection函数,那么不管con_p所处的方法是正常结束还是发生了异常,p都会被销毁,从而保证了连接被关闭。

总结下来,使用智能指针有以下需要注意的地方

  1. 不使用相同的内置指针初始化多个智能指针
  2. 不delete get()返回的内置指针
  3. 不使用get 初始化或者reset另一个智能指针
  4. 如果使用了get返回的内置指针,当最后一个智能指针被销毁后,这个内置指针也将变得无效
  5. 如果使用智能指针管理的资源不是new 分配的内存,那么手动传递给它一个删除器

智能指针之Unique_ptr

一个unique_ptr拥有它所指向的对象,并且某个时候只能有一个unique_ptr指向一个给定的对象,因此unique_ptr不支持普通的拷贝或赋值操作,和shared_ptr类似,初始化unique_ptr必须采用直接初始化的形式。

调用unique_ptr的release方法会切断unique_ptr和它原来管理对象之间的联系,通常release返回的指针被用来初始化另一个智能指针或者给另一个智能指针赋值,我们拿到这个内置指针之后,就由我们来负责指针的销毁。

Andriod中的资源管理

发表于 2017-02-09

Android的安装包本质上是一个zip压缩文件,我们可以使用解压软件打开。
其中的resources.arsc 是一个二进制格式的文件,与二进制的xml完全不同,appt在对资源进行编译的时候会为每一个资源分配唯一的id,程序在执行的时候会根据这些id来读取特定的资源,而resources.arsc文件正是包含了所有id资源值得一个数据集合,在该文件中,如果某个id对应的资源是string或者数值,那么该文件会直接包含对应的值,如果id对应的资源是某个layout或者drawable资源,那么该文件会存入对应资源的地址。

安装后的目录如下

  • /data/app 安装之后,会有一个以报名命名的文件夹在里面,其中base.apk就是用户安装的apk。系统自带的应用在/system/app下面。
  • /data/dalvik-cache, 每个apk里面都有class.dex,安装之后,所有的dex文件都会放到该目录下面,这样用户在启动APP的时候就可以快速的读取dex文件,而不用解压,在早期版本中,会对dex进行优化,最后生成的odex文件也在这个目录下面,任何程序都可以读写该目录,这就为类的动态加载提供了可能,实际上每个app的dex .

styleable, style, attr, theme

styleable 一般和attr联合使用,用于定义一些属性。从AttributeSet对象中获取这三个属性的时候可以,可以传入R.styleable.xxx 这个参数实际上会被编译成一个int []. 数组的内容正是所包含的attrde id。

AttributeSet和TypeArray

AttributeSet 代表了视图属性的集合, TypedArray 类又是对ArributeSet数据的抽象。context.obtainStyledAttribute() ,该函数的内部实现正是通过遍历set中的每一个属性,找到用户感兴趣的属性,然后把值和属性经过重定位返回一个TypedArray对象。

TypedArray 中的内部mValue(类型为一个int 数组)起到了一个内部缓冲的作用,mData 包含了styleable中所有的属性值, 其长度为 styleable 中属性的个数乘以 AssetManager.STYLE_NUM_ENTRIES,AssetManager.STYLE_NUM_ENTRIES的值为6,它实际上表示有6种数据在mData这个数组当中。

static final int STYLE_NUM_ENTRIES = 6;
static final int STYLE_TYPE = 0;
static final int STYLE_DATA = 1;
static final int STYLE_ASSET_COOKIE = 2;
static final int STYLE_RESOURCE_ID = 3;
static final int STYLE_CHANGING_CONFIGURATIONS = 4;
static final int STYLE_DENSITY = 5;

比如说我先有一个int 类型的属性值,那么它 实际上在mData中占据了6个 存储位置,从 0 -5 位置上的值分别对应上面的类型。再看如何获取string的存储的,核心方法是调用了 TypedArray的 loadStringValueAt 方法。

 public String getString(@StyleableRes int index) {
        if (mRecycled) {
            throw new RuntimeException("Cannot make calls to a recycled instance!");
        }

        index *= AssetManager.STYLE_NUM_ENTRIES;
        final int[] data = mData;
        final int type = data[index+AssetManager.STYLE_TYPE];
        if (type == TypedValue.TYPE_NULL) {
            return null;
        } else if (type == TypedValue.TYPE_STRING) {
            return loadStringValueAt(index).toString();
        }

        final TypedValue v = mValue;
        if (getValueAt(index, v)) {
            final CharSequence cs = v.coerceToString();
            return cs != null ? cs.toString() : null;
        }

        throw new RuntimeException("getString of bad type: 0x" + Integer.toHexString(type));
    }


首页要做的就是找到index在mData中正确的位置,这里将index*=6,如果这里存放的本来就是一个sting那么使用loadStringValueAt,如果不是,那么将调用 TypedValue的 coerceTOsTRING();

再来看 loadStringValueAt方法

private CharSequence loadStringValueAt(int index) {
    final int[] data = mData;
    final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
    if (cookie < 0) {
        if (mXml != null) {
            return mXml.getPooledString(
                data[index+AssetManager.STYLE_DATA]);
        }
        return null;
    }
    return mAssets.getPooledStringForCookie(cookie, data[index+AssetManager.STYLE_DATA]);
}

首先尝试获取字符串的cookie,cookie如果有效的话将会从mXml(mXml是用于解析二进制xml的对象)中去加载,无效的话将调用AssetManager的 getPooledStringForCookie 方法,

获取资源
获取资源通常需要Resource对象,在构建Resource对象的过程中需要一个AssertManager。

  public AssetManager() {
    synchronized (this) {
        if (DEBUG_REFS) {
            mNumRefs = 0;
            incRefsLocked(this.hashCode());
        }
        init(false);
        if (localLOGV) Log.v(TAG, "New asset manager: " + this);
        ensureSystemAssets();
    }
}

init 是一个navtive的方法,它会尝试去加载/system/framework-res.apk, 这样APP就可以使用系统提供的资源。

通过尝试去跟Resource. getXXX方法, 最终都是调用了AssetManager的方法。
而AssetManager最终又是采用JNI的方式来获取资源。

static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz,
                                                       jint ident,
                                                       jshort density,
                                                       jobject outValue,
                                                       jboolean resolve)
{
    AssetManager* am = assetManagerForJavaObject(env, clazz);
    if (am == NULL) {
        return 0;
    }
    const ResTable& res(am->getResources());
    Res_value value;
    ResTable_config config;
    uint32_t typeSpecFlags;
    ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);
#if THROW_ON_BAD_ID
    if (block == BAD_INDEX) {
        jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
        return 0;
    }
#endif
    uint32_t ref = ident;
    if (resolve) {
        block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config);
#if THROW_ON_BAD_ID
        if (block == BAD_INDEX) {
            jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
            return 0;
        }
#endif
    }
    return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config) : block;
}

首先通过获取ResTable ,让后从resTable 当中去找到相应的资源,这里的ResTable实际上就是apk文件解压出来之后的resources.arsc。

Framework资源

加载和读取
系统资源是在zygote 进程启动的时候加载的,并且只有在加载系统资源完成之后才开始启动其他的应用进程,从而实现其他应用进程共享系统资源的目标,

方法的调用栈 ZyogteInit.main() -> preload() -> preloadResources() , 系统加载完这些资源之后会在ResoucesImpl通过集合缓存起来。

// Information about preloaded resources.  Note that they are not
// protected by a lock, because while preloading in zygote we are all
// single-threaded, and after that these are immutable.
private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables;
private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
        = new LongSparseArray<>();
private static final LongSparseArray<android.content.res.ConstantState<ComplexColor>>
        sPreloadedComplexColors = new LongSparseArray<>();

在ResouscesImpl 内有一个成员变量mPreloading , 这个值只会在 startPreloading()和 finishPreloading 这两个方法当中被改变,而这两个方法又只会在Zygote进程中被调用,所以当mPreloading为true的时候, 是Zygote 进程正在加载系统资源。所以在ResourcesImpl的cacheDrawble方法当中,可以看到

if (mPreloading) {
        final int changingConfigs = cs.getChangingConfigurations();
        if (isColorDrawable) {
            if (verifyPreloadConfig(changingConfigs, 0, value.resourceId, "drawable")) {
                sPreloadedColorDrawables.put(key, cs);
            }
        } else {
            if (verifyPreloadConfig(
                    changingConfigs, LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) {
                if ((changingConfigs & LAYOUT_DIR_CONFIG) == 0) {
                    // If this resource does not vary based on layout direction,
                    // we can put it in all of the preload maps.
                    sPreloadedDrawables[0].put(key, cs);
                    sPreloadedDrawables[1].put(key, cs);
                } else {
                    // Otherwise, only in the layout dir we loaded it for.
                    sPreloadedDrawables[mConfiguration.getLayoutDirection()].put(key, cs);
                }
            }
        }
    }

如果mPreloading 是true,那么这个时候是在执行ZygoteInit进程,所以这些数据会被缓存到静态变量当中去。这里加载的Framework的资源,加载的仅仅只是一小部分,对于那些非”预装载”的系统资源则不会缓存到静态集合变量中,在这种情况下,如果应用京城需要一个非预装载的资源,则会在各个进程中保持一个资源的缓存。


关于 动态加载皮肤框架的实现

获取新的资源对象 Resources ?
简单的概括一下就是通过AssetManager 来构建一个新的Resouce对象。

AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, skinPkgPath);
Resources superRes = context.getResources();
Resources skinResource = new                        Resources(assetManager,superRes.getDisplayMetrics(),superRes.getConfiguration());

这样既可以获取到一个指向新资源的 Resource 对象。

在获取新的资源对象的时候,先根据现有资源的id来获取资源的字符串名字,就是我们开发的时候给资源的命名,然后根据命名来在新的 Resource对象中 获取相应的资源。

String resName = context.getResources().getResourceEntryName(resId);
int trueResId = mResources.getIdentifier(resName, "color", skinPackageName);
int trueColor = 0;
try{
    trueColor = mResources.getColor(trueResId);
}catch(NotFoundException e){
    e.printStackTrace();
    trueColor = originColor;
}
return trueColor;

拿到资源之后替换即可。

如何获取需要更换皮肤的View.
在作者的实现如下,
在布局文件中为每一个需要替换皮肤的View 添加了一个属性 skin:enable=”true” , 同时为LayoutInflator指定自定义的Factory, 在LayoutInflator解析这个布局文件的时候,会调用Factory的onCreateView 方法,这样可以在这里获取到所有添加了 skin:enable=”true” 这个标签的View.

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
    mSkinInflaterFactory = new SkinInflaterFactory();
    getLayoutInflater().setFactory(mSkinInflaterFactory);
}

自定义Factory的核心方法如下

@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
    // if this is NOT enable to be skined , simplly skip it 
    boolean isSkinEnable = attrs.getAttributeBooleanValue(SkinConfig.NAMESPACE, SkinConfig.ATTR_SKIN_ENABLE, false);
    if (!isSkinEnable){
            return null;
    }
    View view = createView(context, name, attrs);
    if (view == null){
        return null;
    }
    parseSkinAttr(context, attrs, view);
    return view;
}

解决以上两个问题,换肤就好实现了。

Google MVVM 示例代码分析

发表于 2017-02-03

项目的代码位于 github 上

项目中采用了MVVM方式进行开发,与之前的MVP项目模式相比,直接去掉了MVP模式中的Presenter,数据加载的操作直接用ViewModel中开始. 在获取到了数据之后,通过databiding 来完成数据的展示。

在to-do中,有几个部分,下面是主界面的分析内容。

在TasksViewModel 这个类当中,封装了数据所有的操作,包括数据的加载,判空(isEmpty),清除, 添加过滤条件。

同时应该注意到TasksViewModel 继承了 BaseObservable 这个对象,BaseObservable 最终实现了Observable 接口, 这个接口提供了让数据绑定的UI接受数据变化的通知。
同时 BaseObservable 还有其他的类型的子类,例如 ObservableField 等。

在 TasksViewModel 我们也可以找到 BaseObservable 类型的属性,通过这些属性,可以监听到具体属性值 的变化。

除了属性值之外,还可以将方法转可以监听的对象,做法就是给方法加上 @Bindable 注解。
下面是这个注解的说明

The Bindable annotation should be applied to any getter accessor method of an
{@link Observable} class. Bindable will generate a field in the BR class to identify
the field that has changed.

它用来修饰任意一个get method,被这个注解修饰的方法会在 BR.java这个类中生成对应的属性,例如如果定义了 String getName()方法,那么就会在BR中添加一个name的常量字段。当数据发生变化我们需要通过getName来获取最新的数据的时候,调用 BaseObserverable 的 notifyPropertyChanged(BR.name),之后getName将再次被调用。

同时也可以看到这个被 @Bindable 注解修饰的方法,可以在 布局文件中直接通过 xxx.name 的方式来访问。

上面说到数据加载操作也是在TasksViewModel中完成的,在loadTasks方法中 调用了 mTasksRepository 的 getTasks, 然后在callback中将数据 添加到了 items当中去,
item 是一个 ObservableList

public final ObservableList<Task> items = new ObservableArrayList<>();

当调用了 items.addAll(tasksToShow); 方法之后,数据就被绑定到界面上去了。

下面跟踪一下,数据绑定是如何实现的。

在TasksViewModel当中,有一个 ObservableBoolean类型的成员变量dataLoading.在这个类中提供了一个set方法,在改变了value的值之后,它调用了notifyChange();

notifyChange 是一个定义在其父类 BaseObservable 中的一个同步的方法, 这个方法当中简单调用了PropertyChangeRegistry 这个类的 notifyCallbacks 方法 ,进入到notifyCallbacks 方法,实际上这个方法又是定义在其父类CallbackRegistry之中,通过CallbackRegistry的说明可知,这个类是用来存放callback和进行各种回调的,但是CallbackRegistry对于回调的处理实际上只是对回调的集合进行了管理,它通过递归的方式来通知集合中所有的回调,这里之所以采用递归的方式,文档之中给出的说明是避免在堆上进行临时状态的分配(但是采用递归需要每次都在栈上分配内存,并且堆上同样会有内存的分配,这里还不清楚),既然CallbackRegistry只是集合回调的管理,那么真正的回调应该如何处理实际上 是通过其内部类 NotifierCallback 来完成。

/**
 * Class used to notify events from CallbackRegistry.
 *
 * @param <C> The callback type.
 * @param <T> The notification sender type. Typically this is the containing class.
 * @param <A> An opaque argument to pass to the notifier
 */
public abstract static class NotifierCallback<C, T, A> {
    /**
     * Called by CallbackRegistry during
     * {@link CallbackRegistry#notifyCallbacks(Object, int, Object)}} to notify the callback.
     *
     * @param callback The callback to notify.
     * @param sender The opaque sender object.
     * @param arg The opaque notification parameter.
     * @param arg2 An opaque argument passed in
     *        {@link CallbackRegistry#notifyCallbacks}
     * @see CallbackRegistry#CallbackRegistry(CallbackRegistry.NotifierCallback)
     */
    public abstract void onNotifyCallback(C callback, T sender, int arg, A arg2);
}

通过实际上在CallbackRegistry 处理回调的时候就是调用了其 onNotifyCallback来处理。

所以在CallbackRegistry 的说明中可以看到 一句话 :A subclass of CallbackRegistry.NotifierCallback must be passed to the constructor to define how notifications should be called

简单说就是由 NotifierCallback的子类来处理notification。

回到 PropertyChangeRegistry 当中看 NOTIFIER_CALLBACK

private static final CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback, Observable, Void> NOTIFIER_CALLBACK = new CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback, Observable, Void>() {
    @Override
    public void onNotifyCallback(Observable.OnPropertyChangedCallback callback, Observable sender,
            int arg, Void notUsed) {
        callback.onPropertyChanged(sender, arg);
    }
};

在接收到通知之后,实际上会调用Observable.OnPropertyChangedCallback的 onPropertyChanged 方法。

从上面的分析可以看来,CallbackRegistry 实际上是一个事件分发中心和订阅中,所有的事件回调都是通过CallbackRegistry来处理,但是真正对事件的处理,却是通过自定义的 NotifierCallback来完成的。

在android的databinding中,同样是通过这样一套机制来工作的,在ViewDataBinding中,定义了WeakPropertyListener,通过重写 onPropertyChanged 方法完成对数据的重新绑定。

回到 TasksFragBinding.java 这个类是tasks_frag_xml这个文件生成的。
在其中定义了一个变量, mViewmodel ,因此在这个类中,也为我们生成了一个setViewmodel 方法,在这个方法当中,完成了对数据的mViewmodel 的赋值,然后调用了notifyPropertyChanged 方法,这个方法在前面提到过,定义BaseObservable 当中,所以可以简单的推断 ViewDataBinding 也是继承于BaseObservable。

通过mViewmodel 同样可以推断出,如果我们只是修改了 mViewmodel 的内部属性是无法导致试图被刷新的(已经验证),所以在TaskViewModel 这个类中,我们可以看到,在定义了 private final ObservableField mTaskObservable = new ObservableField<>();之后有这么一段代码

mTaskObservable.addOnPropertyChangedCallback(new OnPropertyChangedCallback() {
        @Override
        public void onPropertyChanged(Observable observable, int i) {
            Task task = mTaskObservable.get();
            if (task != null) {
                title.set(task.getTitle());
                description.set(task.getDescription());
            } else {
                title.set(mContext.getString(R.string.no_data));
                description.set(mContext.getString(R.string.no_data_description));
            }
        }
    });

需要自己手动去添加一个监听器来数据的更新。

参照这个例子,如果需要一个更新了成员变量也会刷新对应试图的对象,应该这么定义

public class Task {
    public final ObservableField<String> title = new ObservableField();
    public final ObservableField<String> content = new ObservableField();

    public Task(String title, String content) {
        this.title.set(title);
        this.content.set(content);
    }

    public void setContent(String content) {
        this.content.set(content);
    }
}

对应的布局文件如下

<?xml version="1.0" encoding="utf-8"?>
<layout  xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="task"
            type="com.devtom.databindingtest.Task"/>
    </data>

    <RelativeLayout
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="com.devtom.databindingtest.MainActivity">

        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:text="@{task.title}" />

        <TextView
            android:id="@+id/content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="@{task.content}" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Change"
            android:onClick="change"
            android:layout_alignParentBottom="true"
            />
    </RelativeLayout>
</layout>

Activity 代码如下

public class MainActivity extends AppCompatActivity {
        private Task task;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            View view = this.findViewById(R.id.activity_main);
            ActivityMainBinding mainBinding = DataBindingUtil.bind(view);
            task = new Task("this is title","kai gong da ji");
            mainBinding.setTask(task);
        }

        public void change(View view) {
            task.setContent("this is chinese new year");
        }
}

当手动的去改变task的某一个属性的时候,也会去刷新对应的试图。

这里需要注意的是Task其中的两个成员变量可以定义为public的也可以定义为private 然后给他添加对应的private 方法。

Android中AIDL的理解

发表于 2017-01-28

首先定义

// IMyService.aidl
package com.devtom.myservice;

interface IMyService {
    void printLog(String message);
}

定义完成了之后,编译工具会自动帮我们生成接口相关的文件。
生成的代码文件如下

package com.devtom.myservice;

public interface IMyService extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.devtom.myservice.IMyService {
        private static final java.lang.String DESCRIPTOR = "com.devtom.myservice.IMyService";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.devtom.myservice.IMyService interface,
         * generating a proxy if needed.
         */
        public static com.devtom.myservice.IMyService asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.devtom.myservice.IMyService))) {
                return ((com.devtom.myservice.IMyService) iin);
            }
            return new com.devtom.myservice.IMyService.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_printLog: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    this.printLog(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.devtom.myservice.IMyService {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public void printLog(java.lang.String message) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(message);
                    mRemote.transact(Stub.TRANSACTION_printLog, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_printLog = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public void printLog(java.lang.String message) throws android.os.RemoteException;
}

再来看看service相关的实现

public class MyRemoteService extends Service {

    private static final String TAG = "MyRemoteService";

    public static class MyServiceBinder extends IMyService.Stub {
        @Override
        public void printLog(String message) throws RemoteException {
            Log.e(TAG, "this is message from remote " + message);
        }
    }

    private MyServiceBinder binder;

    @Override
    public void onCreate() {
        super.onCreate();
        binder = new MyServiceBinder();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
}

客户端调用的代码如下

public class MainActivity extends Activity {

    private IMyService binder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this, MyRemoteService.class);
        bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE);
        this.findViewById(R.id.test).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    binder.printLog("this is message from client");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            binder = MyRemoteService.MyServiceBinder.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            binder = null;
        }
    };
}

目前按照个人理解

  1. MainActivity当中通过ServiceConnect 拿到的对象实际上是IMyService 当中的Proxy 对象的引用,之所以会有这个代理对象的存在,目前的理解是,系统为了方便我们调用,通过这个代理对象完成了调用远程服务数据的封装,所以我们在Proxy当中可以看到代理对printLog的实现:

    @Override
    public void printLog(java.lang.String message) throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            _data.writeString(message);
            mRemote.transact(Stub.TRANSACTION_printLog, _data, _reply, 0);
            _reply.readException();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
    }
    

在Proxy的实现中,主要是完成了对数据的封装,并没有实现其他的逻辑,完成数据的封装之后,然后调用了 mRemote的transact方法。

  1. Proxy 中成员变量 mRemote 所指向的对象是 IMyService.Stub ,因为Stub 是一个抽象类,所以最终mRemote指向的实例也就是IMyService.Stub 的实现类了

     @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_printLog: {
                data.enforceInterface(DESCRIPTOR);
                java.lang.String _arg0;
                _arg0 = data.readString();
                this.printLog(_arg0);
                reply.writeNoException();
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }
    

    在onTransact 方法当中实现了对数据的解析,将获取到的数据传给自己定义的方法当中去,最终调用了自己实现的方法。

3.通过这个例子,最重要的还是里面的Binder, 数据的传递,解析都是Binder这个对象来完成的。

知道AIDL 的套路之后,可以自己手写一个实现。

public class MyServiceBinderImpl extends Binder implements MyServiceInterface, IInterface{

    public static final String TAG  = "MyServiceBinderImpl";

    public static final String DESCRIPTOR = "com.devtom.myservice.MyServiceBinderImpl";

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION:
                reply.writeString(DESCRIPTOR);
                break;
            case Binder.FIRST_CALL_TRANSACTION + 0:
                data.enforceInterface(DESCRIPTOR);
                String strData = data.readString();
                printLog(strData);
                reply.writeNoException();
                break;
        }
        return super.onTransact(code, data, reply, flags);
    }

    @Override
    public void printLog(String message) {
        Log.e(TAG, "this message is " + message);
    }

    @Override
    public IBinder asBinder() {
        return this;
    }
}

这个实际上就是自己动手去解析数据。

调用了也很简单 ,

android.os.Parcel _data = android.os.Parcel.obtain();
           android.os.Parcel _reply = android.os.Parcel.obtain();
           try {
               _data.writeInterfaceToken(MyServiceBinderImpl.DESCRIPTOR);
               _data.writeString("this my own aidl implement");
               binder.transact(IMyService.Stub.TRANSACTION_printLog, _data, _reply, 0);
               _reply.readException();
           } catch (RemoteException e) {

           } finally {
               _reply.recycle();
               _data.recycle();
           }

此处的binder对象就是从ServiceConnection的onServiceConnected 方法参数中获取的,因为我们去掉了代理帮帮我们封装数据,所以这里就只能自己手动的去封装数据了。

其实从上面自己手动实现AIDL 的功能可以看出,我们新建的service只是为了方便我们自己调用,我们完全可以去掉了这些方法(printLog)在onTrasaction里面来实现各种功能,当然这个只是扯淡。

Android中的状态保存

发表于 2017-01-22

在Android中,当我们的Acitivity 被系统干掉的时候,会给我们一次保存数据的机会,我们通过可以通过这个机会来将需要的数据保存下来,然后在用户从新打开APP的时候,把这个数据恢复。

先来看一下Activity是怎么保存数据的

先来看一下onSaveInstanceState()方法

protected void onSaveInstanceState(Bundle outState) {
    outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
    Parcelable p = mFragments.saveAllState();
    if (p != null) {
        outState.putParcelable(FRAGMENTS_TAG, p);
    }
    getApplication().dispatchActivitySaveInstanceState(this, outState);
}

代码非常的简单,保存了3个对象的状态,分别是mWindow, mFragments和 Application 对象
mWindow的实际实现对象是PhoneWindow, 我们进入到 saveHierarchyState() 方法看看

    /** {@inheritDoc} */
@Override
public Bundle saveHierarchyState() {
    Bundle outState = new Bundle();
    if (mContentParent == null) {
        return outState;
    }

    SparseArray<Parcelable> states = new SparseArray<Parcelable>();
    mContentParent.saveHierarchyState(states);
    outState.putSparseParcelableArray(VIEWS_TAG, states);

    // Save the focused view ID.
    final View focusedView = mContentParent.findFocus();
    if (focusedView != null && focusedView.getId() != View.NO_ID) {
        outState.putInt(FOCUSED_ID_TAG, focusedView.getId());
    }

    // save the panels
    SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>();
    savePanelState(panelStates);
    if (panelStates.size() > 0) {
        outState.putSparseParcelableArray(PANELS_TAG, panelStates);
    }

    if (mDecorContentParent != null) {
        SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>();
        mDecorContentParent.saveToolbarHierarchyState(actionBarStates);
        outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
    }
    return outState;
}

首先是尝试去保存 mContentParent 的状态, 这个mContentParent 实际上就是在DecorView中 id 为 content的 ViewGroup, 我们setContetnView中添加的View就是放在了它的下面。mContentParent 是FrameLayout, 最终类型就是ViewGroup。先看看ViewGroup的父类 View怎么实现 saveHierarchyState .saveHierarchyState 调用了 dispatchSaveInstanceState,而这个方法是View中的方法,

protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
    if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
        mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
        Parcelable state = onSaveInstanceState();
        if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
            throw new IllegalStateException(
                    "Derived class did not call super.onSaveInstanceState()");
        }
        if (state != null) {
            // Log.i("View", "Freezing #" + Integer.toHexString(mID)
            // + ": " + state);
            container.put(mID, state);
        }
    }
}

实际上就是把onSaveInstanceState 放的返回值 保存了起来,在这里我们可以看到,这里是根据mID 也就是我们在布局文件为View指定的id来保存状态的,同时因为 SparseArray 是一个key ,value的键值对集合,所以这里,如果指定了相同的key,前面的key对应的view状态会被后来的覆盖掉,所以要尽量避免key的重复。

再来看 ViewGroup的 dispatchSaveInstanceState 方法。

@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
    super.dispatchSaveInstanceState(container);
    final int count = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < count; i++) {
        View c = children[i];
        if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
            c.dispatchSaveInstanceState(container);
        }
    }
}

ViewGroup 首先调用了View的dispatchSaveInstanceState 方法来保存自身的状态,然后调用了其子View 的dispatchSaveInstanceState 方法来保存 子view的 状态。这样最终的ViewTree的状态就被保存下来了。

Activity 把View的状态都存下来了之后,再开始存Fragment的状态
Fragment状态的保存实在FragmentManager的saveAllState()方法来完成的,在这个方法当中调用了
saveFragmentBasicState()方法,

 Bundle saveFragmentBasicState(Fragment f) {
    Bundle result = null;

    if (mStateBundle == null) {
        mStateBundle = new Bundle();
    }
    f.performSaveInstanceState(mStateBundle);
    if (!mStateBundle.isEmpty()) {
        result = mStateBundle;
        mStateBundle = null;
    }

    if (f.mView != null) {
        saveFragmentViewState(f);
    }
    if (f.mSavedViewState != null) {
        if (result == null) {
            result = new Bundle();
        }
        result.putSparseParcelableArray(
                FragmentManagerImpl.VIEW_STATE_TAG, f.mSavedViewState);
    }
    if (!f.mUserVisibleHint) {
        if (result == null) {
            result = new Bundle();
        }
        // Only add this if it's not the default value
        result.putBoolean(FragmentManagerImpl.USER_VISIBLE_HINT_TAG, f.mUserVisibleHint);
    }

    return result;
}

performSaveInstanceState 的代码如下

void performSaveInstanceState(Bundle outState) {
    onSaveInstanceState(outState);
    if (mChildFragmentManager != null) {
        Parcelable p = mChildFragmentManager.saveAllState();
        if (p != null) {
            outState.putParcelable(Activity.FRAGMENTS_TAG, p);
        }
    }
}

performSaveInstanceState onSaveInstanceState 方法,在这个方法里面我们可以保存自己想要保存的信息,之后又开始调用FragmentManamager 的 saveAllState()方法,就是递归调用了。跳出这个方法,接着看 saveFragmentViewState()

    void saveFragmentViewState(Fragment f) {
    if (f.mView == null) {
        return;
    }
    if (mStateArray == null) {
        mStateArray = new SparseArray<Parcelable>();
    } else {
        mStateArray.clear();
    }
    f.mView.saveHierarchyState(mStateArray);
    if (mStateArray.size() > 0) {
        f.mSavedViewState = mStateArray;
        mStateArray = null;
    }
}

关键的代码是 f.mView.saveHierarchyState(mStateArray);这个方法保存开始保存View的信息, 这个就是在开始分析的保存ViewTree那一部分的内容了。

在上面的内容保存完了还要保存当前已经添加的Fragment的信息,和回退栈的信息。

// Build list of currently added fragments.
   if (mAdded != null) {
       N = mAdded.size();
       if (N > 0) {
           added = new int[N];
           for (int i=0; i<N; i++) {
               added[i] = mAdded.get(i).mIndex;
               if (added[i] < 0) {
                   throwException(new IllegalStateException(
                           "Failure saving state: active " + mAdded.get(i)
                           + " has cleared index: " + added[i]));
               }
               if (DEBUG) Log.v(TAG, "saveAllState: adding fragment #" + i
                       + ": " + mAdded.get(i));
           }
       }
   }

   // Now save back stack.
   if (mBackStack != null) {
       N = mBackStack.size();
       if (N > 0) {
           backStack = new BackStackState[N];
           for (int i=0; i<N; i++) {
               backStack[i] = new BackStackState(this, mBackStack.get(i));
               if (DEBUG) Log.v(TAG, "saveAllState: adding back stack #" + i
                       + ": " + mBackStack.get(i));
           }
       }
   }

所以在Fragment保存的内容当中一共是当前active fragment的信息,已经添加的fragment 信息,和回退栈的信息,三部分

在回到Activity 的onSaveInstanceState 方法当中去,最后一步就是通知注册在Application当中的Activity 生命周期的回调。

onSaveInstanceState() 一般在onStop之前调用,再到ActivityThread的performStopActivity方法看看

final void performStopActivity(IBinder token, boolean saveState, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    performStopActivityInner(r, null, false, saveState, reason);
}

首先获取了了 一个 ActivityClientRecord 对象,然后在 performStopActivityInner 方法当中,根据saveState的值,会去调用 callCallActivityOnSaveInstanceState 方法

代码如下

 private void callCallActivityOnSaveInstanceState(ActivityClientRecord r) {
    r.state = new Bundle();
    r.state.setAllowFds(false);
    if (r.isPersistable()) {
        r.persistentState = new PersistableBundle();
        mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state,
                r.persistentState);
    } else {
        mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
    }
}

接着看 callActivityOnSaveInstanceState 方法,里面直接 调用了 activity.performSaveInstanceState(outState), 再回到Activity看performSaveInstanceState,

final void performSaveInstanceState(Bundle outState) {
    onSaveInstanceState(outState);
    saveManagedDialogs(outState);
    mActivityTransitionState.saveState(outState);
    storeHasCurrentPermissionRequest(outState);
    if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
}

调用了 onSaveInstanceState 这个就是前文分析的入口了,除了前文分析的保存的状态还保存了,dialog ,activity transaction, 权限相关的信息 ,这些信息都被存入了outState当中。而这个正是ActivityClientRecrod的成员变量之一,ActivityClientRecrod有最终被存在了 ActivityThread 的 mActivities 集合当中。

这里需要注意

现在保存的信息都分析完了,后面用户再回来的时候就需要将保存的信息再传给Activity,启动Acitivty在ActivityThread中的 performLaunchActivity 方法,
在这个方法当有如下代码

if (r.isPersistable()) {
    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
    mInstrumentation.callActivityOnCreate(activity, r.state);
}

这里最终将ActivityClientRecord对象中保存的 state 在传递给 activity的 onCreate 方法,这样就可以在onCreate的时候实现对上一个Activity对象状态的恢复。

C++中的静态

发表于 2017-01-20

通过添加static关键字将成员与类结合在一起而不是与特定的对象绑定在一起,这一点和java中static关键字是一样的,而且也可以使用访问修饰符修饰

静态成员函数不和任何对象绑定在一起,他们不包含任何this 指针,作为结果,静态成员函数不能成名成const的,而且不能再静态函数的内部使用this指针。

可以使用作用域运算法直接访问静态成员。例如在Account中定义了静态的rate()函数,可以使用Accout::rate()调用。

和其他的成员函数一样,我们既可以在类的内部也可以在类的外部定义静态成员函数,当在类的外部定义静态成员函数的时候不能重复static关键字。

因为静态数据库成员不属于类的任何一个对象,所以他们不是在创建类的对象的时候被定义的,这意味着他们不是由类的构造函数初始化的,而且一般来说我们不能在类的内部初始化静态成员,相反的,必须在类的外部定义和初始化静态成员,类似于全局变量,静态成员定义于任何函数之外,一旦定义就存于程序的整个生命周期。

下面是一个使用static的例子

#include <iostream>
#include <string>

using namespace std;

class MyStatic{

public:
    static void test();
    static string name;
};

void MyStatic::test() {
    cout<<"this is static test method"<<endl;
}

string MyStatic::name = "ssssss";
int main(void) {
    MyStatic::test();
    cout<<MyStatic::name<<endl;
    return 0;
}

静态成员可以用于某些场景而普通成员不能

  1. 静态成员不收不完全类型的限制

    class MyStatic{
        private:
            MyStatic *my;
            static MyStatic instance;
        public:
            static void test();
            static string name;
    };
    

在上面的例子中,如果去掉 instance 变量前的是static,那么将会编译失败

2.静态成员可以作为默认的实参,而普通成员则不行

class MyStatic{
    private:
        static const int  a = 0;
        int b;
    public:
        MyStatic(int c = a):b(a){

        }
    };

如果把常量a 的 static关键字拿到,那么编译将报错。

Android 热修复技术

发表于 2017-01-15

今天动手实践了一下安卓上的热修复机制,现在总结一下、

在Android中要想实现热更新实际上就是对class文件的替换,将目标文件替换成新的class文件即可,但是在Android中并没有直接使用class文件,而是将class重新打包在了一个dex文件中,然后从这个dex文件中去load class.
在Android中,系统为我们提供了这么一个类,叫做DexClassLoader, 从这个类的注释中我们可以看到这个类可以从包含了dex文件的jar和 apk文件中去载类,但是这个类实际上什么都没有做,只是继承了BaseDexClassLoader,BaseDexClassLoader中重写findClass方法

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
    Class c = pathList.findClass(name, suppressedExceptions);        
    if (c == null) {
        ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
        for (Throwable t : suppressedExceptions) {
            cnfe.addSuppressed(t);
        }
        throw cnfe;
    }
    return c;
}

从上面的代码可以看出,实际上BaseDexClassLoader自己也什么都没有做,而是交给了已 pathList 这个对象去完成加载的任务,pathList 是一个DexPathList 对象,它的findClass实现如下,

 public Class findClass(String name, List<Throwable> suppressed) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;

        if (dex != null) {
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}

它实际上会先遍历一个内部一个dexElements数组,然后从数组元素Elment中加载class对象,在这里我们最终找到了我们的classd对象是什么时候加载的了,既然知道了是什么时候加载的,我们就可以通过替换的方式来改变class了,在虚拟机加载一个class的时候,如果一个类已经被加载过了那么后面就不会再加载它,所以可以将包含补丁class的 Elment 放在数组的最前面,让虚拟机先加载补丁class, 这样需要被替换的class就不会被加载了,从而实现了更新。

实际上PathClassLoader也是BaseDexClassLoader的子类,系统就是使用了这个类来实现加载,PathClassLoader和上面提到的DexClassLoader区别如下, 在创建BaseDexClassLoader的对象时候需要填一个 optimizedDirectory 参数,optimizedDirectory是用来缓存我们需要加载的dex文件的,并创建一个DexFile对象,如果它为null,那么会直接使用dex文件原有的路径来创建DexFile对象。同时optimizedDirectory必须是一个内部存储路径,无论哪种动态加载,加载的可执行文件一定要存放在内部存储。DexClassLoader可以指定自己的optimizedDirectory,所以它可以加载外部的dex,因为这个dex会被复制到内部路径的optimizedDirectory;而PathClassLoader没有optimizedDirectory,所以它只能加载内部的dex,这些大都是存在系统中已经安装过的apk里面的。

因为这些类和变量我们在SDK中都无法直接的获取,所以只能通过反射的方式来获取。
下面是核心代码:

package com.baidu.myhotfix;

import android.content.Context;
import android.util.Log;

import java.lang.reflect.Array;
import java.lang.reflect.Field;

import dalvik.system.BaseDexClassLoader;
import dalvik.system.DexClassLoader;

/**
 * Created by liuwei64 on 2017/1/14.
 */

public class HotFixUtil {

    private static final String TAG = "HotFixUtil";

    public static void init(Context context) {
        ClassLoader classLoader = context.getClassLoader();
        try {
            if (classLoader instanceof BaseDexClassLoader) {
                Log.i(TAG, "get baseclassloader instance");
                Field field = BaseDexClassLoader.class.getDeclaredField("pathList");
                field.setAccessible(true);
                Object dexPathList = getValue(BaseDexClassLoader.class, classLoader, "pathList");
                Object dexElements = getValue(dexPathList.getClass(), dexPathList, "dexElements");

                DexClassLoader cl = new DexClassLoader("/storage/sdcard0/classex_dex.jar",
                        context.getCacheDir().getAbsolutePath(),
                        null, context.getClassLoader());

                Object dexPathListNew = getValue(BaseDexClassLoader.class, cl, "pathList");
                Object dexElementsNew = getValue(dexPathListNew.getClass(), dexPathListNew, "dexElements");

                Object newElements = combineArray(dexElementsNew, dexElements);
                setValue(dexPathList.getClass(), dexPathList, "dexElements", newElements);
            }
        } catch (Exception ex) {
            Log.e(TAG, ex.getMessage());
        }
    }

    public static Object getValue(Class clz, Object instance, String fileName) {
        try {
            Field field = clz.getDeclaredField(fileName);
            field.setAccessible(true);
            return field.get(instance);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void setValue(Class clz, Object instance, String fileName, Object value) {
        try {
            Field field = clz.getDeclaredField(fileName);
            field.setAccessible(true);
            field.set(instance, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Object combineArray(Object arrayFirst, Object arraySecond) {
        if (arrayFirst == null || arrayFirst == null) {
            return null;
        }

        int firstLen = Array.getLength(arrayFirst);
        int secondLen = Array.getLength(arraySecond);

        int totalLen =  firstLen + secondLen;
        Object result = Array.newInstance(Array.get(arrayFirst, 0).getClass(), totalLen);
        for (int  i = 0; i < totalLen; i++) {
            if (i < firstLen) {
                Array.set(result, i , Array.get(arrayFirst, i));
            } else {
                Array.set(result, i , Array.get(arraySecond, i - firstLen));
            }
        }

        return result;
    }
}

实际上这里还有一个问题要解决,就是CLASS_ISPREVERIFIED的问题,后面自己实现了再来更新!

LeakCanary源码分析(1)

发表于 2016-12-12

LeakCanary是在Android上一款开源的内存泄露分析工具,通过这款工具你可以实时的观察到当前app中出现的内存泄露情况,并且提供泄露路径方便用户来查找内存泄露的根源,[链接]是GitHub上的项目1

LeakCanary 项目中,包括了五个模块,分别是

  • leakcanary-analyser(为解析hprof文件,找到泄露的对象)
  • leakcanry-android(主要包含了暴漏给android使用的接口)
  • leakcanary-anroid-no-op(没有用到)
  • leakcanary-smaple(示例代码)
  • leakcanary-watcher(监视对象,触发GC并且生成hprof文件)

下面从示例代码中开始入手看LeakCanary是如何发挥作用的

leakcanary-smaple

在这个模块当中只有两个类,一个是自定义的ExampleApplication,继承于Application,一个是MainActivity.

@Override
public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
        // This process is dedicated to LeakCanary for heap analysis.
        // You should not init your app in this process.
        return;
    }
    enabledStrictMode();
    LeakCanary.install(this);
}

在onCreate方法当中,首先判断当前的进程是不是分析进程,在LeakCanary当中为了避免在用户程序的进程当中占用太多的资源,所以整个分析过程是在一个单独的进程当中。

这个方法最终是调用了LeakCanaryInternals这个类的isInServiceProcess方法,这个方法比较简单,主要是通过比较当前Application所在进程的名字是不是HeapAnalyzerService在AndroidManifest.xml中所指定的名字来判断当前的进程是不是应用所在的进程,如果是应用所在的进程 LeakCanary.isInAnalyzerProcess(this) 最终会返回一个false, 这个时候将会继续往下执行,最后执行到LeakCanary.install(this); 如果当前进程的名字就是HeapAnalyzerService在AndroidManifest.xml中所指定的名字,那么表示当前进程是分析hprof的进程,这个时候就直接返回。

接下来进入到 LeakCanary.install(this) 当中,方法的代码如下

  /**
  * Creates a {@link RefWatcher} that works out of the box, and starts watching activity
  * references (on ICS+).
*/
 public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
    .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
    .buildAndInstall();
    }

整个方法中都是用调用了通过调用 LeakCanary中的静态方法refWather返回了一个AndroidRefWatcherBuilder对象,这个对象中采用了建造者模式,来设置我们内存泄露中我们设置的一些参数

  • listenerServiceClass()解析结果处理的IntentService. 这个Service需要继承AbstractAnalysisResultService 并且重写onHeapAnalyzed(HeapDump, AnalysisResult)方法来处理得到的分析结果。
  • excludedRefs 用来方便用户去掉一些特定的引用,当进行内存泄露分析的时候这些引用将不会被考虑在内。
  • buildAndInstall() 创建了RefWatcher实例,并且开始监听Activity的引用。

    public RefWatcher buildAndInstall() {
        RefWatcher refWatcher = build();
        if (refWatcher != DISABLED) {
            LeakCanary.enableDisplayLeakActivity(context);
            ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher);
        }
        return refWatcher;
    }
    

从上面代码不难看出其中最重要的方法调用是ActivityRefWatcher.installOnIcsPlus。

@TargetApi(ICE_CREAM_SANDWICH)
public final class ActivityRefWatcher {

    public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
        if (SDK_INT < ICE_CREAM_SANDWICH) {
            // If you need to support Android < ICS, override onDestroy() in your base activity.
            return;
        }
        ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
        activityRefWatcher.watchActivities();
    }

    private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
            new Application.ActivityLifecycleCallbacks() {
                @Override
                public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                }

                @Override
                public void onActivityStarted(Activity activity) {
                }

                @Override
                public void onActivityResumed(Activity activity) {
                }

                @Override
                public void onActivityPaused(Activity activity) {
                }

                @Override
                public void onActivityStopped(Activity activity) {
                }

                @Override
                public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
                }

                @Override
                public void onActivityDestroyed(Activity activity) {
                    ActivityRefWatcher.this.onActivityDestroyed(activity);
                }
            };

    private final Application application;
    private final RefWatcher refWatcher;

    /**
     * Constructs an {@link ActivityRefWatcher} that will make sure the activities are not leaking
     * after they have been destroyed.
     */
    public ActivityRefWatcher(Application application, final RefWatcher refWatcher) {
        this.application = checkNotNull(application, "application");
        this.refWatcher = checkNotNull(refWatcher, "refWatcher");
    }

    void onActivityDestroyed(Activity activity) {
        refWatcher.watch(activity);
    }

    public void watchActivities() {
        // Make sure you don't get installed twice.
        stopWatchingActivities();
        application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
    }

    public void stopWatchingActivities() {
        application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
    }
}

看到 lifecycleCallbacks 变量的类型的类型,就应该恍然大悟了,LeakCanary就是通过在Application当中注册了Activity 生命周期的监听器来实现对Acitivity 引用泄露的分析。 , 每当一个调用一个Activity onDestroy方法之前会调用lifecycleCallbacks的onActivityDestroyed方法。
最终会进入到RefWatcher实例的watch(Object ref, String reference Name)方法当中,代码如下:

/**
 * Watches the provided references and checks if it can be GCed. This method is non blocking,
 * the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
 * with.
 *
 * @param referenceName An logical identifier for the watched object.
 */
public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
        return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    final KeyedWeakReference reference =
            new KeyedWeakReference(watchedReference, key, referenceName, queue);

    ensureGoneAsync(watchStartNanoTime, reference);
}

在解释这段代码之前简单的说一下判断一个对象有没有被回收的原理:

在Java中,当我们使用WeakReference指向一个对象的时候,如果这个对象已经被回收了,那么WeakReference 将会被添加到一个指定的队列当中去

在这个方法中,LeakCanary 创建了一个指向Activity的弱引用KeyedWeakReference 对象,KeyedWeakReference继承于WeakReference 但是做了一定的扩展就是添加了两个字符串类型的字段name 和 key。这个key 也就是通过UUID生成的字符串。然后把这个key 添加到了 retainedKeys 代表的字符串集合当中去,只要key还在retainedKeys 当中就表示和 key 绑定的weakreference所指向的对象还没有被回收。

ensureGoneAsync()只是把分析方法通过异步的方式来处理(里面还有很多内容可以分析),在里面最终的调用的方法是 ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) 方法

放代码

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    ....

    removeWeaklyReachableReferences();

    ...
    if (gone(reference)) {
        return DONE;
    }
    gcTrigger.runGc();
    removeWeaklyReachableReferences();
    if (!gone(reference)) {
        File heapDumpFile = heapDumper.dumpHeap();
        if (heapDumpFile == RETRY_LATER) {
            // Could not dump the heap.
            return RETRY;
        }

        heapdumpListener.analyze(
                new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
                        gcDurationMs, heapDumpDurationMs));
    }
    return DONE;
}

首先进入removeWeaklyReachableReferences() 方法

private void removeWeaklyReachableReferences() {
    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
    // reachable. This is before finalization or garbage collection has actually happened.
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
        retainedKeys.remove(ref.key);
    }
}

在方法的注释当中我们可以清楚的明白方法的含义:
一个对象如果变得只有一个弱引用指向它,那么它就会被添加到指定的队列当中去,就是在前面提到过的queue,这个时候我们可以通过KeyedWeakReference 来找出当初匹配的weakference , name , key,然后将key 从集合中移除,表明key对应引用所指向的对象已经被回收。

接下来这个时候我们在判断一下引用的所指向的对象是不是被回收了,这个判断还是通过判断key 在不在集合当中,如果不在表示这个对象已经完全被回收,可以直接返回。

接下来会触发一次GC. 之所以这样做是为了尽可能的避免进行内存的dump和分析操作。有些对象在这个时候本应该是只有weakreference 可达,但是却不在对队列当中,这个时候触发一次GC并且再进行一次引用分析可以有效地避免一些误判。

这里触发GC的代码如下

// System.gc() does not garbage collect every time. Runtime.gc() is
  // more likely to perfom a gc.
  Runtime.getRuntime().gc();

GC完成之后,接下来再进行一次引用的检查,如果这个时候对象还是没有回收那么接下来就要开始内存的dump 操作了。

C++显式类型转换

发表于 2016-12-10

static_cast
任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast,例如将一个整形的运算对象强制转换为doule类型。static_cast常见的用法有以下两种:

  • 类型之间的转换,例如 doule 转为 int.
  • 可以用于编译器无法自动执行的类型转换也非常有用,可以使用static_cast找回存在于void* 指针中的值,例如

    void p =&d;
    double
    dp = static_cast(p);

当我们把指针保存在void当中,并且使用static_cast找回转换的值得时候,应该保证void 指针的值不变,如果一旦类型发生了变换,那么将产生未定义的后果。

static_cast作类型转换的时候需要我们自己去确保转换的类型是匹配的,static_cast可以用在指针和引用上,还可以用在基础数据和对象上,用static_cast来处理的转换就需要两者具有”一定的关系”了,实际上继承关系是可以被static_cast接受的,其他情况的指针和引用转换都会被static_cast直接扔出编译错误,而这层关系上的转换又几乎都可以被dynamic_cast所代替

const_cast
const_cast 只能改变运算对象底层的const修饰、
对于将常量对象转为非常量对象的行为,我们一般称之为去掉const性质,一旦我们去掉了某个对象的const性质,编译器就不在阻止我们对该对象进行修改了,虽然我们可以获取修改对象的权限,但是这样的操作时未定义的。阅读

reinterpret_cast
reinterpret_cast运算符用来处理无关类型之间的转换,他会产生一个新值,这个值会与原始参数有完全相同的bit位。
IBM的C++指南里倒是明确告诉了我们reinterpret_cast可以,或者说应该在什么地方用来作为转换运算符:

  • 从指针类型到一个足够大的整数类型
  • 从整数类型或者枚举类型到指针类型
  • 从一个指向函数的指针到另一个不同类型的指向函数的指针
  • 从一个指向对象的指针到另一个不同类型的指向对象的指针
  • 从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针
  • 从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针

dynamic_cast
与static_cast一样,dynamic_cast的转换也需要目标类型和源对象有一定的关系:继承关系。 更准确的说,dynamic_cast是用来检查两者是否有继承关系。因此该运算符实际上只接受基于类对象的指针和引用的类转换。

对于从子类到基类的指针转换,static_cast和dynamic_cast都是成功并且正确的(所谓成功是说转换没有编译错误或者运行异常;所谓正确是指方法的调用和数据的访问输出是期望的结果),这是面向对象多态性的完美体现。

而从基类到子类的转换,static_cast和dynamic_cast都是成功的,但是正确性方面,我对两者的结果都先进行了是否非空的判别:dynamic_cast的结果显示是空指针,而static_cast则是非空指针。但很显然,static_cast的结果应该算是错误的。

如果基类或者子类没有任何虚函数(如果基类有虚函数表,子类当然是自动继承了该表),当他们作为dynamic_cast的源类型进行转换时,编译也会失败。

这种情况是有可能存在的,因为在设计的时候,我们可能不需要让子类重写任何基类的方法。但实际上,这是不合理的,
如果要用继承,那么一定要让析构函数是虚函数;如果一个函数是虚函数,那么在子类中也要是虚函数。

12345
Tom Liu

Tom Liu

41 日志
48 标签
© 2018 Tom Liu
由 Hexo 强力驱动
主题 - NexT.Muse