全面认识Android手机(MIUI ROM适配之旅第四天——移植MIUI Framework)

本文主要是介绍全面认识Android手机(MIUI ROM适配之旅第四天——移植MIUI Framework),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 为什么使用代码插桩

    首先我们来回顾第一章中的Android软件架构图,这个图中框架层的代码完全是由Java语言编写的,对于这两层的代码,在没有源代码的情况下我们可以采取代码插桩的方式来注入我们的代码。但是对于下面几层的代码几乎都是以机器码的形式存在,机器码也是可以修改的,但是修改难度和修改smali代码的难度不可同日而语。我们这个系列的文章不介绍如何修改这些机器码,大家有兴趣的可以参考网上的相关资料。MIUI是基于源码开发的,为了提升整个效率,我们会修改下面几层的代码,比如说我们修改了dalvik虚拟机,skia绘图库等。幸好这些修改不多而且有些是为了提升性能的,不影响MIUI的整体功能。MIUI的绝大部分修改都是对框架层和核心应用层,这样保证了我们在原厂ROM的基础上修改这两个层的代码达到移植MIUI的目的。
    
    大家看到这里可能有一个疑问,我们直接替换原厂ROM框架层和核心应用层这两层的代码不就得了。不行,因为各个层次之间是有管理的,框架层和下层代码的一些调用接口是各个厂家自己扩展的,简单的整个替换MIUI框架层和核心应用层的代码无法工作。

2. 方法概述

    这一章介绍MIUI框架层的移植,其实主要是修改system/framework目录下的三个文件:framework.jar, android.policy.jar和services.jar。这3个文件是Android系统的核心:framework.jar提供了应用层调用的各种API的实现,android.policy.jar提供了锁屏的实现以及手机窗口管理策略的实现。services.jar是一些核心服务Java层的实现,比如ActivityManagerService, PackageManagerService等,这些服务大都运行在system_server进程中。
    
    我们目前2.3的代码是基于google发布的android 2.3.7源代码开发的,大家下载附件中的压缩包打开后的目录结构为:
    porting-miui/
        |-----------------android
                       |------------framework.jar
                       |------------services.jar
                       |------------android.policy.jar
        |------------------miui
                      |----------framework.jar
                      |----------services.jar
                      |----------android.policy.jar
                      |-----------framework-res/
                      |-----------framework-miui-res.apk
    其中android目录中的这三个文件是从google发布的android2.3.7源代码编译而来的,
而miui目录中的这三个文件则是我们在android2.3.7源代码基础上修改后的代码编译而来的。这样我们可以先反编译这些文件,找出反编译后的差别之处,然后将这些差别之处应用到原厂ROM的这三个文件中。听起来是不是和Linux下的patch过程很相似,是的,确实相似,只不过通常的patch是基于源代码的,然后解决一些冲突。而我们是基于smali代码,然后解决一些冲突。(解决冲突现在可能不太明白,没关系,下面会有例子)。

3. 移植资源

    在上一节中miui目录下的framework-res目录和framework-miui-res.apk这两个是和移植资源相关的。framework-res目录下是我们对系统资源所做的修改即/system/framework/framework-res.apk的修改,大家可以反编译framework-res.apk,将这些修改合到framework-res中,然后再编译回去,这个比较简单,不多做介绍。

    framework-miui-res.apk是miui的资源包,所有的miui app都会用到它。这个资源包也需要放在/system/framework/目录中,在原厂ROM中,大家一般在/system/framework目录下除了framework-res.apk,也会发现一些其它的xxx-res.apk。为了针对这种情况,miui的资源ID都是以0x03开头的,一般的原厂ROM是2个资源包,framework-res.apk的资源ID是以0x01开头的,另外一个资源包以0x02开头。但是我们发现国行的defy比较变态,这个目录下有3个资源包,因此针对defy我们得特殊处理。所以如果你所移植的机型这个目录下不止两个资源包的话,需要和我们联系。未来我们会考虑MIUI的资源ID都以0x06开头,我们相信应该没有哪个原厂ROM变态到有5个资源包。

4. 修改smali

    这一章我们重点介绍如何修改原厂ROM的smali将MIUI的修改应用到上面去。我们不会将所有的修改都会在文中列出,挑选几个有代表性的讲解,剩下的大家可以自己去做。我们以i9100为例讲述如何修改。在第二篇准备工作中,我们给出了i9100国行ROM的下载链接,并且讨论了如何在这个ROM的基础上做deodex。为了方便起见,我们把i9100原厂ROM做个deodex后的framework.jar,services.jar和android.policy.jar也放在了附件中。

4.1 比较差异
    这里的比较差异包含两个部分:比较miui和原生android的差异,比较i9100和原生android的差异。
    以framework.jar为例,首先可以建3个目录反汇编这3个jar包:
    apktool d framework.jar
    执行完毕后,会产生framework.jar.out目录。
    
    接下来使用附件中的脚本rmline.sh运行如下命令:
    ./rmline.sh framework.jar.out
    rmline.sh是用以把smali所有以.line开头的行去掉,这样我们容易比较smali代码上的差别。但是对于所移植的机型,请先复制一份为去掉.line的framework.jar.out版本,因为这些对调试很重要,我们能通过adb logcat报告的错误信息中去定位在哪一行。
    
    接下来用大家说熟悉的文件比较工具来比较差异,Linux下推荐meld, Windows下推荐Beyond Compare。
    
    用meld比较miui和原生android的区别,大家可以看到有很多新增的Miui开头的类,和一个新增的miui目录,这些新增的文件和目录我们直接拷到i9100的framework.jar.out中对应的目录中即可(使用有.line的版本)。为什么我们不把这些新增的类组织在一个单独的jar包中呢(请大家思考一下这个问题)。

    不比较那些相同的和新加的,我们只比较修改过的文件,在附件中有一个change-list文件,其中列出了我们修改过的文件,你会发现和这个比较结果有一点不符,如果你比较那些文件,会发现只是一些微小的差异(比如说nop这种空指令),这是由于apktool反编译导致的。我们无需关心,我们只需要比较那些我们修改过的文件。
    
    下面我们就开始修改smali文件了,我将这些修改分成3种情况,选择有代表性的3个文件加以介绍,这3种情况难度依次增加。

4.2 直接替换

    以ActivityThread.smali为例,比较发现miui改了其中一个方法getTopLevelResources,而i9100和原生android的实现完全一样,这种情形是最简单也是最happy的,我们改的地方要适配的机型原厂ROM完全没有修改,直接替换就可以了。

4.3 线性代码

    还是以ActivityThread.smali为例,对于这个文件,miui一共改了两个方法,一个是上面介绍的,另一个是applyConfigurationToResourcesLocked。通过比较得知,miui修改了这个方法,i9100也修改了这个方法。怎么办呢,我们先分析一下miui修改的代码:
     .method final applyConfigurationToResourcesLocked(Landroid/content/res/Configuration;)Z

        invoke-virtual {v5, p1}, Landroid/content/res/Configuration;->updateFrom(Landroid/content/res/Configuration;)I
        move-result v0
       .local v0, changes:I
        invoke-static {v0}, Landroid/app/MiuiThemeHelper;->handleExtraConfigurationChanges(I)V
        invoke-virtual {p0, v7}, Landroid/app/ActivityThread;->getDisplayMetricsLocked(Z)Landroid/util/DisplayMetrics;
        move-result-object v1
        .local v1, dm:Landroid/util/DisplayMetrics;

    在上面将miui增加的代码用红色标出,在讲述之前,先解释一下smali代码的一些规律:
所有的局部变量用v开头,方法的顶部.locals 8表示这个方法使用8个局部变量。所有的参数用p开头,局部变量和参数都是从0开始编号。对于非静态方法来说,p0就是对象本身的引用,即this指针。
    
    这里miui新增了一个静态方法调用,对于这种顺序执行的一段代码,我们称之为线性代码。这个例子比较简单,只新增了一个静态方法调用。线性代码的特点是只有一个入口和一个出口,在编译器的术语这叫做基本块。对于这种新增的代码,我们找出它的上下文,即修改的代码前后的操作。然后在i9100的该方法的smali代码中找到相应的位置,把这个修改应用到i9100中去。这种修改也相对简单,插入代码的相应位置比较好定位。

4.4 条件判断

    这种情况指的是miui插入的代码并不是一个线性代码,而是有条件判断的。我们以Resources.smali为例,miui修改了其中的loadDrawable方法,修改后的结果如下:

    .method loadDrawable(Landroid/util/TypedValue;I)Landroid/graphics/drawable/Drawable;
        .end local v8           #e:Ljava/lang/Exception;
        .end local v13          #rnf:Landroid/content/res/Resources$NotFoundException;
        :cond_6

         invoke-virtual/range {p0 .. p2}, 
        Landroid/content/res/Resources;->loadOverlayDrawable(Landroid/util/TypedValue;I)Landroid/graphics/drawable/Drawable;
        move-result-object v6
        if-nez v6, :cond_1

       :try_start_1
       move-object/from16 v0, p0

    红色代码是miui插入的代码,我们再看一下i9100相对于原生android对这个方法的改动,发现改动非常大。这种情况怎么办呢,这种情况下的关键是找到所插入代码的入口点和出口点(即这段代码是从哪执行而来的,执行完毕后又往哪去开始执行代码)。
    
    首先,我们发现插入代码的前面是一个标号:cond_6,这说明程序中应该有一个跳转语句跳转到这个标号:cond_6。而且这种程序应该也可以从:cond_6上面的语句顺序执行而来(即它可能有两个入口点),我们分别去找这两个入口点的代码。首先我们去找哪个语句使用了:cond_6,找到如下代码:
    const-string v15, ".xml"
    invoke-virtual {v9, v15}, Ljava/lang/String;->endsWith(Ljava/lang/String;)Z
    move-result v15
    if-eqz v15, :cond_6

    可以发现这段代码是在判断v9这个字符串是否以".xml"结尾,如果不是的话,跳转到:cond_6。好,我们去i9100中找到对应的代码逻辑。对于这个例子,我们完全可以以".xml"作为一个关键字去i9100的loadDrawable方法中搜索一下,定位到如下代码:
    const-string v17, ".xml"
    move-object v0, v10
    move-object/from16 v1, v17
    invoke-virtual {v0, v1}, Ljava/lang/String;->endsWith(Ljava/lang/String;)Z
    move-result v17
    if-eqz v17, :cond_b
这段代码的逻辑和我们在miui中找到的代码一样,看来,我们应该把miui插入的代码插入到:cond_b之后。找到i9100代码中的:cond_b之后,我们看看这条代码后面的代码,发现和我们插入的代码后面的代码基本类似,这下可以确定miui新插入的代码应该放在:cond_b之后了。

    再来看看出口点,miui插入的代码有两个出口点(是一个条件判断),
    if-nez v6, :cond_1
如果v6为空往下执行,如果不为空,则跳转到:cond_1,好,我们来看看:cond_1的代码是在干嘛?:cond_1的代码如下:
   :cond_1
   :goto_1
   if-eqz v6, :cond_2
   move-object/from16 v0, p1
   iget v0, v0, Landroid/util/TypedValue;->changingConfigurations:I

    我们在i9100中发现了一段类似的代码:
    :cond_1
    :goto_1
    if-eqz v7, :cond_2 
    move-object/from16 v0, p1
    iget v0, v0, Landroid/util/TypedValue;->changingConfigurations:I

只不过是v6变成了v7,说明这段代码检测v7的值,因此我们需要将我们插入的代码改为:
    invoke-virtual/range {p0 .. p2}, 
    Landroid/content/res/Resources;->loadOverlayDrawable(Landroid/util/TypedValue;I)Landroid/graphics/drawable/Drawable;
    move-result-object v7
    if-nez v7, :cond_1

4.5 内部类

    在这一节的最后我们来介绍一下内部类。对于Java文件中的每一个内部类,都会产生一个单独的smali文件,比如ActivityThread$1.smali,这些文件的命名规范是如果是匿名类,外部类+$+数字。否则的话是外部类+$+内部类的名字。
    
    当在内部类中调用外部类的私有方法时,编译器会自动合成一个静态函数。比如下面这个类:
public class Hello {
    public class A {
        void func() {
            setup();
        }
    }
    private void setup() {
    }
}
我们在内部类A的func方法中调用了外部类的setup方法,最终编译的smali代码为:
Hello$A.smali文件代码片段:
# virtual methods
.method func()V
    .locals 1

    .prologue
    .line 5
    iget-object v0, p0, LHello$A;->this$0:LHello;

    #calls: LHello;->setup()V
    invoke-static {v0}, LHello;->access$000(LHello;)V

    .line 6
    return-void
.end method

Hello.smali代码片段:
.method static synthetic access$000(LHello;)V
    .locals 0
    .parameter

    .prologue
    .line 1
    invoke-direct {p0}, LHello;->setup()V

    return-void
.end method
可以看到,编译器自动合成了一个access$000方法,假如当我们在一个较复杂的内部类中加入了一个对外部类私有方法的调用,虽然只是导致新合成了一个方法,但是这些合成的方法名可能都会有变化,这样的结果就是smali文件差异较大,这个时候需要仔细分析,找到调用的私有方法。然后给合成的方法选取一个未被使用的名字。

5. 建议
    最后想对修改smali代码给出一些建议:
    (1) 细心,仔细的定位插入代码在相应机型代码中的插入位置。
    (2) 要注意局部变量序号的改变。
    (3) 不要一次修改完所有的文件再用apktool重新编译,如果插入代码有错误,会无法编译。但是apktool的编译出错信息是天书,你无从知道是哪个文件改错了。
    (4) 出现错误不要紧,检查adb logcat的错误信息,找出错误发生的原因。

修改smali代码没那么难,多实践一定会掌握相应的技巧。

hook_smail.apk

这篇关于全面认识Android手机(MIUI ROM适配之旅第四天——移植MIUI Framework)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/863887

相关文章

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

每天认识几个maven依赖(ActiveMQ+activemq-jaxb+activesoap+activespace+adarwin)

八、ActiveMQ 1、是什么? ActiveMQ 是一个开源的消息中间件(Message Broker),由 Apache 软件基金会开发和维护。它实现了 Java 消息服务(Java Message Service, JMS)规范,并支持多种消息传递协议,包括 AMQP、MQTT 和 OpenWire 等。 2、有什么用? 可靠性:ActiveMQ 提供了消息持久性和事务支持,确保消

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Android 10.0 mtk平板camera2横屏预览旋转90度横屏拍照图片旋转90度功能实现

1.前言 在10.0的系统rom定制化开发中,在进行一些平板等默认横屏的设备开发的过程中,需要在进入camera2的 时候,默认预览图像也是需要横屏显示的,在上一篇已经实现了横屏预览功能,然后发现横屏预览后,拍照保存的图片 依然是竖屏的,所以说同样需要将图片也保存为横屏图标了,所以就需要看下mtk的camera2的相关横屏保存图片功能, 如何实现实现横屏保存图片功能 如图所示: 2.mtk

android应用中res目录说明

Android应用的res目录是一个特殊的项目,该项目里存放了Android应用所用的全部资源,包括图片、字符串、颜色、尺寸、样式等,类似于web开发中的public目录,js、css、image、style。。。。 Android按照约定,将不同的资源放在不同的文件夹中,这样可以方便的让AAPT(即Android Asset Packaging Tool , 在SDK的build-tools目

Android fill_parent、match_parent、wrap_content三者的作用及区别

这三个属性都是用来适应视图的水平或者垂直大小,以视图的内容或尺寸为基础的布局,比精确的指定视图的范围更加方便。 1、fill_parent 设置一个视图的布局为fill_parent将强制性的使视图扩展至它父元素的大小 2、match_parent 和fill_parent一样,从字面上的意思match_parent更贴切一些,于是从2.2开始,两个属性都可以使用,但2.3版本以后的建议使