diff --git a/compiler/src/main/java/info/dourok/compiler/generator/BuilderGenerator.java b/compiler/src/main/java/info/dourok/compiler/generator/BuilderGenerator.java index 6287698..8599bf7 100644 --- a/compiler/src/main/java/info/dourok/compiler/generator/BuilderGenerator.java +++ b/compiler/src/main/java/info/dourok/compiler/generator/BuilderGenerator.java @@ -142,7 +142,8 @@ private MethodSpec buildResultCallback(ResultModel result) throws IOException { .returns(builderWithParameter) .addModifiers(Modifier.PUBLIC) .addParameter(result.getConsumerType(), result.getConsumerName()) - .addStatement("getConsumer().$L = $L", result.getConsumerName(), result.getConsumerName()) + .addStatement( + "getConsumer().set$LConsumer($L)", result.getCapitalizeName(), result.getConsumerName()) .addStatement("return this") .build(); } diff --git a/compiler/src/main/java/info/dourok/compiler/generator/ConsumerGenerator.java b/compiler/src/main/java/info/dourok/compiler/generator/ConsumerGenerator.java index 8a84b65..6ea4dd2 100644 --- a/compiler/src/main/java/info/dourok/compiler/generator/ConsumerGenerator.java +++ b/compiler/src/main/java/info/dourok/compiler/generator/ConsumerGenerator.java @@ -1,6 +1,7 @@ package info.dourok.compiler.generator; import android.content.Intent; +import com.google.common.base.CaseFormat; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.JavaFile; @@ -91,6 +92,7 @@ protected TypeSpec generate() { "return process$L($L,$L)", result.getCapitalizeName(), "activity", "intent"); try { consumer.addField(buildField(result)); + consumer.addMethod(buildSetter(result)); } catch (IOException e) { // TODO 生成 consumer 接口失败 error("create consumer failed:" + e.getMessage()); @@ -109,7 +111,17 @@ protected TypeSpec generate() { } private FieldSpec buildField(ResultModel result) throws IOException { - return FieldSpec.builder(result.getConsumerType(), result.getConsumerName()).build(); + return FieldSpec.builder(result.getConsumerType(), result.getConsumerName()) + .addModifiers(Modifier.PRIVATE) + .build(); + } + + private MethodSpec buildSetter(ResultModel result) throws IOException { + return MethodSpec.methodBuilder("set" + result.getCapitalizeName() + "Consumer") + .addParameter(result.getConsumerType(), "consumer") + .addStatement("beforeAdd($L)","consumer") + .addStatement("this.$L = $L", result.getConsumerName(), "consumer") + .build(); } private MethodSpec buildResultProcessor(ResultModel result) { @@ -120,7 +132,7 @@ private MethodSpec buildResultProcessor(ResultModel result) { .addParameter(TypeVariableName.get("A"), "activity") .addParameter(Intent.class, "intent") .beginControlFlow("if($L != null)", result.getConsumerName()) - .addStatement("doCheck(activity,$L)", result.getConsumerName()); + .addStatement("beforeExecute(activity,$L)", result.getConsumerName()); if (!result.getParameters().isEmpty()) { StringBuilder literal = new StringBuilder(result.getConsumerName()).append(".accept("); String[] names = new String[result.getParameters().size()]; @@ -141,6 +153,7 @@ private MethodSpec buildResultProcessor(ResultModel result) { } else { builder.addStatement("$L.run()", result.getConsumerName()); } + builder.addStatement("afterExecute(activity,$L)", result.getConsumerName()); builder.addStatement("return true"); builder.endControlFlow(); builder.beginControlFlow("else").addStatement("return false").endControlFlow(); diff --git a/compiler/src/test/groovy/info/dourok/compiler/ResultParameterSpec.groovy b/compiler/src/test/groovy/info/dourok/compiler/ResultParameterSpec.groovy index a4c2236..18c74b8 100644 --- a/compiler/src/test/groovy/info/dourok/compiler/ResultParameterSpec.groovy +++ b/compiler/src/test/groovy/info/dourok/compiler/ResultParameterSpec.groovy @@ -34,15 +34,22 @@ class ResultParameterSpec extends Specification { }""") .source() def consumer = Source.consumer() - .field("BiConsumer textConsumer;", + .field("private BiConsumer textConsumer;", ["info.dourok.esactivity.function.BiConsumer", ArrayList, Character]) + .method(""" + void setTextConsumer(BiConsumer consumer) { + beforeAdd(consumer); + this.textConsumer = consumer; + } + """) .method(""" private boolean processText(A activity,Intent intent) { if(textConsumer != null) { - doCheck(activity, textConsumer); + beforeExecute(activity, textConsumer); ArrayList ids = RefManager.getInstance().get(intent,"ids"); Character name = RefManager.getInstance().get(intent,"name"); textConsumer.accept(ids,name); + afterExecute(activity, textConsumer); return true; } else { diff --git a/compiler/src/test/groovy/info/dourok/compiler/ResultSetSpec.groovy b/compiler/src/test/groovy/info/dourok/compiler/ResultSetSpec.groovy index ede7e3d..55fe672 100644 --- a/compiler/src/test/groovy/info/dourok/compiler/ResultSetSpec.groovy +++ b/compiler/src/test/groovy/info/dourok/compiler/ResultSetSpec.groovy @@ -29,12 +29,12 @@ class ResultSetSpec extends Specification { .hasConsumer() .method(""" public EmptyActivityBuilder forDate(Consumer dateConsumer) { - getConsumer().dateConsumer = dateConsumer; + getConsumer().setDateConsumer(dateConsumer); return this; }""", ["info.dourok.esactivity.function.Consumer", Long]) .method(""" public EmptyActivityBuilder forText(BiConsumer textConsumer) { - getConsumer().textConsumer = textConsumer; + getConsumer().setTextConsumer(textConsumer); return this; }""", ["info.dourok.esactivity.function.BiConsumer", ArrayList, Character]) .source() @@ -66,14 +66,21 @@ class ResultSetSpec extends Specification { }""") .source() def consumer = Source.consumer() - .field("Consumer dateConsumer;", + .field("private Consumer dateConsumer;", ["info.dourok.esactivity.function.Consumer", Long]) + .method(""" + void setDateConsumer(Consumer consumer) { + beforeAdd(consumer); + this.dateConsumer = consumer; + } + """) .method(""" private boolean processDate(A activity,Intent intent) { if(dateConsumer != null) { - doCheck(activity, dateConsumer); + beforeExecute(activity, dateConsumer); Long date = intent.getLongExtra("date",0); dateConsumer.accept(date); + afterExecute(activity, dateConsumer); return true; } else { @@ -81,15 +88,22 @@ class ResultSetSpec extends Specification { } } """) - .field("BiConsumer textConsumer;", + .field("private BiConsumer textConsumer;", ["info.dourok.esactivity.function.BiConsumer", ArrayList, Character]) + .method(""" + void setTextConsumer(BiConsumer consumer) { + beforeAdd(consumer); + this.textConsumer = consumer; + } + """) .method(""" private boolean processText(A activity,Intent intent) { if(textConsumer != null) { - doCheck(activity, textConsumer); + beforeExecute(activity, textConsumer); ArrayList ids = RefManager.getInstance().get(intent,"ids"); Character name = intent.getCharExtra("name",(char)0); textConsumer.accept(ids,name); + afterExecute(activity, textConsumer); return true; } else { diff --git a/compiler/src/test/groovy/info/dourok/compiler/ResultSpec.groovy b/compiler/src/test/groovy/info/dourok/compiler/ResultSpec.groovy index d3c2f78..8421098 100644 --- a/compiler/src/test/groovy/info/dourok/compiler/ResultSpec.groovy +++ b/compiler/src/test/groovy/info/dourok/compiler/ResultSpec.groovy @@ -19,7 +19,7 @@ class ResultSpec extends Specification { .hasConsumer() .method(""" public EmptyActivityBuilder forText(Runnable textConsumer) { - getConsumer().textConsumer = textConsumer; + getConsumer().setTextConsumer(textConsumer); return this; }""", [Runnable]) .source() @@ -36,12 +36,19 @@ class ResultSpec extends Specification { }""") .source() def consumer = Source.consumer() - .field("Runnable textConsumer;", [Runnable]) + .field("private Runnable textConsumer;", [Runnable]) + .method(""" + void setTextConsumer(Runnable consumer) { + beforeAdd(consumer); + this.textConsumer = consumer; + } + """) .method(""" private boolean processText(A activity, Intent intent) { if(textConsumer != null) { - doCheck(activity, textConsumer); + beforeExecute(activity, textConsumer); textConsumer.run(); + afterExecute(activity, textConsumer); return true; } else { @@ -88,7 +95,7 @@ class ResultSpec extends Specification { .hasConsumer() .method(""" public EmptyActivityBuilder forText(Runnable textConsumer) { - getConsumer().textConsumer = textConsumer; + getConsumer().setTextConsumer(textConsumer); return this; }""", [Runnable]) .source() @@ -105,12 +112,19 @@ class ResultSpec extends Specification { }""") .source() def consumer = Source.consumer() - .field("Runnable textConsumer;", [Runnable]) + .field("private Runnable textConsumer;", [Runnable]) + .method(""" + void setTextConsumer(Runnable consumer) { + beforeAdd(consumer); + this.textConsumer = consumer; + } + """) .method(""" private boolean processText(A activity, Intent intent) { if(textConsumer != null) { - doCheck(activity, textConsumer); + beforeExecute(activity, textConsumer); textConsumer.run(); + afterExecute(activity, textConsumer); return true; } else { @@ -157,7 +171,7 @@ class ResultSpec extends Specification { .hasConsumer() .method(""" public EmptyActivityBuilder forText(BiConsumer textConsumer) { - getConsumer().textConsumer = textConsumer; + getConsumer().setTextConsumer(textConsumer); return this; }""", ["info.dourok.esactivity.function.BiConsumer", ArrayList, Character]) .source() @@ -177,15 +191,22 @@ class ResultSpec extends Specification { }""") .source() def consumer = Source.consumer() - .field("BiConsumer textConsumer;", + .field("private BiConsumer textConsumer;", ["info.dourok.esactivity.function.BiConsumer", ArrayList, Character]) + .method(""" + void setTextConsumer(BiConsumer consumer) { + beforeAdd(consumer); + this.textConsumer = consumer; + } + """) .method(""" private boolean processText(A activity,Intent intent) { if(textConsumer != null) { - doCheck(activity, textConsumer); + beforeExecute(activity, textConsumer); ArrayList ids = RefManager.getInstance().get(intent,"ids"); Character name = intent.getCharExtra("name",(char)0); textConsumer.accept(ids,name); + afterExecute(activity, textConsumer); return true; } else { @@ -232,7 +253,7 @@ class ResultSpec extends Specification { .hasConsumer() .method(""" public EmptyActivityBuilder forDate(Consumer dateConsumer) { - getConsumer().dateConsumer = dateConsumer; + getConsumer().setDateConsumer(dateConsumer); return this; }""", ["info.dourok.esactivity.function.Consumer", Long]) .source() @@ -251,14 +272,21 @@ class ResultSpec extends Specification { }""") .source() def consumer = Source.consumer() - .field("Consumer dateConsumer;", + .field("private Consumer dateConsumer;", ["info.dourok.esactivity.function.Consumer", Long]) + .method(""" + void setDateConsumer(Consumer consumer) { + beforeAdd(consumer); + this.dateConsumer = consumer; + } + """) .method(""" private boolean processDate(A activity,Intent intent) { if(dateConsumer != null) { - doCheck(activity, dateConsumer); + beforeExecute(activity, dateConsumer); long date = intent.getLongExtra("date",0); dateConsumer.accept(date); + afterExecute(activity, dateConsumer); return true; } else { @@ -305,7 +333,7 @@ class ResultSpec extends Specification { .hasConsumer() .method(""" public EmptyActivityBuilder forMap(Consumer>> mapConsumer) { - getConsumer().mapConsumer = mapConsumer; + getConsumer().setMapConsumer(mapConsumer); return this; }""", ["info.dourok.esactivity.function.Consumer", Map, String, ArrayList, Integer]) @@ -325,14 +353,21 @@ class ResultSpec extends Specification { }""") .source() def consumer = Source.consumer() - .field("Consumer>> mapConsumer;", + .field("private Consumer>> mapConsumer;", ["info.dourok.esactivity.function.Consumer", Map, String, ArrayList, Integer]) + .method(""" + void setMapConsumer(Consumer>> consumer) { + beforeAdd(consumer); + this.mapConsumer = consumer; + } + """) .method(""" private boolean processMap(A activity,Intent intent) { if(mapConsumer != null) { - doCheck(activity, mapConsumer); + beforeExecute(activity, mapConsumer); Map> map = RefManager.getInstance().get(intent,"map"); mapConsumer.accept(map); + afterExecute(activity, mapConsumer); return true; } else { @@ -384,12 +419,12 @@ class ResultSpec extends Specification { .hasConsumer() .method(""" public EmptyActivityBuilder forDate(Consumer dateConsumer) { - getConsumer().dateConsumer = dateConsumer; + getConsumer().setDateConsumer(dateConsumer); return this; }""", ["info.dourok.esactivity.function.Consumer", Long]) .method(""" public EmptyActivityBuilder forText(BiConsumer textConsumer) { - getConsumer().textConsumer = textConsumer; + getConsumer().setTextConsumer(textConsumer); return this; }""", ["info.dourok.esactivity.function.BiConsumer", ArrayList, Character]) .source() @@ -421,14 +456,21 @@ class ResultSpec extends Specification { }""") .source() def consumer = Source.consumer() - .field("Consumer dateConsumer;", + .field("private Consumer dateConsumer;", ["info.dourok.esactivity.function.Consumer", Long]) + .method(""" + void setDateConsumer(Consumer consumer) { + beforeAdd(consumer); + this.dateConsumer = consumer; + } + """) .method(""" private boolean processDate(A activity,Intent intent) { if(dateConsumer != null) { - doCheck(activity, dateConsumer); + beforeExecute(activity, dateConsumer); long date = intent.getLongExtra("date",0); dateConsumer.accept(date); + afterExecute(activity, dateConsumer); return true; } else { @@ -436,15 +478,22 @@ class ResultSpec extends Specification { } } """) - .field("BiConsumer textConsumer;", + .field("private BiConsumer textConsumer;", ["info.dourok.esactivity.function.BiConsumer", ArrayList, Character]) + .method(""" + void setTextConsumer(BiConsumer consumer) { + beforeAdd(consumer); + this.textConsumer = consumer; + } + """) .method(""" private boolean processText(A activity,Intent intent) { if(textConsumer != null) { - doCheck(activity, textConsumer); + beforeExecute(activity, textConsumer); ArrayList ids = RefManager.getInstance().get(intent,"ids"); Character name = intent.getCharExtra("name",(char)0); textConsumer.accept(ids,name); + afterExecute(activity, textConsumer); return true; } else { diff --git a/compiler/src/test/resources/mock/BaseResultConsumer.java b/compiler/src/test/resources/mock/BaseResultConsumer.java index e8b9b88..94157ee 100644 --- a/compiler/src/test/resources/mock/BaseResultConsumer.java +++ b/compiler/src/test/resources/mock/BaseResultConsumer.java @@ -16,7 +16,11 @@ public final void accept(Activity context, Integer result, Intent intent) { // } - protected void doCheck(Activity activity, Object lambda) {} + public void beforeAdd(Object closure) {} + + public void beforeExecute(Activity activity, Object closure) {} + + public void afterExecute(Activity activity, Object closure) {} protected boolean handleResult(A context, int result, Intent intent) { return false; diff --git a/library/build.gradle b/library/build.gradle index 1e00972..674a1de 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -32,7 +32,6 @@ dependencies { implementation deps.support.annotations implementation deps.support.fragment - implementation 'com.android.support:support-v4:26.1.0' androidTestImplementation 'com.android.support:appcompat-v7:27.0.2' androidTestImplementation 'com.android.support.constraint:constraint-layout:1.0.2' androidTestAnnotationProcessor project(':compiler') diff --git a/library/src/androidTest/java/info/dourok/esactivity/CaptureInstrumentedTest.java b/library/src/androidTest/java/info/dourok/esactivity/CaptureInstrumentedTest.java index 5ffd5d2..9c07bd1 100644 --- a/library/src/androidTest/java/info/dourok/esactivity/CaptureInstrumentedTest.java +++ b/library/src/androidTest/java/info/dourok/esactivity/CaptureInstrumentedTest.java @@ -102,7 +102,6 @@ public void support_fragment_with_tag__ref_should_be_updated_after_activity_recr onView(withId(R.id.content)).check(matches(withText(text))); } - @Test public void method__ref_should_be_updated_after_activity_recreate() throws Exception { final String text = "text"; @@ -112,4 +111,63 @@ public void method__ref_should_be_updated_after_activity_recreate() throws Excep onView(withId(R.id.action_ok)).perform(click()); onView(withId(R.id.content)).check(matches(withText(text))); } + + @Test + public void view_with_id__ref_should_work() { + final String text = "text"; + onView(withText("captureViewWithId")).perform(click()); + + onView(withId(R.id.edit_text)).perform(replaceText(text)); + onView(withId(R.id.action_ok)).perform(click()); + onView(withId(R.id.content)).check(matches(withText(text))); + } + + @Test + public void fragment_with_id__ref_should_work() { + final String text = "text"; + onView(withText("captureFragmentWithId")).perform(click()); + + onView(withId(R.id.edit_text)).perform(replaceText(text)); + onView(withId(R.id.action_ok)).perform(click()); + onView(withId(R.id.content)).check(matches(withText(text))); + } + + @Test + public void fragment_with_tag__ref_should_work() { + final String text = "text"; + onView(withText("captureFragmentWithTag")).perform(click()); + + onView(withId(R.id.edit_text)).perform(replaceText(text)); + onView(withId(R.id.action_ok)).perform(click()); + onView(withId(R.id.content)).check(matches(withText(text))); + } + + @Test + public void support_fragment_with_id__ref_should_work() { + final String text = "text"; + onView(withText("captureSupportFragmentWithId")).perform(click()); + + onView(withId(R.id.edit_text)).perform(replaceText(text)); + onView(withId(R.id.action_ok)).perform(click()); + onView(withId(R.id.content)).check(matches(withText(text))); + } + + @Test + public void support_fragment_with_tag__ref_should_work() { + final String text = "text"; + onView(withText("captureSupportFragmentWithTag")).perform(click()); + + onView(withId(R.id.edit_text)).perform(replaceText(text)); + onView(withId(R.id.action_ok)).perform(click()); + onView(withId(R.id.content)).check(matches(withText(text))); + } + + @Test + public void method__ref_should_work() throws Exception { + final String text = "text"; + onView(withText("methodRef")).perform(click()); + onView(withId(R.id.edit_text)).perform(replaceText(text)); + onView(withId(R.id.action_ok)).perform(click()); + onView(withId(R.id.content)).check(matches(withText(text))); + } } diff --git a/library/src/androidTest/java/info/dourok/esactivity/activity/CaptureTestActivity.java b/library/src/androidTest/java/info/dourok/esactivity/activity/CaptureTestActivity.java index 309cfa8..784863a 100644 --- a/library/src/androidTest/java/info/dourok/esactivity/activity/CaptureTestActivity.java +++ b/library/src/androidTest/java/info/dourok/esactivity/activity/CaptureTestActivity.java @@ -17,6 +17,7 @@ protected void onCreate(Bundle savedInstanceState) { textView = findViewById(R.id.content); if (savedInstanceState == null) { prepareFragmentWithTag(); + prepareSupportFragmentWithTag(); } } diff --git a/library/src/androidTest/res/layout/activity_test.xml b/library/src/androidTest/res/layout/activity_test.xml index 4106d09..e5d6f6b 100644 --- a/library/src/androidTest/res/layout/activity_test.xml +++ b/library/src/androidTest/res/layout/activity_test.xml @@ -32,7 +32,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="captureViewWithId" - android:text="captureView" + android:text="captureViewWithId" /> getConsumer() { return consumer; } + public void addClosureStateCallback(ClosureStateCallback callback) { + getConsumer().addClosureStateCallback(callback); + } + /** intent 应该在构造函数的时候就初始化 因为 Intent 可能会和 RefMap 绑定在一起 */ public void setIntent(Intent intent) { if (intentWrapper != null) { diff --git a/library/src/main/java/info/dourok/esactivity/BaseResultConsumer.java b/library/src/main/java/info/dourok/esactivity/BaseResultConsumer.java index bbd868c..63df076 100644 --- a/library/src/main/java/info/dourok/esactivity/BaseResultConsumer.java +++ b/library/src/main/java/info/dourok/esactivity/BaseResultConsumer.java @@ -1,14 +1,16 @@ package info.dourok.esactivity; import android.app.Activity; -import android.app.Fragment; import android.content.Intent; import android.support.annotation.Nullable; -import android.view.View; import info.dourok.esactivity.function.BiConsumer; import info.dourok.esactivity.function.Consumer; import info.dourok.esactivity.function.TriConsumer; -import java.lang.reflect.Field; +import info.dourok.esactivity.reference.ActivityReferenceUpdater; +import info.dourok.esactivity.reference.FragmentReferenceUpdater; +import info.dourok.esactivity.reference.ViewReferenceUpdater; +import java.util.ArrayList; +import java.util.List; /** * @author tiaolins @@ -16,110 +18,85 @@ * @date 2017/8/6 */ public class BaseResultConsumer - implements TriConsumer { + implements TriConsumer, ClosureStateCallback { private BiConsumer biConsumer; private Consumer okConsumer; private Consumer cancelConsumer; + private List callbackList = new ArrayList<>(4); + { + callbackList.add(new ActivityReferenceUpdater()); + callbackList.add(new FragmentReferenceUpdater()); + callbackList.add(new ViewReferenceUpdater()); + } + /** + * {@link MessengerFragment} 会调用 request code 相同的该方法。 + * + * @param context 请求者 Activity + * @param result 同 onActivityResult 方法 + * @param intent 同 onActivityResult 方法 + */ @Override public final void accept(Activity context, Integer result, @Nullable Intent intent) { A starter = (A) context; if (!handleResult(starter, result, intent)) { if (result == Activity.RESULT_OK && okConsumer != null) { - doCheck(context, result); + beforeExecute(context, okConsumer); okConsumer.accept(intent); + afterExecute(context, okConsumer); } if (result == Activity.RESULT_CANCELED && cancelConsumer != null) { - doCheck(context, cancelConsumer); + beforeExecute(context, cancelConsumer); cancelConsumer.accept(intent); + afterExecute(context, cancelConsumer); } if (biConsumer != null) { - doCheck(context, cancelConsumer); + beforeExecute(context, biConsumer); biConsumer.accept(result, intent); + afterExecute(context, biConsumer); } } RefManager.getInstance().clearRefs(intent); } void setBiConsumer(BiConsumer biConsumer) { + beforeAdd(biConsumer); this.biConsumer = biConsumer; } void setOkConsumer(Consumer okConsumer) { + beforeAdd(okConsumer); this.okConsumer = okConsumer; } void setCancelConsumer(Consumer cancelConsumer) { + beforeAdd(cancelConsumer); this.cancelConsumer = cancelConsumer; } - protected void doCheck(Activity activity, Object lambda) { - try { - // 可以先标记为是否有 Activity 引用 - checkActivityReference(activity, lambda); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (NoSuchFieldException e) { - e.printStackTrace(); + @Override + public void beforeAdd(Object closure) { + for (ClosureStateCallback callback : callbackList) { + callback.beforeAdd(closure); } } - /** 确保捕获了 Activity 实例的 lambda 对象引用的是最新的 Activity 实例,如果不是会尝试更新 Activity 引用 */ - private void checkActivityReference(Activity activity, Object lambda) - throws IllegalAccessException, NoSuchFieldException { - Class zlass = lambda.getClass(); - for (Field field : zlass.getDeclaredFields()) { - field.setAccessible(true); - // 对四种类型做特殊处理 1. Activity 2. Fragment 3. View 4. support fragment - if (Activity.class.isAssignableFrom(field.getType())) { - // Activity 直接替换 - Activity originActivity = (Activity) field.get(lambda); - if (originActivity != activity) { - // Activity 被重建了需要更新引用 - reassignFinalField(field, lambda, activity); - } - } else if (Fragment.class.isAssignableFrom(field.getType())) { - // Fragment - Fragment fragment = (Fragment) field.get(lambda); - // fragment detach 之后 getActivity 应返回为空 - if (fragment.getActivity() == null || fragment.getActivity() != activity) { - // 尝试通过 id 或者 tag 来查找 - Fragment newFragment = activity.getFragmentManager().findFragmentById(fragment.getId()); - if (newFragment == null) { - newFragment = activity.getFragmentManager().findFragmentByTag(fragment.getTag()); - } - if (newFragment != null) { - reassignFinalField(field, lambda, newFragment); - } else { - // TODO - throw new RuntimeException("Activity 重建后找不到 fragment:" + fragment); - } - } - } else if (View.class.isAssignableFrom(field.getType())) { - // View - View view = (View) field.get(lambda); - if (view.getContext() != activity) { - View newView = null; - if (view.getId() != View.NO_ID) { - newView = activity.findViewById(view.getId()); - } - if (newView != null) { - reassignFinalField(field, lambda, newView); - } else { - throw new RuntimeException("Activity 重建后找不到 View:" + view); - } - } - } - // TODO support fragment + + @Override + public void beforeExecute(Activity activity, Object closure) { + for (ClosureStateCallback callback : callbackList) { + callback.beforeExecute(activity, closure); + } + } + + @Override + public void afterExecute(Activity activity, Object closure) { + for (ClosureStateCallback callback : callbackList) { + callback.afterExecute(activity, closure); } } - private void reassignFinalField(Field field, Object object, Object value) - throws IllegalAccessException, NoSuchFieldException { - field.setAccessible(true); - // Field modifiersField = Field.class.getDeclaredField("modifiers"); - // modifiersField.setAccessible(true); - // modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); - field.set(object, value); + public void addClosureStateCallback(ClosureStateCallback callback) { + callbackList.add(callback); } protected boolean handleResult(A context, int result, Intent intent) { diff --git a/library/src/main/java/info/dourok/esactivity/ClosureStateCallback.java b/library/src/main/java/info/dourok/esactivity/ClosureStateCallback.java new file mode 100644 index 0000000..5a28e62 --- /dev/null +++ b/library/src/main/java/info/dourok/esactivity/ClosureStateCallback.java @@ -0,0 +1,27 @@ +package info.dourok.esactivity; + +import android.app.Activity; +import info.dourok.esactivity.reference.AbstractActivityReferenceUpdater; + +/** + * 参考 {@link AbstractActivityReferenceUpdater} + * @author tiaolins + * @date 2018/3/2. + */ +public interface ClosureStateCallback { + /** + * 在 closure 被添加前调用 + */ + void beforeAdd(Object closure); + + /** + * 在 closure 被调用前调用。onActivityResult 将会执行这个 lambda 表达式 + * @param activity starter activity + */ + void beforeExecute(Activity activity, Object closure); + + /** + * 在 closure 被调用后调用 + */ + void afterExecute(Activity activity, Object closure); +} diff --git a/library/src/main/java/info/dourok/esactivity/function/Function.java b/library/src/main/java/info/dourok/esactivity/function/Function.java new file mode 100644 index 0000000..778b82d --- /dev/null +++ b/library/src/main/java/info/dourok/esactivity/function/Function.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package info.dourok.esactivity.function; + +import java.util.Objects; + +/** + * Represents a function that accepts one argument and produces a result. + * + *

This is a functional interface whose functional method is + * {@link #apply(Object)}. + * + * @param the type of the input to the function + * @param the type of the result of the function + * @since 1.8 + */ +@FunctionalInterface +public interface Function { + + /** + * Applies this function to the given argument. + * + * @param t the function argument + * @return the function result + */ + R apply(T t); + /** + * Returns a function that always returns its input argument. + * + * @param the type of the input and output objects to the function + * @return a function that always returns its input argument + */ + static Function identity() { + return t -> t; + } +} diff --git a/library/src/main/java/info/dourok/esactivity/reference/AbstractActivityReferenceUpdater.java b/library/src/main/java/info/dourok/esactivity/reference/AbstractActivityReferenceUpdater.java new file mode 100644 index 0000000..8534900 --- /dev/null +++ b/library/src/main/java/info/dourok/esactivity/reference/AbstractActivityReferenceUpdater.java @@ -0,0 +1,72 @@ +package info.dourok.esactivity.reference; + +import android.app.Activity; +import info.dourok.esactivity.ClosureStateCallback; +import java.lang.reflect.Field; + +/** + * 用于更新 clousure 捕获的与 Activity 相关的对象的引用。 一些 UI 相关的组件,比如 View、Fragment 或 Activity 本身,在 Activity + * 重建后会被重新创建。 而 Closure 捕获的还是旧的对象引用, + * + * @author tiaolins + * @date 2018/3/2. + */ +public abstract class AbstractActivityReferenceUpdater implements ClosureStateCallback { + + @Override + public void beforeAdd(Object closure) {} + + @Override + public void beforeExecute(Activity activity, Object closure) { + Class zlass = closure.getClass(); + for (Field field : zlass.getDeclaredFields()) { + field.setAccessible(true); + if (isMatch(field)) { + try { + Object oldObject = field.get(closure); + Object object = findNewObject(activity, field, closure, oldObject); + if (object != null) { + reassignFinalField(field, closure, object); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + } + } + } + + /** + * 尝试找回新对象 + * + * @param activity 新的 Activity 对象 + * @param oldObject 旧对象 + * @return 找不到返回 null + */ + protected abstract Object findNewObject(Activity activity, Field field, Object closure, + Object oldObject); + + + /** + * 确定当前捕获的类型该更新器可以出来的类型。 + * + * @param field 用检查是否匹配的字段 + * @return 是该更新器可以处理的类型 + */ + protected abstract boolean isMatch(Field field); + + @Override + public void afterExecute(Activity activity, Object closure) {} + + /** 更新 final 引用的值 */ + void reassignFinalField(Field field, Object object, Object value) + throws IllegalAccessException, NoSuchFieldException { + field.setAccessible(true); + // ART 虚拟机不需要更新 modifier 就能直接修改 + // Field modifiersField = Field.class.getDeclaredField("modifiers"); + // modifiersField.setAccessible(true); + // modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + field.set(object, value); + } +} diff --git a/library/src/main/java/info/dourok/esactivity/reference/ActivityReferenceUpdater.java b/library/src/main/java/info/dourok/esactivity/reference/ActivityReferenceUpdater.java new file mode 100644 index 0000000..bf5feee --- /dev/null +++ b/library/src/main/java/info/dourok/esactivity/reference/ActivityReferenceUpdater.java @@ -0,0 +1,20 @@ +package info.dourok.esactivity.reference; + +import android.app.Activity; +import java.lang.reflect.Field; + +/** + * @author tiaolins + * @date 2018/3/3. + */ +public class ActivityReferenceUpdater extends AbstractActivityReferenceUpdater { + @Override + protected Object findNewObject(Activity activity, Field field, Object closure, Object oldObject) { + return activity; + } + + @Override + protected boolean isMatch(Field field) { + return Activity.class.isAssignableFrom(field.getType()); + } +} diff --git a/library/src/main/java/info/dourok/esactivity/reference/FragmentReferenceUpdater.java b/library/src/main/java/info/dourok/esactivity/reference/FragmentReferenceUpdater.java new file mode 100644 index 0000000..67bb5ca --- /dev/null +++ b/library/src/main/java/info/dourok/esactivity/reference/FragmentReferenceUpdater.java @@ -0,0 +1,90 @@ +package info.dourok.esactivity.reference; + +import android.app.Activity; +import android.app.Fragment; +import android.support.v4.app.FragmentActivity; +import info.dourok.esactivity.function.Function; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +import static android.view.View.NO_ID; + +/** + * @author tiaolins + * @date 2018/3/3. + */ +public class FragmentReferenceUpdater extends AbstractActivityReferenceUpdater { + private Map> map = new HashMap<>(4); + + /** 缓存 Fragment Finder. fragment makeInactive 之后状态会被清空,所以得提前对 Fragment 的 id 或 tag 进行保存。 */ + @Override + public void beforeAdd(Object closure) { + Class zlass = closure.getClass(); + for (Field field : zlass.getDeclaredFields()) { + field.setAccessible(true); + try { + Function fragmentFinder = null; + if (Fragment.class.isAssignableFrom(field.getType())) { + Fragment fragment = (Fragment) field.get(closure); + if (fragment.getId() != NO_ID) { + int id = fragment.getId(); + fragmentFinder = + (Activity activity) -> activity.getFragmentManager().findFragmentById(id); + } else if (fragment.getTag() != null) { + String tag = fragment.getTag(); + fragmentFinder = + (Activity activity) -> activity.getFragmentManager().findFragmentByTag(tag); + } + // Support Fragment + } else if (android.support.v4.app.Fragment.class.isAssignableFrom(field.getType())) { + android.support.v4.app.Fragment fragment = + (android.support.v4.app.Fragment) field.get(closure); + if (fragment.getId() != NO_ID) { + int id = fragment.getId(); + fragmentFinder = + (Activity activity) -> + ((FragmentActivity) activity).getSupportFragmentManager().findFragmentById(id); + } else if (fragment.getTag() != null) { + String tag = fragment.getTag(); + fragmentFinder = + (Activity activity) -> + ((FragmentActivity) activity) + .getSupportFragmentManager() + .findFragmentByTag(tag); + } + } + if (fragmentFinder != null) { + map.put(field, fragmentFinder); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + @Override + protected Object findNewObject(Activity activity, Field field, Object closure, Object oldObject) { + final boolean fragmentNeedUpdate = + oldObject instanceof Fragment + // Fragment detach 之后 Activity 应该为空 + && (((Fragment) oldObject).getActivity() == null + || ((Fragment) oldObject).getActivity() != activity); + final boolean supportFragmentNeedUpdate = + oldObject instanceof android.support.v4.app.Fragment + && (((android.support.v4.app.Fragment) oldObject).getActivity() == null + || ((android.support.v4.app.Fragment) oldObject).getActivity() != activity); + if (fragmentNeedUpdate || supportFragmentNeedUpdate) { + return map.get(field).apply(activity); + } else { + return null; + } + } + + @Override + protected boolean isMatch(Field field) { + // XXX 一个 Lambda 表达式只有一个实例(断言) + // 所以用 field 就能够确保唯一性 + return map.containsKey(field); + } +} diff --git a/library/src/main/java/info/dourok/esactivity/reference/ViewReferenceUpdater.java b/library/src/main/java/info/dourok/esactivity/reference/ViewReferenceUpdater.java new file mode 100644 index 0000000..a24452f --- /dev/null +++ b/library/src/main/java/info/dourok/esactivity/reference/ViewReferenceUpdater.java @@ -0,0 +1,29 @@ +package info.dourok.esactivity.reference; + +import android.app.Activity; +import android.view.View; +import java.lang.reflect.Field; + +/** + * @author tiaolins + * @date 2018/3/3. + */ +public class ViewReferenceUpdater extends AbstractActivityReferenceUpdater { + @Override + protected Object findNewObject(Activity activity, Field field, Object closure, Object oldObject) { + View view = (View) oldObject; + if (view.getContext() != activity) { + View newView = null; + if (view.getId() != View.NO_ID) { + newView = activity.findViewById(view.getId()); + } + return newView; + } + return null; + } + + @Override + protected boolean isMatch(Field field) { + return View.class.isAssignableFrom(field.getType()); + } +}