Skip to content

Latest commit

 

History

History
353 lines (225 loc) · 9.22 KB

README_zh.md

File metadata and controls

353 lines (225 loc) · 9.22 KB

Intimate

Intimate 提供了友好的 API 让 java 反射的使用更加简单平滑。

其最核心的价值在于 Intimate 将在编译期对 Apk 内部代码(您编写的 App 代码或引入的第三方库)的调用进行反射优化,完全免除反射的效率问题,使得反射调用就像普通调用一样快捷且无任何代价。

‘ Apk 内部代码包含您编写的 App 应用层代码以及引入的第三方库(含android.support.*)代码。

固化在ROM中的系统代码目前依然只能使用普通反射实现’

开始使用

在根目录的 build.gradle 添加:

dependencies{
    classpath 'me.ele:intimate-plugin:1.0.2'
}

在 app 目录的build.gradle添加:

apply plugin: 'intimate-plugin'

dependencies {
    compile 'me.ele:intimate:1.0.2'
    annotationProcessor 'me.ele:intimate-compiler:1.0.2'
}

示例

希望反射调用的类:

public class User {

    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private void setAge(int a, int b) {
        this.age = a + b;
    }

}

通过接口描述你对反射的需求:

@RefTarget(clazz = User.class, optimizationRef = true)
public interface RefUser {

    @GetField("name")
    String getName();

    @SetField("name")
    void setName(String value);

    @GetField("age")
    int getAge();

    @Method
    void setAge(int a, int b);
    
}

使用 RefImplFactory 创建 RefUser 实例,之后便可以通过接口 RefUser 实现对某个对象任意属性或方法的访问

User user = new User("暴打小女孩", "男", 19, "三年二班");
RefUser refUser = RefImplFactory.getRefImpl(user, RefUser.class);
if(refUser != null){

    assertEquals(refUser.getName(), "暴打小女孩");
    refUser.setName("kaka");
    assertEquals(refUser.getName(), "kaka");

    assertEquals(refUser.getAge(), 19);
    refUser.setAge(19,1);
    assertEquals(refUser.getAge(), 20);
    
}

API

@RefTarget @RefTargetForName

public @interface RefTarget {

    Class clazz();
    
    boolean optimizationRef();
}

public @interface RefTargetForName {

    String className();
    
    boolean optimizationRef();
}

@RefTarget @RefTargetForName 描述期望反射的目标类。我称这个接口为 RefInterface

@RefTarget(clazz=XXX.class) 属性传入期望反射的目标类的 Class ,当你期望反射的目标类是某个不对外暴露的私有类或内部类,无法取得 Class对象时,可以使用@RefTargetForName(className="xxx.xxx.xx")通过目标类的字符串类名进行描述。

Intimate可以对apk内部代码(您编写的app代码或引入的第三方库)的调用进行反射优化,使得反射调用就像普通调用一样快捷且无任何代价,对固化在ROM中的系统类则依然只能通过常规反射进行调用。

所以,对于第三方库的反射,optimizationRef 值应该为 true,Intimate 将对其进行优化。 Android 系统类以及 java 核心库等 System 类 optimizationRef 值应为 false

示例:

@RefTarget(clazz = RecyclerView.class, optimizationRef = true)
public interface RefRecyclerView {

    @GetField("mLastTouchY")
    int getLastTouchY();

    @SetField("mLastTouchY")
    void setLastTouchY(int itemsChanged);
}

@RefTargetForName(className = "android.view.View$ListenerInfo", optimizationRef = false)
public interface RefListenerInfo {

    @GetField("mOnClickListener")
    View.OnClickListener getListener();

}


应尽可能的使用optimizationRef = true,以避免不必要的反射查找耗时,但当System 类使用optimizationRef = true时,将构建失败。

应尽可能的使用@RefTarget(clazz = XXX.class) ,因为@RefTargetForName(className = "xxx.xx.xxx.class") 属性将使用 Class.forName("xxx.xx.xxx.class")实现 Class的获取,你应该避免这样的操作。

@GetField @SetField

public @interface GetField {

    String value();
    
}

public @interface SetField {

    String value();
    
}

@GetField @SetField 描述对类属性的 get 与 set。

value 值描述属性的名称,Intimate 通过 @GetField 修饰的方法的返回值确定属性的类型,通过 @SetField 的参数值确定属性的类型。

需要特别说明的是,当某个属性的类型是内部类或私有类时,你可以用Object来修饰返回值或参数。

@GetField("name")
String getName();

@SetField("age")
void setName(int age);

上面两个例子中,Intimate 得知将要反射调用的两个属性分别为 [java.lang.String : name] , [int : age] 。

@Method

public @interface Method {
    String value() default "";
}

@Method 描述对类方法的调用。

value 值描述方法名,可缺省。缺省时,Intimate 默认方法名为@Method修饰的方法名。

Intimate 将匹配目标类中与@Method修饰的方法完全一样(返回类型,方法名,参数列表)的方法。

需要特别说明的是,当某个属性的类型是内部类或私有类时,你可以用Object来修饰返回值或参数。

下面将给出正确与错误的示例。 目标类:

class User {
    int calculateAge(int year) {
        return 2018 - year;
    }
}

//匹配User calculateAge方法
@Method
int calculateAge(int year);

//匹配User calculateAge方法
@Method("calculateAge")
int getAge(int year);

//User类中无getAge(int)方法,匹配失败,构建失败
@Method
int getAge(int year);

//User类中无calculateAge(int,int)方法,匹配失败,构建失败
@Method
int calculateAge(int year,int month);

异常处理

默认情况下,当optimizationRef = false 时,Intimate 将会 catch 掉所有异常。optimizationRef = true时若找不到方法或属性,编译期便会抛出异常。

此时如果你需要对某些异常进行处理,可以这样做:

@GetField("mListenerInfo")
Object getListenerInfo() throws IllegalAccessException, NoSuchFieldException;
    

在调用getListenerInfo()时,已申明的异常类型将会向上抛出,Intimate catch其余异常。

创建实例

你可以通过RefImplFactorygetRefImpl方法创建反射描述接口的实例:


public class RefImplFactory {
    public static <T> T getRefImpl(Object object, Class clazz){...}
}

RefTextView refTextView = RefImplFactory.getRefImpl(textView, RefTextView.class);

缓存回收

@RefTarget(optimizationRef = false)@RefTargetForName(optimizationRef = false)时,Intimate 将会对 Field 和 Method 的实例做缓存,以使得同一目标类的多次操作仅需一次 Field 和 Method 的反射查找。

如果你确定后续将不会继续对某个目标类进行反射操作,可以通过下面的方法清空缓存,方法参数RefInterface的Class对象:

public class RefImplFactory {

    public static void clearAccess(Class refClazz){...}
    
    public static void clearAllAccess(){...}
}

RefImplFactory.clearAccess(RefTextView.class);

or 

RefImplFactory.clearAllAccess();

@RefTarget(optimizationRef = false)@RefTargetForName(optimizationRef = false)时,无缓存,无需回收。

特殊示例

一切特别的场景需要特别的姿势。

目标类:

class View {

	...
    static class ListenerInfo {
    	...
        public OnClickListener mOnClickListener;
    }

}

当你期望反射得到 OnClickListener mOnClickListener时,一个RefInterface可能并不能实现你的需求,此时你需要两个。

@RefTarget(clazz = TextView.class, optimizationRef = false)
public interface RefTextView {

    @GetField("mListenerInfo")
    Object getListenerInfo() throws IllegalAccessException, NoSuchFieldException;
    
}

@RefTargetForName(className = "android.view.View$ListenerInfo", optimizationRef = false)
public interface RefListenerInfo {

    @GetField("mOnClickListener")
    View.OnClickListener getListener();
    
}

调用

TextView textView = new TextView(context);
RefTextView refTextView = RefImplFactory.getRefImpl(textView, RefTextView.class);

RefListenerInfo refListenerInfo = RefImplFactory.getRefImpl(refTextView.getListenerInfo(), RefListenerInfo.class);

View.OnClickListener listener = refListenerInfo.getListener();

ProGuard

你应该确保你要反射的目标类不会被混淆,否则Intimate将无法找到目标类及其属性

Tips:

  • 内部类应该命名为 package.outer_class$inner_class
  • @RefTarget@RefTargetForName中,应尽可能的使用optimizationRef = true。但当系统类修饰optimizationRef = true时将构建失败,合理识别目标类的类型
  • 当某个属性的类型是内部类或私有类时,你可以用Object来修饰返回值或参数
  • 对于某些内部类或私有类,可以通过多个RefInterface结合使用
  • 其他使用示例可以在Test case中查看:app/src/androidTest/
  • 如果Aspectjx先执行,Intimate可能会失效

License

Apache-2.0 license

Intimate 基于 Apache-2.0 协议进行分发和使用,更多信息参见协议文件。