MelonTeam 移动终端前沿技术的探索者

Android代码上减少方法数的一些奇技淫巧


| 导语 随着Android项目代码量的增加,当应用方法数量超过了65536的时候,编包的时候就会报出著名“64k”方法数问题。虽然然最简单粗暴的方法是分dex,还有其他像混淆等,但本人还是研究了几种代码上减少方法的方式,希望能帮到“有缘人”。

一、工具介绍

二、代码场景与方法数分析

下面要介绍下几种常见的代码使用场景,分析方法数增加情况。

1.1 子类中调用了父类中未被子类重写的方法

(1)场景

先看一个简单的类:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

按照我们手算出来的方法数是2(一个默认构造器,一个onCreate方法);

那我们使用工具看下这个类的方法数。

[ MainActivity的方法数 ]

[ MainActivity.smali文件 ]

3个?为什么是3个?原来是多了setContentView这个方法。因为按照java的语义,如果有覆盖父类的方法,则会直接调用覆盖的方法。从smali文件可以看出setContentView是属于MainActivity的方法。

(2)解决方案

那这次我们改成这样:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.setContentView(R.layout.activity_main);
    }
}

工具看下方法数:

[ 修改后MainActivity的方法数 ]

结果的确和我们手算出来的一样!

(3) 其他特例

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.setContentView(R.layout.activity_main);
    }
}

public class TestActivity extends MainActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.setContentView(R.layout.activity_main);
    }
}

[ MainActivity和TestActivity的方法数 ]

[ TestActivity.smali文件 ]

方法数实际增加了5个。因为TestActivitysuper就是MainActivity,而MainActivity并没有setContentView这个方法,而AppCompatActivity才有,所以这时候的super.setContentView相当于this.setContentView
这个其实也是有解决办法的,可以这样写((AppCompatActivity)this).setContentView

(4)综上所述:

子类中调用了父类中未被子类重写的方法时,请尽量使用super来调用或者使用方法的父类强转下this

1.2 私有内部类

(1)场景

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.setContentView(R.layout.activity_main);
        new Thread(new Task()).start();
    }

   private class Task implements Runnable{

        @Override
        public void run() {
        }
    }
}

目测,MainActivity2个方法数(默认构造器、onCreate),Task2个方法数(默认构造器、run)。
那,事实是不是这样呢?
工具看下方法数:

[ MainActivity和Task方法数 ]

[ MainActivity$Task.smali文件 ]

MainActivity方法数没错,而Task实际上得出来的方法数却是3个。私有内部类默认直接增加了两个带参构造器。
其他情况呢?

(2)解决方案

实验了下非私有的内部类,是正常的,2个方法数。所以将内部类改成非private就能解决。

(3)综上所述:

建议定义内部类是尽量使用非私有的。

1.3 在内部类中访问外部类的私有方法/变量

(1)场景

public class MainActivity extends AppCompatActivity {

    private String text = "在内部类里调用";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.setContentView(R.layout.activity_main);
        new Thread(new Task()).start();
    }

   class Task implements Runnable{

        @Override
        public void run() {
            System.out.println(text);
        }
    }
}

1.2分析,目测是:MainActivity2个方法数(默认构造器、onCreate),Task2个方法数(默认构造器、run)。
而实际上,是:

[ MainActivity和Task方法数 ]

[ MainActivity.smali文件 ]

在外部类中,增加了一个access$000的方法,这方法是为了支持Task访问MainActivityprivate变量。

若将字段变成非私有,就不会产生access$000的方法。

(2)综上所述:

若外部类字段有可能被内部类访问到,就尽量不使用private

三、总结

(1)子类中调用了父类中未被子类重写的方法时,请尽量使用super来调用。
(2)建议定义内部类是尽量使用非私有的。
(3)若外部类字段有可能被内部类访问到,就尽量不使用private


相关文章

说一说