Skip to content

编译时自动生成一个类似R.java的文件,编译入sdk,方便sdk以jar包+res的形式分发

License

Notifications You must be signed in to change notification settings

TangHuaiZhe/AutoR

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 

Repository files navigation

996.icu

使用指南

  1. 支持mac、linux、ubuntu环境,windows未支持,可以尝试debug修复,多数是路径等问题
  2. 把整个autoR文件夹复制到项目目录下
  3. 机器已经安装好python3.7环境
  4. 配置config.ini文件:
[Dir]
ProjectOrResDir = SdpOpenWallet
# android sdk目录,优先会查找ANDROID_HOME环境变量,没有再读取sdkdir目录
sdkdir = /Users/tang/Library/Android/sdk

[Version]
# 编译sdk版本,用于生成R.java,最好和项目版本保持一致
buildVersion = 27.0.3
compileSdkVersion = 27

[RClass]
# 生成的AutoR.java文件所在包名
RClassPackage = com.shengpay.tool
# 是否需要替换代码
ReplaceCode = true
  1. Android App的gradle文件增加如下任务
//编译之前运行AutoR
task autoR(type: Exec) {
    println 'before preBuild'
    commandLine 'python3','../autoR/autoR.py'
}
  1. 编译代码,autoR任务会运行
  2. git status 查看结果

Todo: 使用gradle plugin实现.

使用场景

一般而言是不需要手动生成R.java文件的,对app开发而言,无疑是画蛇添足,对sdk开发而言,因为Android提供了aar的依赖方式,可以将资源文件一起打包入aar,最后集成方一起编译生成R.java即可。 然而,快要2019年了,仍然有一些强势的集成方/游戏开发商仍然在使用Eclipse开发,不支持aar的一来方式,要求SDK是jar包的形式。对于sdk包含UI的开发方而言是十分痛苦的。 这种提供jar包+res的业务场景下就需要SDK开发者改变资源的获取方式,不能再通过原生R.String.xxx的方式获取资源,因为只有最后一次编译的时候才能确定资源的ID,之前的任何一次打包产生的ID值都是没有意义的。

据此分析,需要做两件事情:

  • 自动生成一个类似R.java一样的文件AutoR.java,包括所有的资源类型的引用,这个java文件最终一起打包进SDK的jar包。
  • 通过资源名和资源类型可以获取到宿主APP最终打包后的资源ID值
  • 为方便重构/编码,仍然需要支持IDE的代码提示

通过资源名和资源类型获取ID

通过资源名和资源类型可以获取资源ID,方式有两种,代码如下:

第一种反射宿主app的R文件方式,注意这里的mPackageName是宿主的package:

	Class<?> cls = Class.forName(mPackageName + ".R$" + resType);
	return cls.getField(resName).getInt(cls);

第二种:调用系统api获取:

	mContext.getResources().getIdentifier(resName, resType, mPackageName);

资源的获取方面: 除了Android系统自带的资源第一种反射的方式无法获取外,两者几乎是等价的,不存在某一种方式能获取到资源,另一种却获取不到的情况。

性能方面: 测试循环一万次,第一种反射的方式在2014年macbookpro上耗时157ms; 第二种方式耗时1900ms,而R.string.xxx的方式,循环1万次的耗时是0ms……

因此显然,应该优先使用反射方式获取资源文件。 代码如下:

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

/* AUTO-GENERATED FILE.  DO NOT MODIFY.
 *
 * This class was automatically generated by the
 * AutoR tool .
 * It should not be modified by hand.
 */
public final class AutoR {
    private static String mPackageName;
    @SuppressLint("StaticFieldLeak")
    private static Context mContext;
    private static volatile boolean RExist;

    public static void init(Context context){
        if (context != null) {
            mContext = context;
            mPackageName = context.getPackageName();
            try {
                Class.forName(mPackageName + ".R");
                RExist = true;
            }
            catch (ClassNotFoundException e) {
                //some game situation
                Log.i("autoR", "No R class");
                RExist =false;
            }
        }else {
            Log.e("autoR","don't init AutoR with null");
        }
    }


    private static int getResId(String resName, String resType) {
        if (mContext != null) {
            //android system resource
            if (!resName.isEmpty() && resName.startsWith("android_")) {
                return mContext.getResources().getIdentifier(resName.replace("android_", ""), resType, "android");
            }
            // R exist
            if (RExist) {
                try {
                    Class<?> cls = Class.forName(mPackageName + ".R$" + resType);
                    return cls.getField(resName).getInt(cls);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                    Log.e("autoR", "IllegalAccessException:" + e.getMessage());
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                    Log.e("autoR", "ClassNotFoundException:" + e.getMessage());
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                    Log.e("autoR", "NoSuchFieldException:" + e.getMessage());
                }
            } else {
                return mContext.getResources().getIdentifier(resName, resType, mPackageName);
            }
        }else {
            Log.e("autoR","you should init AutoR first");
        }
        return 0;
    }
}

在你sdk的初始方法中调用AutoR.inti(context.getApplicationContext)即可。为了避免内存泄露,这里的context应该是ApplcationContext。 第一个问题就此解决。

自动生成AutoR.java文件

其实很简单,打开任何一份系统自动生成的R.java文件,类似如下:

。。。。
    public static final class string {
        public static int status_string = 0x7f150202;
    。。。
    }
。。。

这是一个String类型的资源,系统编译的时候会自动确定他的常量ID值,我们需要做的就是将这个ID值,替换为上一节中的AutoR.getResId("status_string","string")

因此,我们的策略:

  • 系统prebuild task之前插入一个task,手动先调用Android sdk的aapt命令生产R.java,然后根据R.java生成AutoR.java
  • 同时将sdk源码中的import xx.R全部替换为import xx.AutoR
  • 将所有调用资源的地方修改为AutoR.string.xxxx
//编译之前运行AutoR
task autoR(type: Exec) {
    println 'before preBuild'
    commandLine 'python3','../autoR/autoR.py'
}

最后生成的AutoR.java类似如下:

...
    public static final class anim {
        public static int activity_close_enter = getResId("activity_close_enter", "anim");
        public static int activity_close_exit = getResId("activity_close_exit", "anim");
        public static int activity_in_right = getResId("activity_in_right", "anim");
	......
    public static final class attr {
        public static int isLoop = getResId("isLoop", "attr");
        public static int barContent = getResId("barContent", "attr");
	......

具体不多说,可以参考源码。

About

编译时自动生成一个类似R.java的文件,编译入sdk,方便sdk以jar包+res的形式分发

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages