1. 程式人生 > >MVP模式探索——Presenter和View解耦的嘗試之APT技術

MVP模式探索——Presenter和View解耦的嘗試之APT技術

前言

在前一篇文章《MVP模式探索-Presenter和View解耦的嘗試》裡,我在文章末尾說到了該解耦方式有幾個已知的問題,其中一個就是用反射的方式去執行方法會有效能上的損耗,但是可以用APT技術替代反射的方式。後來我去查了一下反射效能損耗的問題,發現反射出現效能損耗是有一定條件的,那就是需要在反覆多次執行同一個反射呼叫時,當反覆執行的次數達到一個數量級後,才會和普通呼叫產生效能差異。然而像MMVP模式裡的那些反射執行的方法,並不會在短時間內反覆多次呼叫,所以其實並不存在什麼效能上的問題。

關於反射效能問題的文章參考如下

但是今天在這裡我還是要用APT技術去替代反射的方案,這僅僅是多嘗試一種方式和思維去解決同一個問題,也順便學習一下APT技術和JavaPoet。

當然,在具體實施之前,如果大家對APT技術或JavaPoet還不太瞭解,就先去充一下電在來看,會順暢很多。

一、技術實施細節

因為替代方案是在原來的程式碼基礎上改的,所以後面的敘述都會以原來的程式碼為相對參考,所以,如果有什麼疑惑的地方,請記得去看我前一篇文章

1.1 基本說明

  1. 在嘗試APT技術的時候,一開始就遇到了一個問題,我們要編寫的annotationProcessor是一個Java Library,而我最開始的MMVP moudle是一個Android Library,annotationProcessor需要MMVP moudle裡的註解,但是我發現似乎沒有辦法把一個Android Library作為Java Library的依賴,但是反過來卻是可以的,所以我就只好把MMVP moudle裡的一些annotationProcessor需要的註解抽離出去,單獨建了一個mmvpannotation Java Library,讓annotationProcessor moudle和MMVP moudle都依賴mmvpannotation。關於把Android Library作為Java Library的依賴的資料參考

  2. 原來在使用反射方式的的時候,有些類(比如Presenter類)是不需要使用註解來標識的,但是使用APT技術後,只能通過註解來找到目標類生成對應的代理類解析MMVPAction後,再呼叫目標類的方法或修改其屬性,所這裡就新增了了一個註解,MMVPActionProcessor,這個註解在mmvpannotation moudle下

    /**
     * 包名:com.ykbjson.lib.mmvp.annotation
     * 描述:MMVPAction處理類需要的註解
     * 建立者:yankebin
     * 日期:2018/5/7
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.CLASS)
    public @interface MMVPActionProcessor {
    }
    
  3. 在MMVP專案裡,APT技術在編譯時生成的類,需要能夠直接呼叫或通過目標類的例項呼叫目標類(即添加了MMVPActionProcessor註解的類)的某些方法或者更改目標類的某些屬性(這就是和反射的差別之處,直接呼叫),因為IMMVPActionHandler介面是專門負責處理MMVPActin的介面,所以我們就讓編譯時生成的類也實現IMMVPActionHandler介面,方便處理MMVPAction後呼叫目標類的方法。

  4. 修改之後的專案結構有所變化,新增了mmvpannotation、mmvpcompiler兩個moudle

1.2 APT+JavaPoet輸出目標類

其實這裡真沒什麼好說的,只要大家去看了我前面說的那幾篇文章(APT+JavaPoet),加上我程式碼裡的註釋,一定很輕易地就可以看懂了。
我們來看一下這個生成目標類的MMVPAnnotationProcessor

/**
 * 包名:com.ykbjson.lib.mmvp.compiler
 * 描述:mmvp{@link MMVPActionProcessor}註解處理器
 * 建立者:yankebin
 * 日期:2018/5/4
 */
@AutoService(Processor.class)
public class MMVPAnnotationProcessor extends AbstractProcessor {
    private static final String CLASS_NAME_SUFFIX = "_MMVPActionProcessor";
    private static final String METHOD_NAME_SUFFIX = "_Process";
    private static final String OVERRIDE_METHOD_NAME_HANDLE_ACTION = "handleAction";
    private static final String OVERRIDE_METHOD_NAME_GET = "get";

    private Elements elementUtils;

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        parseExecutor(roundEnv);
        return true;
    }

    private void parseExecutor(RoundEnvironment roundEnv) {
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(MMVPActionProcessor.class);
        for (Element element : elements) {
            // 判斷是否Class
            TypeElement typeElement = (TypeElement) element;
            //要生成的類
            TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(element.getSimpleName() +
                    CLASS_NAME_SUFFIX)//類名
                    .addSuperinterface(ClassName.get("com.ykbjson.lib.mmvp", "IMMVPActionHandler"))//實現的介面
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)//類修飾符
                    .addField(FieldSpec.builder(TypeName.get(typeElement.asType()), "target", Modifier.PRIVATE).build());//成員變數
            //生成的類的構造方法
            MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)//方法的修飾符
                    .addParameter(TypeName.get(typeElement.asType()), "target")//方法的引數
                    .addStatement("this.$N = $N", "target", "target");//方法的內容

            typeSpecBuilder.addMethod(constructorBuilder.build());//新增到類裡
            //target類目標方法獲取和引數解析後呼叫。這裡就是遍歷target目標類裡的方法,看那哪些方法添加了ActionProcess註解,
            // 然後,根據收到的Action,根據方法的註解引數,解析action裡的引數,然後呼叫目標類的方法。
            final List<? extends Element> members = elementUtils.getAllMembers(typeElement);
            final Map<String, String> methodMap = new LinkedHashMap<>();
            for (Element item : members) {
                ActionProcess methodAnnotation = item.getAnnotation(ActionProcess.class);
                if (methodAnnotation == null) {
                    continue;
                }

                final String generatedMethodName = item.getSimpleName().toString() + METHOD_NAME_SUFFIX;
                //儲存方法和註解的關係
                methodMap.put(methodAnnotation.value(), generatedMethodName);
                MethodSpec.Builder actionProcessMethodSpecBuilder = MethodSpec.methodBuilder(
                        generatedMethodName)
                        .returns(TypeName.BOOLEAN)
                        .addModifiers(Modifier.PUBLIC);

                //方法必要的唯一引數-MMVPAction
                actionProcessMethodSpecBuilder.addParameter(ParameterSpec.builder(
                        ClassName.get("com.ykbjson.lib.mmvp", "MMVPAction"), "action")
                        .build());
                //如果當前傳入的MMVPAction要執行的方法和當前方法不一致,中斷執行
                CodeBlock codeBlock = CodeBlock.builder().beginControlFlow("if(!\"" + methodAnnotation.value() +
                        "\".equals(action.getAction().getAction()))")
                        .addStatement("return false")
                        .endControlFlow()
                        .build();
                actionProcessMethodSpecBuilder.addCode(
                        codeBlock
                );

                //獲取和處理方法引數列表
                ExecutableElement method = (ExecutableElement) item;//方法
                List<? extends VariableElement> parameters = method.getParameters();//方法的引數
                StringBuilder parametersBuffer = new StringBuilder();
                //引數集合,需要引數才去解析引數,這下面生成的程式碼和MMVPArtist裡的execute方法裡的程式碼非常類似
                if (!parameters.isEmpty()) {
                    actionProcessMethodSpecBuilder.addStatement("$T  paramList= new ArrayList<>()", ArrayList.class);
                    if (methodAnnotation.needActionParam()) {
                        if (methodAnnotation.needTransformAction()) {
                            actionProcessMethodSpecBuilder.addStatement(
                                    "action = action.transform()"
                            );
                        }

                        actionProcessMethodSpecBuilder.addStatement(
                                "paramList.add(action)"
                        );
                    }
                    if (methodAnnotation.needActionParams()) {
                        actionProcessMethodSpecBuilder.addStatement(
                                //這裡其實可以使用JavaPoet的beginControlFlow來優雅地實現for迴圈
                                "if(null != action.getParams() && !action.getParams().isEmpty()) {\n" +
                                        "for (String key : action.getParams().keySet()) {\n" +
                                        "     paramList.add(action.getParam(key));\n" +
                                        "}" +
                                        "}"
                        );
                    }

                    for (int i = 0; i < parameters.size(); i++) {
                        parametersBuffer.append(
                                "(" + parameters.get(i).asType() + ")" + "paramList.get(" + i + ")");
                        if (i != parameters.size() - 1) {
                            parametersBuffer.append(",");
                        }
                    }
                }
                //這裡生成的程式碼類似 target.findViewById(-122443433)
                actionProcessMethodSpecBuilder.addStatement("target." +
                        method.getSimpleName().toString() +
                        "(" + parametersBuffer.toString() + ")");

                actionProcessMethodSpecBuilder.addStatement("return true");
                typeSpecBuilder.addMethod(actionProcessMethodSpecBuilder.build());
            }
            //過載IMMVPActionHandler方法handleAction
            MethodSpec.Builder overrideMethodSpecBuilder = MethodSpec.methodBuilder(OVERRIDE_METHOD_NAME_HANDLE_ACTION)
                    .addAnnotation(Override.class)
                    .addModifiers(Modifier.PUBLIC)
                    .returns(TypeName.BOOLEAN);

            overrideMethodSpecBuilder.addParameter(ParameterSpec.builder(
                    ClassName.get("com.ykbjson.lib.mmvp", "MMVPAction"), "action")
                    .addAnnotation(ClassName.get("android.support.annotation", "NonNull"))
                    .build());
            //由於無法預知即將呼叫的方法,只能把重寫的方法全部執行一遍,重寫方法裡有判斷可以避免錯誤執行,並且只要有某個方法返回了true,後續方法將不再執行.
            // 或許這樣也不比反射執行的風險小吧.
            int index = 0;
            StringBuilder resultBuilder = new StringBuilder();
            for (String key : methodMap.keySet()) {
                resultBuilder.append(methodMap.get(key) + "(action)" + (index != methodMap.keySet().size() - 1 ? "||" : ""));
                index++;
            }
            overrideMethodSpecBuilder.addStatement("return " + (resultBuilder.length() == 0 ? "false" : resultBuilder.toString()));
            typeSpecBuilder.addMethod(overrideMethodSpecBuilder.build());

            //過載IMMVPActionHandler方法get
            overrideMethodSpecBuilder = MethodSpec.methodBuilder(OVERRIDE_METHOD_NAME_GET)
                    .addAnnotation(Override.class)
                    .addModifiers(Modifier.PUBLIC)
                    .returns(ClassName.get("com.ykbjson.lib.mmvp", "IMMVPActionHandler"));
            overrideMethodSpecBuilder.addStatement("return this");
            typeSpecBuilder.addMethod(overrideMethodSpecBuilder.build());

            //生成java檔案
            JavaFile javaFile = JavaFile.builder(getPackageName(typeElement), typeSpecBuilder.build())
                    .addFileComment(" Generated code from MMVP. Do not modify! ")
                    .build();
            try {
                javaFile.writeTo(processingEnv.getFiler());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> supportedAnnotationTypes = new LinkedHashSet<>();
        supportedAnnotationTypes.add(MMVPActionProcessor.class.getCanonicalName());
        return supportedAnnotationTypes;
    }

    private String getPackageName(TypeElement type) {
        return elementUtils.getPackageOf(type).getQualifiedName().toString();
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        elementUtils = processingEnv.getElementUtils();
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_7;
    }
}

這個類裡面如果一定要細講的話,那講得很多的就是java註解語法基礎和JavaPoet使用方法了,但是這並不是本篇文章的討論範圍,也不是大家的難點所在,所以就不贅述了,總之,這裡就是在編譯時為所有添加了MMVPActionProcessor註解的類都生成一個對應的類,生成的這個類可以直接操作添加了MMVPActionProcessor註解的類裡的方法或屬性,從而用直接呼叫的方式替代反射呼叫的方式。

二、效果測試和比對

我們以mmvpsample裡的LoginPresenter為例,看一看這個類生成的類到底是如何呼叫LoginPresenter裡的方法的。

LoginPresenter

/**
 * 包名:com.ykbjson.app.hvp.login
 * 描述:登入邏輯處理
 * 建立者:yankebin
 * 日期:2018/4/13
 */
@MMVPActionProcessor
public class LoginPresenter implements LoginContract.ILoginPresenter {
    public static final String ACTION_DO_LOGIN = "LoginPresenter.action.ACTION_DO_LOGIN";
    public static final String ACTION_NOTIFY_LOGIN_FAILED = "LoginPresenter.action.ACTION_NOTIFY_LOGIN_FAILED";
    public static final String ACTION_NOTIFY_LOGIN_SUCCESS = "LoginPresenter.action.ACTION_NOTIFY_LOGIN_SUCCESS";

    private LoginRepository repository = new LoginRepository();

    @ActionProcess(value = ACTION_DO_LOGIN, needActionParam = true, needTransformAction = true)
    @Override
    public void doLogin(final MMVPAction action) {
        //引數校驗
        String mobile = action.getParam("mobile");
        String password = action.getParam("password");
        if (TextUtils.isEmpty(mobile)) {
            action.getAction().setAction(ACTION_NOTIFY_LOGIN_FAILED);
            action.clearParam().putParam("error", "登入賬號無效").send();
            return;
        }
        if (TextUtils.isEmpty(password)) {
            action.getAction().setAction(ACTION_NOTIFY_LOGIN_FAILED);
            action.clearParam().putParam("error", "登入密碼無效").send();
            return;
        }

        repository.doLogin(mobile, password, new IMMVPOnDataCallback<LoginResult>() {
            @Override
            public void onSuccess(LoginResult data) {
                action.getAction().setAction(ACTION_NOTIFY_LOGIN_SUCCESS);
                action.clearParam().putParam("loginResult", data).send();
            }

            @Override
            public void onError(String msg) {
                action.getAction().setAction(ACTION_NOTIFY_LOGIN_FAILED);
                action.clearParam().putParam("error", msg).send();
            }
        });
    }

    @Override
    public boolean handleAction(@NonNull MMVPAction action) {
        switch (action.getAction().getAction()) {
            case ACTION_DO_LOGIN:
                doLogin(action.transform());
                break;
        }

        return true;
    }

    @NonNull
    @Override
    public IMMVPActionHandler get() {
        return this;
    }
}   

只需要在LoginPresenter上加上MMVPActionProcessor註解,rebuild project,然後到mmvpsample/build/generated/source/debug(或release,根據你build的環境)/LoginPresenter所在包路徑/目錄下,會有一個生成的類:

LoginPresenter_MMVPActionProcessor

//  Generated code from MMVP. Do not modify! 
package com.ykbjson.app.hvp.login;

import android.support.annotation.NonNull;
import com.ykbjson.lib.mmvp.IMMVPActionHandler;
import com.ykbjson.lib.mmvp.MMVPAction;
import java.lang.Override;
import java.util.ArrayList;

public final class LoginPresenter_MMVPActionProcessor implements IMMVPActionHandler {
  private LoginPresenter target;

  public LoginPresenter_MMVPActionProcessor(LoginPresenter target) {
    this.target = target;
  }

  public boolean doLogin_Process(MMVPAction action) {
    if(!"LoginPresenter.action.ACTION_DO_LOGIN".equals(action.getAction().getAction())) {
      return false;
    }
    ArrayList  paramList= new ArrayList<>();
    action = action.transform();
    paramList.add(action);
    target.doLogin((com.ykbjson.lib.mmvp.MMVPAction)paramList.get(0));
    return true;
  }

  @Override
  public boolean handleAction(@NonNull MMVPAction action) {
    return doLogin_Process(action);
  }

  @Override
  public IMMVPActionHandler get() {
    return this;
  }
}

生成這個類的目的,文章開篇就說了,就是替代原來依靠反射方式執行某個方法的方案。原來MMVPArtist的處理邏輯是,在接收到一個MMVPAction後,經過層層查詢和解析,如果有符合條件的目標物件需要解析這個MMVPAction,就會執行execute方法

    /**
     * 執行action裡目標類需要執行的方法
     *
     * @param action {@link MMVPAction}
     * @param find   {@link MMVPView}或{@link MMVPPresenter}
     * @return
     */
    private static boolean execute(MMVPAction action, IMMVPActionHandler find) {
        Method executeMethod = findRegisterMMVPActionMethod(action);
        if (null == executeMethod) {
            if (enableLog) {
                Log.d(TAG, " Find " + find.getClass().getName() + "'s execute method failure");
            }
            return false;
        }
        if (enableLog) {
            Log.d(TAG, " Find  method " + find.getClass().getName() + "." + executeMethod.getName() + " success");
        }

        List<Object> paramList = new ArrayList<>();
        ActionProcess methodAnnotation = executeMethod.getAnnotation(ActionProcess.class);
        if (methodAnnotation.needActionParam()) {
            if (methodAnnotation.needTransformAction()) {
                action = action.transform();
            }
            paramList.add(action);
        }
        if (methodAnnotation.needActionParams() && null != action.getParams() && !action.getParams().isEmpty()) {
            for (String key : action.getParams().keySet()) {
                paramList.add(action.getParam(key));
            }
        }
        Object[] params = paramList.isEmpty() ? null : paramList.toArray();
        try {
            executeMethod.setAccessible(true);
            executeMethod.invoke(find, params);
            if (enableLog) {
                Log.d(TAG, " Execute "
                        + find.getClass().getName() + "." + executeMethod.getName() + " success");
            }
            return true;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            if (enableLog) {
                Log.d(TAG, " Execute "
                        + action.getTargetClass().getName() + "." + executeMethod.getName() + " failure", e);
            }
        } catch (InvocationTargetException e) {
            e.printStackTrace();
            if (enableLog) {
                Log.d(TAG, " Execute "
                        + action.getTargetClass().getName() + "." + executeMethod.getName() + " failure", e);
            }
        }

        return false;
    }

通過程式碼大家可以看到,這裡面最終目標類的方法的呼叫是使用的反射方式,現在我們用APT技術就是想不使用反射的方式也能呼叫目標類的方法。

當我們有了用APT技術生成的類時,execute方法可以修改如下,這裡是重新寫了一個方法,以示區分

    /**
     * 執行action裡目標類需要執行的方法
     *
     * @param action {@link MMVPAction}
     * @param target 要執行action的類
     * @return
     */
    private static boolean executeByApt(@NonNull MMVPAction action, @NonNull Object target) {
        IMMVPActionHandler executor = createExecutor(target);
        return executor.handleAction(action);
    }

是不是很好奇executor到底是誰?我們接著看一下createExecutor方法

    /**
     * 建立{@link IMMVPActionHandler}
     *
     * @param target 當前關聯的目標物件
     * @return
     */
    private static IMMVPActionHandler createExecutor(@NonNull Object target) {
        Class<?> targetClass = target.getClass();
        if (enableLog) {
            Log.d(TAG, "Looking up executor for " + targetClass.getName());
        }
        Constructor<? extends IMMVPActionHandler> constructor = findExecutorConstructorForClass(targetClass);

        if (constructor == null) {
            return IMMVPActionHandler.EMPTY;
        }

        //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
        try {
            return constructor.newInstance(target);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Unable to invoke " + constructor, e);
        } catch (InstantiationException e) {
            throw new RuntimeException("Unable to invoke " + constructor, e);
        } catch (InvocationTargetException e) {
            Throwable cause = e.getCause();
            if (cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            }
            if (cause instanceof Error) {
                throw (Error) cause;
            }
            throw new RuntimeException("Unable to create executor instance.", cause);
        }
    } 

這裡根據target,通過findExecutorConstructorForClass方法,找到了 LoginPresenter_MMVPActionProcessor 的構造方法,生成了LoginPresenter_MMVPActionProcessor的例項。

    /**
     * 建立{@link IMMVPActionHandler}
     *
     * @param cls 當前關聯的目標類
     * @return
     */
    @Nullable
    @CheckResult
    @UiThread
    private static Constructor<? extends IMMVPActionHandler> findExecutorConstructorForClass(Class<?> cls) {
        Constructor<? extends IMMVPActionHandler> executorCtor = EXECUTORS.get(cls);
        if (executorCtor != null) {
            if (enableLog) {
                Log.d(TAG, "Cached in executor map.");
            }
            return executorCtor;
        }
        String clsName = cls.getName();
        if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
            if (enableLog) {
                Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
            }
            return null;
        }
        try {
            Class<?> executorClass = cls.getClassLoader().loadClass(clsName + "_MMVPActionProcessor");
            //noinspection unchecked
            executorCtor = (Constructor<? extends IMMVPActionHandler>) executorClass.getConstructor(cls);
            if (enableLog) {
                Log.d(TAG, "HIT: Loaded executor class and constructor.");
            }
        } catch (ClassNotFoundException e) {
            if (enableLog) {
                Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
            }
            executorCtor = findExecutorConstructorForClass(cls.getSuperclass());
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("Unable to find executor constructor for " + clsName, e);
        }
        EXECUTORS.put(cls, executorCtor);
        return executorCtor;
    }

如果大家研究過ButterKnife的原始碼,看到這裡,是否有一種似曾相識的感覺?

現在有了LoginPresenter_MMVPActionProcessor的例項,然後呼叫其handleAction方法,我們回過去看看LoginPresenter_MMVPActionProcessor的handleAction方法,呼叫了自身的doLogin_Process方法,然後再看doLogin_Process方法,是不是呼叫了LoginPresenter的doLogin方法?你肯定會很好奇,LoginPresenter_MMVPActionProcessor怎麼會這麼智慧,直接就知道要呼叫LoginPresenter的doLogin方法,其實它一點都不智慧,如果LoginPresenter有N個方法添加了ActionProcess註解,那麼LoginPresenter_MMVPActionProcessor的handleAction方法就會是這個樣子

  @Override
  public boolean handleAction(@NonNull MMVPAction action) {
    return method1(action)||method2(action)||method3(action)||...||methodN(action);
  }

所以我在MMVPAnnotationProcessor裡有這樣一句註釋:“由於無法預知即將呼叫的方法,只能把重寫的方法全部執行一遍,重寫方法裡有判斷可以避免錯誤執行,並且只要有某個方法返回了true,後續方法將不再執行。或許這樣也不比反射執行的風險小吧”。這就是在我已經快要放棄用APT技術解決反射問題時想到的一個笨辦法,雖然解決了我遇到的問題,但是我內心是拒絕這樣去實現的。

為什麼說無法預知即將呼叫的方法?因為這些程式碼都是我主動去生成的,即便我知道這裡一定有一個handleAction方法,其引數MMVPAction,但是MMVPAction在我面前他就是一個字串,我無法得到裡面的內容,也就無法根據MMVPAction的action精準匹配要執行的方法。

當然,如果瞭解我MMVP專案的童鞋肯定會發現一個更簡單的方法,那就是我的View和Presenter都實現了IMMVPActionHandler介面的,LoginPresenter具體實現了handleAction方法,所以我們的LoginPresenter_MMVPActionProcessor的handleAction方法就可以簡化成這個樣子

      @Override
      public boolean handleAction(@NonNull MMVPAction action) {
        return target.handleAction(action);
      }

甚至連反射和這裡的APT技術都不需要,直接在MMVPArtist的handleActionFromView或handleActionFromPresenter方法裡,直接呼叫IMMVPActionHandler的handleAction方法。只是這樣做的前提是,Presenter和View都必須正確實現handleAction方法。

關於用APT技術解決反射呼叫效能損耗的問題,以及對專案的改造,大概就是這個樣子啦,如果看文章覺得不清楚很晦澀,那就去文章末尾貼出的專案地址檢視原始碼,或許會好一些吧。如果大家有什麼意見或建議,希望各位不吝賜教,感謝!

三、結語

今天呢,我們一起探討了用APT技術解決反射呼叫效能損耗的問題,雖然結果並不完美,甚至可以說的上是有點牽強,但是,這都不重要,重要的是我們又學會了用一些不同的技術解決相同的問題,用不同的思維方式去思考同一個問題,我想,這應該是值得的。

通過一次次的寫部落格,我發現一個道理,沒有什麼東西是一蹴而就的,你們現在看到原始碼,寥寥數行,可是我都不記得自己改了多少次,才僅僅有這樣的結果。這或許是一種磨礪,也或許是一個程式猿的樂趣吧。

最後,當然是所有程式猿都喜歡的程式碼啦