2dxgujun / Kpan

:four_leaf_clover:Kotlin wrapper around SpannableStringBuilder to bring modern API
Apache License 2.0
105 stars 9 forks source link

混淆后报错 方法未找到 #3

Closed ggggxiaolong closed 6 years ago

ggggxiaolong commented 6 years ago

java.lang.NoSuchMethodError

me.gujun.android.span.Span.length(Proguard:29)
android.text.SpannableStringBuilder.append(SpannableStringBuilder.java:268)
me.gujun.android.span.Span.me.gujun.android.span.Span build()(Proguard:157)
me.gujun.android.span.SpanKt.me.gujun.android.span.Span span$default(me.gujun.android.span.Span,java.lang.CharSequence,kotlin.jvm.functions.Function1,int,java.lang.Object)(Proguard:257)
##_parent_##2##_parent_##
##_child_## me.gujun.android.span.Span span(kotlin.jvm.functions.Function1)##_child_##
##_child_## me.gujun.android.span.Span span(me.gujun.android.span.Span,java.lang.CharSequence,kotlin.jvm.functions.Function1)##_child_##
me.gujun.android.span.SpanKt.me.gujun.android.span.Span span$default(me.gujun.android.span.Span,java.lang.CharSequence,kotlin.jvm.functions.Function1,int,java.lang.Object)(Proguard:253)
##_parent_##2##_parent_##
##_child_## me.gujun.android.span.Span span(kotlin.jvm.functions.Function1)##_child_##
##_child_## me.gujun.android.span.Span span(me.gujun.android.span.Span,java.lang.CharSequence,kotlin.jvm.functions.Function1)##_child_##

我自定义了一个类,是它的问题吗

/**
 * 取消url的下划线
 * mrtan on 2018/3/21.
 */
class NewsSpan : URLSpan {
  constructor(url: String): super(url)
  constructor(src: Parcel): super(src)

  override fun updateDrawState(ds: TextPaint) {
    super.updateDrawState(ds)
    ds.isUnderlineText = false
  }
}
2dxgujun commented 6 years ago

看上去kotlin代码经过编译后在Span类里面生成了两个辅助方法,那个d混淆之前是getLength image 而第二个方法正好重载了其父类SpannableStringBuilderlength()方法,所以SpannableStringBuilder内部调用时就调用到了那个重载的length()

但是我这边目前看来只混淆了getLength()方法,运行时没有问题,你这边能看一下是不是有个方法被shrink掉了,或者你给我看一哈你的proguard文件,我试试看能不能复现

ggggxiaolong commented 6 years ago
# http://drakeet.me/android-advanced-proguard-and-security/
-ignorewarnings
-keep public class * extends android.os.Binder
-keepclassmembers enum * {
    **[] $VALUES;
    public *;
}

# v7
-keep public class android.support.v7.widget.** { *; }
-keep public class android.support.v7.internal.widget.** { *; }
-keep public class android.support.v7.internal.view.menu.** { *; }
-keep public class * extends android.support.v4.view.ActionProvider {
    public <init>(android.content.Context);
}

# log
-assumenosideeffects class android.util.Log {
    public static boolean isLoggable(java.lang.String, int);
    public static int d(...);
    public static int w(...);
    public static int v(...);
    public static int i(...);
}

-keepclassmembers class * {
   public <init> (org.json.JSONObject);
}

-keep class org.ocpsoft.prettytime.** { *; }

# Fabric
#-keep public class * extends java.lang.Exception
#-keepattributes *Annotation*
#-keep class com.crashlytics.** { *; }
#-dontwarn com.crashlytics.**
#-keep class io.fabric.** { *; }

-renamesourcefileattribute Proguard
-keepattributes SourceFile,LineNumberTable

# 混淆字典
#-obfuscationdictionary dictionary-drakeet.txt
#-classobfuscationdictionary dictionary-drakeet.txt
#-packageobfuscationdictionary dictionary-drakeet.txt

# 把代码以及所使用到的各种第三方库代码统统移动到同一个包下
-repackageclasses 'com.mrtan.data'
-allowaccessmodification
#############################################################################################
########################                 以上通用           ##################################
#############################################################################################
# umeng
-keepclassmembers class * {
   public <init> (org.json.JSONObject);
}

# ARoute
-keep public class com.alibaba.android.arouter.routes.**{*;}
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}

#Glide
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.AppGlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
    **[] $VALUES;
    public *;
}

#okhttp
-dontwarn okio.**
-dontwarn javax.annotation.Nullable
-dontwarn javax.annotation.ParametersAreNonnullByDefault

#bugly
-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}

#alipush
-keepclasseswithmembernames class ** {
    native <methods>;
}
-keepattributes Signature
-keep class sun.misc.Unsafe { *; }
-keep class com.taobao.** {*;}
-keep class com.alibaba.** {*;}
-keep class com.alipay.** {*;}
-dontwarn com.taobao.**
-dontwarn com.alibaba.**
-dontwarn com.alipay.**
-keep class com.ut.** {*;}
-dontwarn com.ut.**
-keep class com.ta.** {*;}
-dontwarn com.ta.**
-keep class anet.**{*;}
-keep class org.android.spdy.**{*;}
-keep class org.android.agoo.**{*;}
-dontwarn anet.**
-dontwarn org.android.spdy.**
-dontwarn org.android.agoo.**

#update
-keep class android.content.pm.** { *; }

# BottomNavigationViewEx
-keep public class android.support.design.widget.BottomNavigationView { *; }
-keep public class android.support.design.internal.BottomNavigationMenuView { *; }
-keep public class android.support.design.internal.BottomNavigationPresenter { *; }
-keep public class android.support.design.internal.BottomNavigationItemView { *; }
ggggxiaolong commented 6 years ago

这个是混淆文件,后来我把整个span包 keep 之后还是会报错.有点奇怪

2dxgujun commented 6 years ago

我用了你这个混淆文件,还是不能复现啊- -反编译出来看上去也一切正常 你试试看能不能反编译找到Span类,看一下调用环节里面哪个方法缺失了 image

ggggxiaolong commented 6 years ago
override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    val news = arguments!!.getParcelable<News>(KEY_NEWS)!!
    mDataBinding.news = news
    //获取所有附件
    val attach = news.attachments()
    if (attach.isNotEmpty()) {
      val span = span{
        span("附件")
      }
      attach.forEach {
        span.append("\n")
        val url = it.url
        span.span(it.name){
          spans.add(NewsSpan(url))
        }
      }
      mDataBinding.attachment.movementMethod = LinkMovementMethod.getInstance()
      mDataBinding.attachment.text = span

    }
  }

是不是我代码写的有问题 哈? image image

ggggxiaolong commented 6 years ago

对了 我使用了 R8 混淆 gradle.properties

org.gradle.jvmargs=-Xmx4096m
# gradle4.6 添加kotlin cache
org.gradle.caching=true
# AGP 3.2.0-alpha06 R8 混淆配置
android.enableR8 = true
2dxgujun commented 6 years ago

你方不方便把混淆后的apk发给我,我邮箱2dxgujun@gmail.com

你上面的代码我看没什么问题,不过你可以换种写法

val text = span {
  +"附件"
  attach.forEach {
    span("\n${it.name}") {
      addSpan(NewsSpan(it.url))
    }
  }
}
ggggxiaolong commented 6 years ago

发过去了,我反编译之后发现 length() 方法并没有被重写

2dxgujun commented 6 years ago

你的mapping里面缺少这些方法,而且反编译出来也缺少这些方法 image

这是正常的反编译后的结果: image

我用了你的proguard配置也启用了R8,都是一切正常的,这是我的mapping:

me.gujun.android.span.Span -> com.mrtan.data.hp:
    java.lang.CharSequence text -> b
    java.lang.Integer textColor -> c
    java.lang.Integer backgroundColor -> d
    java.lang.Integer textSize -> e
    java.lang.String fontFamily -> f
    android.graphics.Typeface typeface -> g
    java.lang.String textStyle -> h
    java.lang.String alignment -> i
    java.lang.String textDecorationLine -> j
    java.lang.Integer lineSpacing -> k
    java.lang.Integer paddingTop -> l
    java.lang.Integer paddingBottom -> m
    java.lang.Integer verticalPadding -> n
    kotlin.jvm.functions.Function0 onClick -> o
    java.util.ArrayList spans -> p
    me.gujun.android.span.Span style -> q
    me.gujun.android.span.Span parent -> r
    me.gujun.android.span.Span EMPTY_STYLE -> s
    me.gujun.android.span.Span globalStyle -> t
    me.gujun.android.span.Span$Companion Companion -> a
    37:37:void setText(java.lang.CharSequence) -> a
    39:39:void setTextColor(java.lang.Integer) -> a
    41:41:void setBackgroundColor(java.lang.Integer) -> b
    43:43:void setTextSize(java.lang.Integer) -> c
    45:45:void setFontFamily(java.lang.String) -> a
    47:47:void setTypeface(android.graphics.Typeface) -> a
    49:49:void setTextStyle(java.lang.String) -> b
    51:51:void setAlignment(java.lang.String) -> c
    53:53:void setTextDecorationLine(java.lang.String) -> d
    55:55:void setLineSpacing(java.lang.Integer) -> d
    61:61:void setVerticalPadding(java.lang.Integer) -> e
    63:63:kotlin.jvm.functions.Function0 getOnClick() -> a
    63:63:void setOnClick(kotlin.jvm.functions.Function0) -> a
    65:65:java.util.ArrayList getSpans() -> b
    67:67:void setStyle(me.gujun.android.span.Span) -> a
    70:111:void buildCharacterStyle(java.util.ArrayList) -> a
    114:140:void buildParagraphStyle(java.util.ArrayList) -> b
    143:144:void prebuild() -> g
    147:318:me.gujun.android.span.Span build() -> c
    172:212:void override(me.gujun.android.span.Span) -> b
    215:218:java.lang.CharSequence unaryPlus(java.lang.CharSequence) -> b
    29:67:void <init>(me.gujun.android.span.Span) -> <init>
    29:29:void <init>(me.gujun.android.span.Span,int,kotlin.jvm.internal.DefaultConstructorMarker) -> <init>
    void <init>() -> <init>
    32:34:void <clinit>() -> <clinit>
    29:29:char get(int) -> a
    29:29:char charAt(int) -> charAt
    29:29:int getLength() -> d
    29:29:int length() -> length
    29:29:me.gujun.android.span.Span access$getEMPTY_STYLE$cp() -> e
    29:29:me.gujun.android.span.Span access$getGlobalStyle$cp() -> f
    29:29:void access$setGlobalStyle$cp(me.gujun.android.span.Span) -> c

按理来说proguard不会把那几个方法给shrink掉,不排除是kotlin编译器的问题,你不跑proguard就没问题吗?我的kotlin版本时1.2.20,你可以试试看

ggggxiaolong commented 6 years ago

我 clone 的你的项目 修改了sample的代码 可以复现问题(需要修改maven地址) https://github.com/ggggxiaolong/Spannable-In-Kotlin

修改的内容 https://github.com/ggggxiaolong/Spannable-In-Kotlin/commit/098396d3f3d63e907a6bd0e1f4ab3b48b542b65e

2dxgujun commented 6 years ago

应该是R8的问题,开了R8之后这些方法就没了,我明天试试看写个简单点的demo去复现一下

ggggxiaolong commented 6 years ago

又一个疑问想问你一下,你的 Sapn 类并没有重写父类的 length 方法。为什么 kotlin 还要生成一个代理方法? Sapn 类的父类存在 length 方法 而且 Span 也没有重写 length 方法为什么会报 length 方法找不到的错误?有点困惑

2dxgujun commented 6 years ago

kotlin为了让java.lang.CharSequence支持下标访问和length属性访问,对java.lang.CharSequence做了一些特殊处理,用kotlin.CharSequence替换掉了接口,因为下标访问和属性访问都是需要Java这边的方法满足一定的条件(get(int)和getXXX()),所以kotlin编译后会生成这些synthetic方法来提供这种支持

2dxgujun commented 6 years ago

另外,这个问题已经有结论了,是R8的BUG,已经给官方提Issue了,https://issuetracker.google.com/issues/76383728 kotlin编译器对CharSequence做的优化正好掉进了这个坑,R8会把满足特定条件的方法认为是visibility bridges给优化掉,例如下面的代码,通过asm修改bridge方法的access flags,R8就会把bridge方法给删掉,建议你先不要混淆这个lib或者把proguard-rules.pro里面的-allowaccessmodification给注释掉

public class ClassA {
  int method() {
    return 0;
  }
}

public class ClassB extends ClassA {
  int bridge() {
    return super.method();
  }

  int method() {
    return bridge();
  }
}
2dxgujun commented 6 years ago

https://r8-review.googlesource.com/c/r8/+/19264 Google已经修复这个问题了,你可以再混淆试试看