Skip to content

Commit

Permalink
feat: 保持Fragment类型不变的情况下支持Fragment正常工作
Browse files Browse the repository at this point in the history
对Fragment中涉及Activity类型的方法进行字节码编辑,在调入调出前后相互转换ContainerActivity和PluginActivity。

现在Fragment不会被改名,所以能正常Debug了。

测试用例考虑了如下情况:
1.Fragment是系统Fragment的直接子类、间接子类;
2.Fragment父类是DialogFragment(父类是系统类);
3.Fragment以代码方式添加和Xml方式添加;
4.Fragment作为其他Fragment的父类;
5.Fragment的父类只Override了Context版本或Activity版本的方法,或者同时Override了。
6.测试了API28和API19两款模拟器;

fix Tencent#168

related issues: Tencent#307, Tencent#285, Tencent#242, Tencent#243, Tencent#165, Tencent#151, Tencent#115, Tencent#24
  • Loading branch information
shifujun committed Apr 19, 2020
1 parent 8f2f982 commit b5d1bdc
Show file tree
Hide file tree
Showing 45 changed files with 2,006 additions and 112 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.tencent.shadow.core.runtime;

import android.annotation.SuppressLint;
import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

import com.tencent.shadow.core.runtime.container.PluginContainerActivity;

@SuppressLint("NewApi")
public class ShadowFragmentSupport {

public static ShadowActivity fragmentGetActivity(Fragment fragment) {
PluginContainerActivity pluginContainerActivity
= (PluginContainerActivity) fragment.getActivity();
return (ShadowActivity) PluginActivity.get(pluginContainerActivity);
}

public static Context fragmentGetContext(Fragment fragment) {
Context context = fragment.getContext();
if (context instanceof PluginContainerActivity) {
return PluginActivity.get((PluginContainerActivity) context);
} else {
return context;
}
}

public static Object fragmentGetHost(Fragment fragment) {
Object host = fragment.getHost();
if (host instanceof PluginContainerActivity) {
return PluginActivity.get((PluginContainerActivity) host);
} else {
return host;
}
}

public static void fragmentStartActivity(Fragment fragment, Intent intent) {
fragmentStartActivity(fragment, intent, null);
}

@SuppressLint("NewApi")
public static void fragmentStartActivity(Fragment fragment, Intent intent, Bundle options) {
ShadowContext shadowContext = fragmentGetActivity(fragment);
Intent containerActivityIntent
= shadowContext.mPluginComponentLauncher.convertPluginActivityIntent(intent);
if (options == null) {
fragment.startActivity(containerActivityIntent);
} else {
fragment.startActivity(containerActivityIntent, options);
}
}

public static Context toPluginContext(Context pluginContainerActivity) {
return PluginActivity.get((PluginContainerActivity) pluginContainerActivity);
}

public static Context toOriginalContext(Context pluginActivity) {
PluginActivity activity = (PluginActivity) pluginActivity;
return activity.hostActivityDelegator.getHostActivity().getImplementActivity();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class TransformManager(ctClassInputMap: Map<CtClass, InputClass>,
ActivityTransform(),
ServiceTransform(),
InstrumentationTransform(),
FragmentSupportTransform(),
DialogTransform(),
WebViewTransform(),
ContentProviderTransform(),
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package android.app;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

import com.tencent.shadow.core.runtime.ShadowActivity;

/**
* 因为在TransformManager中ActivityTransform是在FragmentSupportTransform之前进行的,
* 所以FragmentSupportTransform运行时,Activity已经都变成ShadowActivity了,
* 所以这个Mock类定义的方法中Activity已经是ShadowActivity了。
*/
public class Fragment {
final public ShadowActivity getActivity() {
return null;
}

public void startActivity(Intent intent) {

}

public void startActivity(Intent intent, Bundle options) {

}

public void onAttach(Context context) {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package android.content;

public class Intent {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package android.os;

public class Bundle {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.tencent.shadow.core.runtime;

import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

public class ShadowFragmentSupport {

public static ShadowActivity fragmentGetActivity(Fragment fragment) {
return null;
}

public void fragmentStartActivity(Fragment fragment, Intent intent) {
}

public void fragmentStartActivity(Fragment fragment, Intent intent, Bundle options) {
}

public static Context toPluginContext(Context pluginContainerActivity) {
return null;
}

public static Context toOriginalContext(Context pluginActivity) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package test.fragment;

import android.app.Fragment;
import android.content.Context;

public class TestFragment extends Fragment {

@Override
public void onAttach(Context context) {
System.out.println("before super.onAttach" + context);
super.onAttach(context);
System.out.println("after super.onAttach" + context);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package test.fragment;

import android.content.Intent;
import android.os.Bundle;

import com.tencent.shadow.core.runtime.ShadowActivity;

public class UseFragment {

ShadowActivity test(TestFragment fragment) {
fragment.startActivity(new Intent());
fragment.startActivity(new Intent(), new Bundle());
return fragment.getActivity();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Tencent is pleased to support the open source community by making Tencent Shadow available.
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package com.tencent.shadow.core.transform.specific

import com.tencent.shadow.core.transform_kit.AbstractTransformTest
import javassist.CtClass
import javassist.NotFoundException
import org.junit.Assert
import org.junit.Test

/**
* ./gradlew -p projects/sdk/core :transform:test --tests com.tencent.shadow.core.transform.specific.FragmentSupportTransformTest
*/
class FragmentSupportTransformTest : AbstractTransformTest() {

companion object {
const val ShadowFragmentSupportClassName = "com.tencent.shadow.core.runtime.ShadowFragmentSupport"
const val ShadowActivitySig = "Lcom/tencent/shadow/core/runtime/ShadowActivity;"
const val TestFragmentSig = "Ltest/fragment/TestFragment;"
const val FragmentSig = "Landroid/app/Fragment;"
const val IntentSig = "Landroid/content/Intent;"
const val BundleSig = "Landroid/os/Bundle;"
}

val shadowFragmentSupportClazz = sLoader[ShadowFragmentSupportClassName]
val fragmentGetActivity = shadowFragmentSupportClazz.getMethod("fragmentGetActivity", "($FragmentSig)$ShadowActivitySig")
val fragmentStartActivity1 = shadowFragmentSupportClazz.getMethod("fragmentStartActivity", "($FragmentSig$IntentSig)V")
val fragmentStartActivity2 = shadowFragmentSupportClazz.getMethod("fragmentStartActivity", "($FragmentSig$IntentSig$BundleSig)V")

private fun transform(clazz: CtClass) {
val transform = FragmentSupportTransform()
transform.mClassPool = sLoader

val allInputClass = setOf(
clazz,
sLoader["test.fragment.TestFragment"]
)
transform.setup(allInputClass)

transform.list.forEach { step ->
step.filter(allInputClass).forEach {
step.transform(it)
it.writeFile(WRITE_FILE_DIR)
}
}
}


@Test
fun fragmentGetActivity() {
val name = "test.fragment.UseFragment"
transform(sLoader[name])

val transformedClass = dLoader.get(name)
try {
transformedClass.getMethod("test", "($TestFragmentSig)$ShadowActivitySig")
} catch (e: Exception) {
Assert.fail("找不到正确的test方法")
}

Assert.assertTrue("${fragmentGetActivity}调用应该可以找到",
matchMethodCallInClass(fragmentGetActivity, transformedClass)
)
}

@Test
fun fragmentStartActivity() {
val name = "test.fragment.UseFragment"
transform(sLoader[name])

val transformedClass = dLoader.get(name)
try {
transformedClass.getMethod("test", "($TestFragmentSig)$ShadowActivitySig")
} catch (e: Exception) {
Assert.fail("找不到正确的test方法")
}

Assert.assertTrue("${fragmentStartActivity1}调用应该可以找到",
matchMethodCallInClass(fragmentStartActivity1, transformedClass)
)
Assert.assertTrue("${fragmentStartActivity2}调用应该可以找到",
matchMethodCallInClass(fragmentStartActivity2, transformedClass)
)
}

@Test
fun attachContext() {
val name = "test.fragment.TestFragment"
val transform = FragmentSupportTransform()
transform.mClassPool = sLoader

val allInputClass = setOf(
sLoader[name]
)
transform.setup(allInputClass)

transform.list.forEach { step ->
step.filter(allInputClass).forEach {
step.transform(it)
it.writeFile(WRITE_FILE_DIR)
}
}

val transformedClass = dLoader.get(name)
try {
transformedClass.getDeclaredMethod("onAttach")
} catch (e: NotFoundException) {
Assert.fail("找不到onAttach")
}

try {
transformedClass.getDeclaredMethod("onAttachShadowContext")
} catch (e: NotFoundException) {
Assert.fail("找不到onAttachShadowContext")
}

try {
transformedClass.getDeclaredMethod("superOnAttach")
} catch (e: NotFoundException) {
Assert.fail("找不到superOnAttach")
}
}
}
Loading

0 comments on commit b5d1bdc

Please sign in to comment.