android - 查找为什么某些类没有被 Proguard 删除

标签 android proguard thrift android-proguard thrift-protocol

我的应用程序项目中有 Thrift 客户端库和一些生成的 thrift java 类。需要帮助找出 Proguard 未删除未使用的 Thrift 客户端库和生成的类的原因。

我已确认已启用代码收缩

minifyEnabled true
proguardFiles file('proguard-android.txt')

我完全注释掉了我的应用程序中的 Thrift 用法。[之前在我的代码中,我只使用了一个带有 Thrifts TDeserializer 和 TSerializer 类的生成类,并且期望 Proguard 不会保留 TServer 等类,因为它们没有被使用。但是我注意到它仍然在那里。为了确保它不是生成的类间接保留 Thrift 客户端类,我没有全部评论]

即使没有人在我的应用代码中实际调用 com.test.thrift.Task,seeds.txt 仍然显示其中的 com.test.thrift.Task。

我尝试调试为什么该类没有被删除:

使用

 -verbose
 -whyareyoukeeping class com.offerup.android.thrift.Task { public * ; }

它唯一显示的是 com.offerup.android.thrift.Task 由配置中的指令保存。

我尝试删除一些候选者,如下所示。似乎没有什么区别。除了用强力方法删除所有内容并找出配置子句之外,是否有更好的方法来了解为什么保留这些类?

#-keep public class * extends java.lang.Exception
#-keep class * implements android.os.Parcelable {
#  public static final android.os.Parcelable$Creator *;
#}
#-keepclassmembers enum * {
#    public static **[] values();
#    public static ** valueOf(java.lang.String);
#}
#-keep class org.apache.** { *; }

Proguard 配置如下:

# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html

-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose

# For Fabric (Crashlytics)
-keep class com.crashlytics.** { *; }
-keep class com.crashlytics.android.**
-keepattributes SourceFile,LineNumberTable, *Annotation*
-keep public class * extends java.lang.Exception

# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).

#-dontoptimize
#-dontpreverify

# Note that if you want to enable optimization, you cannot just
# include optimization flags in your own project configuration file;
# instead you will need to point to the
# "proguard-android-optimize.txt" file instead of this one from your
# project.properties file.

-keepattributes *Annotation*
-keepclasseswithmembers class * {
    @retrofit.http.* <methods>;
}
#-keep public class com.google.vending.licensing.ILicensingService
#-keep public class com.android.vending.licensing.ILicensingService

-keep class com.android.vending.billing.**

# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native

#-keepclasseswithmembernames class * {
#    native <methods>;
#}

# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans

#-keepclassmembers public class * extends android.view.View {
#   void set*(***);
#   *** get*();
#}

# We want to keep methods in Activity that could be used in the XML attribute onClick

#-keepclassmembers class * extends android.app.Activity {
#   public void *(android.view.View);
#}

# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations

#-keepclassmembers enum * {
#    public static **[] values();
#    public static ** valueOf(java.lang.String);
#}

#-keep class * implements android.os.Parcelable {
#  public static final android.os.Parcelable$Creator *;
#}

#-keepclassmembers class **.R$* {
#    public static <fields>;
#}

# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version.  We know about them, and they are safe.
-dontwarn android.support.**
-dontwarn com.google.android.**
-dontwarn retrofit.**
-dontwarn com.square.**
-dontwarn com.squareup.**
-dontwarn org.apache.**
-dontwarn org.joda.**
-dontwarn rx.**

# Jars that we need to keep
-keep class org.apache.** { *; }
-keep class org.joda.** { *; }
-keep class com.google.** { *; }
-keep class com.mobileapptracker.** { *; }
-keep class com.urbanairship.** { *; }
-keep class com.appsflyer.** { *; }

# Libaries that we need to keep
# Jumio
-keep class com.jumio.** { *; }
-keep class jumiomobile.** { *; }
-keep class net.sf.scuba.smartcards.IsoDepCardService {*;}
-keep class org.jmrtd.** { *; }
-keep class net.sf.scuba.** {*;}
-keep class org.spongycastle.** {*;}
-keep class org.ejbca.** {*;}
-dontwarn org.ejbca.**
-dontwarn org.spongycastle.**

# Apptentive
-dontwarn com.apptentive.android.sdk.**
-keepattributes SourceFile,LineNumberTable
-keep class com.apptentive.android.sdk.** { *; }

-keep class com.facebook.** { *; }
-keep class com.android.** { *; }
-keep class uk.co.senab.** { *; }

# Gradle libraries that we need to keep
-keep class retrofit.** { *; }
-keep class com.square.** { *; }
-keep class com.squareup.** { *; }
-keep class com.crashlytics.** { *; }
-keep class com.stripe.** { *; }

# Android libraries that we need to keep
#-keep class android.support.** { *; }
-keep class com.google.android.** { *; }
#-keep interface android.support.** { *; }

##
# Allow obfuscation of android.support.v7.internal.view.menu.**
# to avoid problem on Samsung 4.2.2 devices with appcompat v21
# see https://code.google.com/p/android/issues/detail?id=78377
#-keep class !android.support.v7.internal.view.menu.**,android.support.** {*;}

# https://code.google.com/p/android/issues/detail?id=78377#c224
# MenuBuilder not found issue on AppCompat 23.1+. Keep all classes except the ones indicated by the patterns preceded by an exclamation mark
#http://cudou.com/pages/ddbffhcj-once-again-getting-java-lang-noclassdeffounderror-android-support-v7-internal-v.html
-keep class !android.support.v7.view.menu.**,!android.support.design.internal.NavigationMenu,!android.support.design.internal.NavigationMenuPresenter,!android.support.design.internal.NavigationSubMenu,** {*;}

# For otto subscribers/producers
-keepclassmembers class ** {
    @com.squareup.otto.Subscribe public *;
    @com.squareup.otto.Produce public *;
}

# More retrofit fixes
-keep class javax.inject.* { *; }
-keepattributes Signature
-keep class sun.misc.Unsafe { *; }

# okhttp 2.2 fixes
-dontwarn java.nio.file.*
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement

# Local files that are needed
-keep class com.byarger.exchangeit.**  { *; }
-keep class com.test.R { *; }
-keep class com.test.R$* { *; }
-keep class com.test.BuildConfig { *; }
-keep class com.test.Manifest { *; }
-keep class com.test.Manifest$* { *; }

# Trying other things to get search to load

# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

# Ensure the unreferenced Creator isn't removed by proguard
-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}

# We want to keep methods in Activity that could be used in the XML attribute onClick
#-keepclassmembers class * extends android.app.Activity {
#   public void *(android.view.View);
#}

# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

#-keep class * implements retrofit.Callback { *; }

-keepclassmembers class com.test.android.activities.BaseActivity {
    protected android.location.LocationManager mLocationManager;
}

-keepclass class com.test.android.bus.BusProvider { *; }

-keep class com.test.android.gson.** { *; }

-keep class com.test.android.dto.** { *; }

-keep class android.support.v7.widget.** {*;}

-keepclassmembers class * {
    @com.leanplum.annotations.* <fields>;
}

-keep class com.leanplum.** { *; }
-dontwarn com.leanplum.**

#RXJava work arounds
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
    long producerIndex;
    long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
    long producerNode;
    long consumerNode;
}

Thrift 生成的未使用的类如下所示:

 /**
 * Autogenerated by Thrift Compiler (0.9.3)
 *
 * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
 *  @generated
 */
package com.test.thrift;

import org.apache.thrift.scheme.IScheme;
import org.apache.thrift.scheme.SchemeFactory;
import org.apache.thrift.scheme.StandardScheme;

import org.apache.thrift.scheme.TupleScheme;
import org.apache.thrift.protocol.TTupleProtocol;
import org.apache.thrift.EncodingUtils;

import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Collections;
import java.util.BitSet;

import javax.annotation.Generated;

@SuppressWarnings({"cast", "rawtypes", "serial", "unchecked"})
@Generated(value = "Autogenerated by Thrift Compiler (0.9.3)", date = "2016-09-30")
public class Task implements org.apache.thrift.TBase<Task, Task._Fields>, java.io.Serializable, Cloneable, Comparable<Task> {
  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Task");

  private static final org.apache.thrift.protocol.TField IDENTIFIER_FIELD_DESC = new org.apache.thrift.protocol.TField("Identifier", org.apache.thrift.protocol.TType.I64, (short)1);
  private static final org.apache.thrift.protocol.TField DESCRIPTION_FIELD_DESC = new org.apache.thrift.protocol.TField("Description", org.apache.thrift.protocol.TType.STRING, (short)2);

  private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>();
  static {
    schemes.put(StandardScheme.class, new TaskStandardSchemeFactory());
    schemes.put(TupleScheme.class, new TaskTupleSchemeFactory());
  }

  /**
   * A unique identifier for this task.
   */
  public long Identifier; // required
  /**
   * A description of the task.
   */
  public String Description; // required

  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
    /**
     * A unique identifier for this task.
     */
    IDENTIFIER((short)1, "Identifier"),
    /**
     * A description of the task.
     */
    DESCRIPTION((short)2, "Description");

    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();

    static {
      for (_Fields field : EnumSet.allOf(_Fields.class)) {
        byName.put(field.getFieldName(), field);
      }
    }

    /**
     * Find the _Fields constant that matches fieldId, or null if its not found.
     */
    public static _Fields findByThriftId(int fieldId) {
      switch(fieldId) {
        case 1: // IDENTIFIER
          return IDENTIFIER;
        case 2: // DESCRIPTION
          return DESCRIPTION;
        default:
          return null;
      }
    }

    /**
     * Find the _Fields constant that matches fieldId, throwing an exception
     * if it is not found.
     */
    public static _Fields findByThriftIdOrThrow(int fieldId) {
      _Fields fields = findByThriftId(fieldId);
      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
      return fields;
    }

    /**
     * Find the _Fields constant that matches name, or null if its not found.
     */
    public static _Fields findByName(String name) {
      return byName.get(name);
    }

    private final short _thriftId;
    private final String _fieldName;

    _Fields(short thriftId, String fieldName) {
      _thriftId = thriftId;
      _fieldName = fieldName;
    }

    public short getThriftFieldId() {
      return _thriftId;
    }

    public String getFieldName() {
      return _fieldName;
    }
  }

  // isset id assignments
  private static final int __IDENTIFIER_ISSET_ID = 0;
  private byte __isset_bitfield = 0;
  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
  static {
    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
    tmpMap.put(_Fields.IDENTIFIER, new org.apache.thrift.meta_data.FieldMetaData("Identifier", org.apache.thrift.TFieldRequirementType.DEFAULT, 
        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I64        , "id")));
    tmpMap.put(_Fields.DESCRIPTION, new org.apache.thrift.meta_data.FieldMetaData("Description", org.apache.thrift.TFieldRequirementType.DEFAULT, 
        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
    metaDataMap = Collections.unmodifiableMap(tmpMap);
    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Task.class, metaDataMap);
  }

  public Task() {
    this.Identifier = -1L;

  }

  public Task(
    long Identifier,
    String Description)
  {
    this();
    this.Identifier = Identifier;
    setIdentifierIsSet(true);
    this.Description = Description;
  }

  /**
   * Performs a deep copy on <i>other</i>.
   */
  public Task(Task other) {
    __isset_bitfield = other.__isset_bitfield;
    this.Identifier = other.Identifier;
    if (other.isSetDescription()) {
      this.Description = other.Description;
    }
  }

  public Task deepCopy() {
    return new Task(this);
  }

  @Override
  public void clear() {
    this.Identifier = -1L;

    this.Description = null;
  }

  /**
   * A unique identifier for this task.
   */
  public long getIdentifier() {
    return this.Identifier;
  }

  /**
   * A unique identifier for this task.
   */
  public Task setIdentifier(long Identifier) {
    this.Identifier = Identifier;
    setIdentifierIsSet(true);
    return this;
  }

  public void unsetIdentifier() {
    __isset_bitfield = EncodingUtils.clearBit(__isset_bitfield, __IDENTIFIER_ISSET_ID);
  }

  /** Returns true if field Identifier is set (has been assigned a value) and false otherwise */
  public boolean isSetIdentifier() {
    return EncodingUtils.testBit(__isset_bitfield, __IDENTIFIER_ISSET_ID);
  }

  public void setIdentifierIsSet(boolean value) {
    __isset_bitfield = EncodingUtils.setBit(__isset_bitfield, __IDENTIFIER_ISSET_ID, value);
  }

  /**
   * A description of the task.
   */
  public String getDescription() {
    return this.Description;
  }

  /**
   * A description of the task.
   */
  public Task setDescription(String Description) {
    this.Description = Description;
    return this;
  }

  public void unsetDescription() {
    this.Description = null;
  }

  /** Returns true if field Description is set (has been assigned a value) and false otherwise */
  public boolean isSetDescription() {
    return this.Description != null;
  }

  public void setDescriptionIsSet(boolean value) {
    if (!value) {
      this.Description = null;
    }
  }

  public void setFieldValue(_Fields field, Object value) {
    switch (field) {
    case IDENTIFIER:
      if (value == null) {
        unsetIdentifier();
      } else {
        setIdentifier((Long)value);
      }
      break;

    case DESCRIPTION:
      if (value == null) {
        unsetDescription();
      } else {
        setDescription((String)value);
      }
      break;

    }
  }

  public Object getFieldValue(_Fields field) {
    switch (field) {
    case IDENTIFIER:
      return getIdentifier();

    case DESCRIPTION:
      return getDescription();

    }
    throw new IllegalStateException();
  }

  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
  public boolean isSet(_Fields field) {
    if (field == null) {
      throw new IllegalArgumentException();
    }

    switch (field) {
    case IDENTIFIER:
      return isSetIdentifier();
    case DESCRIPTION:
      return isSetDescription();
    }
    throw new IllegalStateException();
  }

  @Override
  public boolean equals(Object that) {
    if (that == null)
      return false;
    if (that instanceof Task)
      return this.equals((Task)that);
    return false;
  }

  public boolean equals(Task that) {
    if (that == null)
      return false;

    boolean this_present_Identifier = true;
    boolean that_present_Identifier = true;
    if (this_present_Identifier || that_present_Identifier) {
      if (!(this_present_Identifier && that_present_Identifier))
        return false;
      if (this.Identifier != that.Identifier)
        return false;
    }

    boolean this_present_Description = true && this.isSetDescription();
    boolean that_present_Description = true && that.isSetDescription();
    if (this_present_Description || that_present_Description) {
      if (!(this_present_Description && that_present_Description))
        return false;
      if (!this.Description.equals(that.Description))
        return false;
    }

    return true;
  }

  @Override
  public int hashCode() {
    List<Object> list = new ArrayList<Object>();

    boolean present_Identifier = true;
    list.add(present_Identifier);
    if (present_Identifier)
      list.add(Identifier);

    boolean present_Description = true && (isSetDescription());
    list.add(present_Description);
    if (present_Description)
      list.add(Description);

    return list.hashCode();
  }

  @Override
  public int compareTo(Task other) {
    if (!getClass().equals(other.getClass())) {
      return getClass().getName().compareTo(other.getClass().getName());
    }

    int lastComparison = 0;

    lastComparison = Boolean.valueOf(isSetIdentifier()).compareTo(other.isSetIdentifier());
    if (lastComparison != 0) {
      return lastComparison;
    }
    if (isSetIdentifier()) {
      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.Identifier, other.Identifier);
      if (lastComparison != 0) {
        return lastComparison;
      }
    }
    lastComparison = Boolean.valueOf(isSetDescription()).compareTo(other.isSetDescription());
    if (lastComparison != 0) {
      return lastComparison;
    }
    if (isSetDescription()) {
      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.Description, other.Description);
      if (lastComparison != 0) {
        return lastComparison;
      }
    }
    return 0;
  }

  public _Fields fieldForId(int fieldId) {
    return _Fields.findByThriftId(fieldId);
  }

  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
    schemes.get(iprot.getScheme()).getScheme().read(iprot, this);
  }

  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
    schemes.get(oprot.getScheme()).getScheme().write(oprot, this);
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder("Task(");
    boolean first = true;

    sb.append("Identifier:");
    sb.append(this.Identifier);
    first = false;
    if (!first) sb.append(", ");
    sb.append("Description:");
    if (this.Description == null) {
      sb.append("null");
    } else {
      sb.append(this.Description);
    }
    first = false;
    sb.append(")");
    return sb.toString();
  }

  public void validate() throws org.apache.thrift.TException {
    // check for required fields
    // check for sub-struct validity
  }

  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
    try {
      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
    } catch (org.apache.thrift.TException te) {
      throw new java.io.IOException(te);
    }
  }

  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
    try {
      // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
      __isset_bitfield = 0;
      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
    } catch (org.apache.thrift.TException te) {
      throw new java.io.IOException(te);
    }
  }

  private static class TaskStandardSchemeFactory implements SchemeFactory {
    public TaskStandardScheme getScheme() {
      return new TaskStandardScheme();
    }
  }

  private static class TaskStandardScheme extends StandardScheme<Task> {

    public void read(org.apache.thrift.protocol.TProtocol iprot, Task struct) throws org.apache.thrift.TException {
      org.apache.thrift.protocol.TField schemeField;
      iprot.readStructBegin();
      while (true)
      {
        schemeField = iprot.readFieldBegin();
        if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { 
          break;
        }
        switch (schemeField.id) {
          case 1: // IDENTIFIER
            if (schemeField.type == org.apache.thrift.protocol.TType.I64) {
              struct.Identifier = iprot.readI64();
              struct.setIdentifierIsSet(true);
            } else { 
              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
            }
            break;
          case 2: // DESCRIPTION
            if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
              struct.Description = iprot.readString();
              struct.setDescriptionIsSet(true);
            } else { 
              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
            }
            break;
          default:
            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
        }
        iprot.readFieldEnd();
      }
      iprot.readStructEnd();

      // check for required fields of primitive type, which can't be checked in the validate method
      struct.validate();
    }

    public void write(org.apache.thrift.protocol.TProtocol oprot, Task struct) throws org.apache.thrift.TException {
      struct.validate();

      oprot.writeStructBegin(STRUCT_DESC);
      oprot.writeFieldBegin(IDENTIFIER_FIELD_DESC);
      oprot.writeI64(struct.Identifier);
      oprot.writeFieldEnd();
      if (struct.Description != null) {
        oprot.writeFieldBegin(DESCRIPTION_FIELD_DESC);
        oprot.writeString(struct.Description);
        oprot.writeFieldEnd();
      }
      oprot.writeFieldStop();
      oprot.writeStructEnd();
    }

  }

  private static class TaskTupleSchemeFactory implements SchemeFactory {
    public TaskTupleScheme getScheme() {
      return new TaskTupleScheme();
    }
  }

  private static class TaskTupleScheme extends TupleScheme<Task> {

    @Override
    public void write(org.apache.thrift.protocol.TProtocol prot, Task struct) throws org.apache.thrift.TException {
      TTupleProtocol oprot = (TTupleProtocol) prot;
      BitSet optionals = new BitSet();
      if (struct.isSetIdentifier()) {
        optionals.set(0);
      }
      if (struct.isSetDescription()) {
        optionals.set(1);
      }
      oprot.writeBitSet(optionals, 2);
      if (struct.isSetIdentifier()) {
        oprot.writeI64(struct.Identifier);
      }
      if (struct.isSetDescription()) {
        oprot.writeString(struct.Description);
      }
    }

    @Override
    public void read(org.apache.thrift.protocol.TProtocol prot, Task struct) throws org.apache.thrift.TException {
      TTupleProtocol iprot = (TTupleProtocol) prot;
      BitSet incoming = iprot.readBitSet(2);
      if (incoming.get(0)) {
        struct.Identifier = iprot.readI64();
        struct.setIdentifierIsSet(true);
      }
      if (incoming.get(1)) {
        struct.Description = iprot.readString();
        struct.setDescriptionIsSet(true);
      }
    }
  }

}

最佳答案

我遇到了类似的问题,但 ProGuard 没有告诉您哪个指令强制保留该类。但是,通过查看您的配置,我怀疑这一行可能是罪魁祸首:

-keep class !android.support.v7.view.menu.**,!android.support.design.internal.NavigationMenu,!android.support.design.internal.NavigationMenuPresenter,!android.support.design.internal.NavigationSubMenu,** {*;}

我怀疑最后一个 ** 可能会强制保留所有类,包括您的类。

此外,您还可以使用 ClassyShark要分析 APK 中的类和方法,而无需安装 APK,因此您可以做的一件事是通过规则进行二分搜索,以查看哪一个使该类被保留。

关于android - 查找为什么某些类没有被 Proguard 删除,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39860007/

相关文章:

android - ConstraintLayout 剪辑文本中的 TextView

java.lang.NoClassDefFoundError : opennlp. model.GenericModelReader

kotlin - 使用挂起函数混淆内部类后的 ClassCastException

android - 使用 Proguard 构建时出现多个警告

python - 如何使用 python pyhs2 连接到配置单元?

java - Apache Thrift Python-Java 'Connection Refused'

java - 如何在不损失图像边缘质量的情况下旋转 ImageView

php - Android App无法访问Vagrant Homestead托管的Web API

java - 带有 sdk 23 和 proguard 的 Apache HTTP 客户端

c++ - Apache Thrift 编译错误