Android 解析DEX文件

简介: 1. DEX文件简介1). 基本格式Android DEX文件格式--非虫大神杰作.pngdex-file-general-structure.

1. DEX文件简介

1). 基本格式
img_44dda2cbb40f28c643bd38c4a08894d6.png
Android DEX文件格式--非虫大神杰作.png
img_4a9cafdd0e27ebdd976b03e67347dca1.png
dex-file-general-structure.png
  • Dex Header: header文件头
  • String Table: 字符串的索引
  • Type Table: 类型的索引
  • Proto Table: 方法原型的索引
  • Field Table: 域的索引
  • Method Table: 方法索引
  • Class Def Table: 类的定义区
  • Data Section: 数据区
1). Dex Header

dex文件里的header,描述.dex文件的文件信息,及其它各个区域的索引。

/**
 * DEX 头部信息类型
 * 主要分为两部分:
 *  1). 魔数 + 签名 + 文件大小等信息
 *  2). 后面的各个数据结构的大小和偏移值,成对出现
 * 
 * struct DexHeader {
        u1  magic[8];           // includes version number
        u4  checksum;           // adler32 checksum 
        u1  signature[kSHA1DigestLen]; // SHA-1 hash
        u4  fileSize;           // length of entire file
        u4  headerSize;         // offset to start of next section
        u4  endianTag;
        u4  linkSize;
        u4  linkOff;
        u4  mapOff;
        u4  stringIdsSize;
        u4  stringIdsOff;
        u4  typeIdsSize;
        u4  typeIdsOff;
        u4  protoIdsSize;
        u4  protoIdsOff;
        u4  fieldIdsSize;
        u4  fieldIdsOff;
        u4  methodIdsSize;
        u4  methodIdsOff;
        u4  classDefsSize;
        u4  classDefsOff;
        u4  dataSize;
        u4  dataOff;
    };
 * 
 * @author mazaiting
 */
public class HeaderType {
    /**8个字节,一般是常量,为使.dex文件能够被识别出来,必须出现在.dex文件的最开头位置
     *  数组的值一般可以转换为一个字符串: { 0x64 0x65 0x78 0x0a 0x30 0x33 0x35 0x00 } = "dex\n035\0"
     *  中间是一个 ‘\n' 符号 ,后面 035 是 Dex 文件格式的版本 。
     */
    public byte[] magic = new byte[8];
    /**文件校验码,使用alder32算法校验文件出去magic,checknum外余下所有文件区域用于检查文件错误*/
    public int checkSum;
    /**采用SHA-1算法hash出去magic,checknum和signature外余下所有的文件区域,用于唯一识别本文件*/
    public byte[] signAture = new byte[20];
    /**DEX文件的大小*/
    public int fileSize;
    /**header区域的大小,单位Byte,一般固定为0x70常量*/
    public int headerSize;
    /**大小端标签,标准.dex文件为小端,此项一般固定为0x12345678常量*/
    public int endianTag;
    /**链接数据的大小*/
    public int linkSize;
    /**链接数据的偏移值*/
    public int linkOff;
    /**map item的偏移地址,该item属于data区里的内容,值要大于等于dataOff的大小*/
    public int mapOff;
    /**DEX中用到的所有字符串内容的大小*/
    public int stringIdsSize;
    /**DEX中用到的所有字符串内容的偏移量*/
    public int stringIdsOff;
    /**DEX中类型数据结构的大小*/
    public int typeIdsSize;
    /**DEX中类型数据结构的偏移值*/
    public int typeIdsOff;
    /**DEX中的元数据信息数据结构的大小*/
    public int protoIdsSize;
    /**DEX中的元数据信息数据结构的偏移值*/
    public int protoIdsOff;
    /**DEX中字段信息数据结构的大小*/
    public int fieldIdsSize;
    /**DEX中字段信息数据结构的偏移值*/
    public int fieldIdsOff;
    /**DEX中方法信息数据结构的大小*/
    public int methodIdsSize;
    /**DEX中方法信息数据结构的偏移值*/
    public int methodIdsOff;
    /**DEX中的类信息数据结构的大小*/
    public int classDefsSize;
    /**DEX中的类信息数据结构的偏移值*/
    public int classDefsOff;
    /**DEX中数据区域的结构信息的大小*/
    public int dataSize;
    /**DEX中数据区域的结构信息的偏移值*/
    public int dataOff;
    
    @Override
    public String toString(){
        return "magic:"+Util.bytesToHexString(magic)+"\n"
                + "checksum:"+checkSum + "\n"
                + "siganature:"+Util.bytesToHexString(signAture) + "\n"
                + "file_size:"+fileSize + "\n"
                + "header_size:"+headerSize + "\n"
                + "endian_tag:"+endianTag + "\n"
                + "link_size:"+linkSize + "\n"
                + "link_off:"+Util.bytesToHexString(Util.int2Byte(linkOff)) + "\n"
                + "map_off:"+Util.bytesToHexString(Util.int2Byte(mapOff)) + "\n"
                + "string_ids_size:"+stringIdsSize + "\n"
                + "string_ids_off:"+Util.bytesToHexString(Util.int2Byte(stringIdsOff)) + "\n"
                + "type_ids_size:"+typeIdsSize + "\n"
                + "type_ids_off:"+Util.bytesToHexString(Util.int2Byte(typeIdsOff)) + "\n"
                + "proto_ids_size:"+protoIdsSize + "\n"
                + "proto_ids_off:"+Util.bytesToHexString(Util.int2Byte(protoIdsOff)) + "\n"
                + "field_ids_size:"+fieldIdsSize + "\n"
                + "field_ids_off:"+Util.bytesToHexString(Util.int2Byte(fieldIdsOff)) + "\n"
                + "method_ids_size:"+methodIdsSize + "\n"
                + "method_ids_off:"+Util.bytesToHexString(Util.int2Byte(methodIdsOff)) + "\n"
                + "class_defs_size:"+classDefsSize + "\n"
                + "class_defs_off:"+Util.bytesToHexString(Util.int2Byte(classDefsOff)) + "\n"
                + "data_size:"+dataSize + "\n"
                + "data_off:"+Util.bytesToHexString(Util.int2Byte(dataOff));
    }
}
img_9a1f317c4bf53b37ba6b29498df56ec6.png
图1.png
2). String Table

string_ids区索引.dex文件所有的字符串

/**
 * 索引.dex文件所有的字符串
 * 
 * struct DexStringId {
        u4 stringDataOff;      // file offset to string_data_item
    };
 * @author mazaiting
 */
public class StringIdsItem {
    /**字符串数据的偏移地址*/
    public int stringDataOff;
    
    /**
     * 获取当前类属性所占字节大小
     * @return
     */
    public static int getSize() {
        return 4;
    }
    
    @Override
    public String toString() {
        return Util.bytesToHexString(Util.int2Byte(stringDataOff));
    }
}

string_data_off是一个偏移地址,指向的数据结构为string_data_item.

/**
 * StringIdsItem中stringDataOff指向的数据结构
 * struct string_data_item {
        uleb128 utf16_size;
        ubyte data;
    }
 * @author mazaiting
 */
public class StringDataItem {
    /**LEB128 ( little endian base 128 ) 格式 ,是基于 1 个 Byte 的一种不定长度的
        编码方式 。若第一个 Byte 的最高位为 1 ,则表示还需要下一个 Byte 来描述 ,直至最后一个 Byte 的最高
        位为 0 。每个 Byte 的其余 Bit 用来表示数据*/
    public byte data;
    public List<Byte> utf16Size = new ArrayList<>();
}
3). Type Table

主要描述dex中所有的类型,如类类型,基本类型等信息,type_ids区索引了dex文件里的所有数据类型,包括class类型,数据类型(array types) 和基本类型(primitive types).

/**
 * DEX所有类型,基本类型等信息
 * type_ids 区索引了 dex 文件里的所有数据类型 ,包括 class 类型 ,数组类型(array types)和基本类型(primitive types)
 * 
 * struct DexTypeId {
    u4  descriptorIdx;      // index into stringIds list for type descriptor 
    };
 * @author mazaiting
 */
public class TypeIdsItem {
    /**
     * ID索引
     */
    public int descriptorIdx;
    
    /**
     * 获取所属
     * @return
     */
    public static int getSize() {
        return 4;
    }
}
4). Proto Table

proto是method prototype代表java语言里的一个method的原型

/**
 * proto 的意思是 method prototype 代表 java 语言里的一个 method 的原型 。proto_ids 里的元素为 proto_id_item ,
 * 
 * struct DexProtoId {
        u4  shortyIdx;          // index into stringIds for shorty descriptor
        u4  returnTypeIdx;      // index into typeIds list for return type 
        u4  parametersOff;      // file offset to type_list for parameter types
    };
 *
 * @author mazaiting
 */
public class ProtoIdsItem {
    /**值为一个string_ids的index号,用来说明该method原型*/
    public int shortyIdx;
    /**值为一个type_ids的index,表示该method原型的返回值类型*/
    public int returnTypeIdx;
    /**指定method原型的参数列表type_list,若method没有参数,则值为0. 参数的格式是type_list*/
    public int parametersOff;
    
    // 这个不是公共字段,而是为了存储方法原型中的参数类型名和参数个数
    public List<String> paramtersList = new ArrayList<>();
    public int paramterCount;
    
    /**
     * 获取当前类字节数
     * @return
     */
    public static int getSize() {
        return 4 + 4 + 4;
    }
    
    @Override
    public String toString() {
        return "shortyIdx:"+shortyIdx+",returnTypeIdx:"+returnTypeIdx+",parametersOff:"+parametersOff;
    }
}
5). Field Table

field_ids区里面存放的是dex文件引用的所有field

/**
 * field_ids区里存放的是dex文件引用的所有的field,本区元素格式是field_id_item
 * 
 * struct DexFieldId {
        u2  classIdx;           // index into typeIds list for defining class
        u2  typeIdx;            // index into typeIds for field type
        u4  nameIdx;            // index into stringIds for field name
    };
 * 
 * @author mazaiting
 */
public class FieldIdsItem {
    /**
     * field所属的class类型,class_idx的值时type_ids的一个index,指向所属的类
     */
    public short classIdx;
    /**
     * field的类型,值是type_ids的一个index
     */
    public short typeIdx;
    /**
     * field的名称,它的值是string_ids的一个index
     */
    public int nameIdx;
    
    /**
     * 当前区域所占字节大小
     * @return
     */
    public static int getSize() {
        return 2 + 2 + 4;
    }
    
    @Override
    public String toString() {
        return "classIdx: " + classIdx + ",typeIdx: " + typeIdx + ",nameIdx: " + nameIdx;
    }
}
6). Method Table

method_ids是索引区的最后一个条目,它索引了dex文件里的所有的method.

/**
 * method_ids是索引区的最后一个条目,它索引了dex文件里的所有method.
 * method_ids的元素格式是method_id_item
 * 
 * struct DexMethodId {
        u2  classIdx;           // index into typeIds list for defining class
        u2  protoIdx;           // index into protoIds for method prototype
        u4  nameIdx;            // index into stringIds for method name
    };
 * 
 * @author mazaiting
 */
public class MethodIdsItem {
    /**
     * method所属的class类型,class_idx的值是type_ids的一个index,必须指向一个class类型
     */
    public short classIdx;
    /**
     * method的原型,指向proto_ids的一个index
     */
    public short protoIdx;
    /**
     * method的名称,值为string_ids的一个index
     */
    public int nameIdx;
    
    public static int getSize() {
        return 2 + 2 + 4;
    }

    @Override
    public String toString(){
        return "classIdx: " + classIdx + ",protoIdx: " + protoIdx + ",nameIdx: " + nameIdx;
    }

}
7). Class Def Table

class_defs区域里存放着class definitions,class的定义。它的结构较dex区要复杂,有的数据直接指向data区

/**
 * class_defs区域里存放着class definitions,有些数据直接指向了data区
 * 
 * struct DexClassDef {
        u4  classIdx;           /* index into typeIds for this class
        u4  accessFlags;
        u4  superclassIdx;      /* index into typeIds for superclass
        u4  interfacesOff;      /* file offset to DexTypeList
        u4  sourceFileIdx;      /* index into stringIds for source file name
        u4  annotationsOff;     /* file offset to annotations_directory_item
        u4  classDataOff;       /* file offset to class_data_item
        u4  staticValuesOff;    /* file offset to DexEncodedArray
    };
 * 
 * @author mazaiting
 */
public class ClassDefItem {
    /**
     * 描述具体的class类型,值是type_ids的一个index,值必须是一个class类型,不能是数组雷兴国或者基本类型
     */
    public int classIdx;
    /**
     * 描述class的访问类型,如public,final,static等
     */
    public int accessFlags;
    /**
     * 描述父类的类型,值必须是一个class类型,不能是数组雷兴国或者基本类型
     */
    public int superClassIdx;
    /**
     * 值为偏移地址,被指向的数据结构为type_list,class若没有interfaces,值为0
     */
    public int interfacesOff;
    /**
     * 表示源代码文件的信息,值为string_ids的一个index。若此项信息丢失,此项赋值为NO_INDEX=0xFFFFFFFF
     */
    public int sourceFileIdx;
    /**
     * 值为偏移地址,指向的内容是该class的注解,位置在data区,格式为annotations_directory_item,若没有此项,值为0
     */
    public int annotationsOff;
    /**
     * 值为偏移地址,指向的内容是该class的使用到的数据,位置在data区,格式为class_data_item。无偶没有此项,则值为0
     */
    public int classDataOff;
    /**
     * 值为偏移地址,指向data区里的一个列表,格式为encoded_array_item。若没有此项,值为0.
     */
    public int staticValueOff;
    
    /**
     * enum {
            ACC_PUBLIC       = 0x00000001,       // class, field, method, ic
            ACC_PRIVATE      = 0x00000002,       // field, method, ic
            ACC_PROTECTED    = 0x00000004,       // field, method, ic
            ACC_STATIC       = 0x00000008,       // field, method, ic
            ACC_FINAL        = 0x00000010,       // class, field, method, ic
            ACC_SYNCHRONIZED = 0x00000020,       // method (only allowed on natives)
            ACC_SUPER        = 0x00000020,       // class (not used in Dalvik)
            ACC_VOLATILE     = 0x00000040,       // field
            ACC_BRIDGE       = 0x00000040,       // method (1.5)
            ACC_TRANSIENT    = 0x00000080,       // field
            ACC_VARARGS      = 0x00000080,       // method (1.5)
            ACC_NATIVE       = 0x00000100,       // method
            ACC_INTERFACE    = 0x00000200,       // class, ic
            ACC_ABSTRACT     = 0x00000400,       // class, method, ic
            ACC_STRICT       = 0x00000800,       // method
            ACC_SYNTHETIC    = 0x00001000,       // field, method, ic
            ACC_ANNOTATION   = 0x00002000,       // class, ic (1.5)
            ACC_ENUM         = 0x00004000,       // class, field, ic (1.5)
            ACC_CONSTRUCTOR  = 0x00010000,       // method (Dalvik only)
            ACC_DECLARED_SYNCHRONIZED =
                               0x00020000,       // method (Dalvik only)
            ACC_CLASS_MASK =
                (ACC_PUBLIC | ACC_FINAL | ACC_INTERFACE | ACC_ABSTRACT
                        | ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM),
            ACC_INNER_CLASS_MASK =
                (ACC_CLASS_MASK | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC),
            ACC_FIELD_MASK =
                (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL
                        | ACC_VOLATILE | ACC_TRANSIENT | ACC_SYNTHETIC | ACC_ENUM),
            ACC_METHOD_MASK =
                (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL
                        | ACC_SYNCHRONIZED | ACC_BRIDGE | ACC_VARARGS | ACC_NATIVE
                        | ACC_ABSTRACT | ACC_STRICT | ACC_SYNTHETIC | ACC_CONSTRUCTOR
                        | ACC_DECLARED_SYNCHRONIZED),
        };

     */
    
    /**
     * 访问修饰符
     */
    public static final int 
        ACC_PUBLIC       = 0x00000001,       // class, field, method, ic
        ACC_PRIVATE      = 0x00000002,       // field, method, ic
        ACC_PROTECTED    = 0x00000004,       // field, method, ic
        ACC_STATIC       = 0x00000008,       // field, method, ic
        ACC_FINAL        = 0x00000010,       // class, field, method, ic
        ACC_SYNCHRONIZED = 0x00000020,       // method (only allowed on natives)
        ACC_SUPER        = 0x00000020,       // class (not used in Dalvik)
        ACC_VOLATILE     = 0x00000040,       // field
        ACC_BRIDGE       = 0x00000040,       // method (1.5)
        ACC_TRANSIENT    = 0x00000080,       // field
        ACC_VARARGS      = 0x00000080,       // method (1.5)
        ACC_NATIVE       = 0x00000100,       // method
        ACC_INTERFACE    = 0x00000200,       // class, ic
        ACC_ABSTRACT     = 0x00000400,       // class, method, ic
        ACC_STRICT       = 0x00000800,       // method
        ACC_SYNTHETIC    = 0x00001000,       // field, method, ic
        ACC_ANNOTATION   = 0x00002000,       // class, ic (1.5)
        ACC_ENUM         = 0x00004000,       // class, field, ic (1.5)
        ACC_CONSTRUCTOR  = 0x00010000,       // method (Dalvik only)
        ACC_DECLARED_SYNCHRONIZED =
                           0x00020000,       // method (Dalvik only)
        ACC_CLASS_MASK =
            (ACC_PUBLIC | ACC_FINAL | ACC_INTERFACE | ACC_ABSTRACT
                    | ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM),
        ACC_INNER_CLASS_MASK =
            (ACC_CLASS_MASK | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC),
        ACC_FIELD_MASK =
            (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL
                    | ACC_VOLATILE | ACC_TRANSIENT | ACC_SYNTHETIC | ACC_ENUM),
        ACC_METHOD_MASK =
            (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL
                    | ACC_SYNCHRONIZED | ACC_BRIDGE | ACC_VARARGS | ACC_NATIVE
                    | ACC_ABSTRACT | ACC_STRICT | ACC_SYNTHETIC | ACC_CONSTRUCTOR
                    | ACC_DECLARED_SYNCHRONIZED);

    /**
     * 获取当前区域所占字节数
     * @return
     */
    public static int getSize() {
        return 4 * 8;
    }
    
    @Override
    public String toString(){
        return "classIdx: " + classIdx + ",accessFlags: " + accessFlags + ",superClassIdx: " + superClassIdx + 
                ",iterfacesOff: " + interfacesOff + ",sourceFileIdx: " + sourceFileIdx + ",annotationsOff: " + 
                annotationsOff + ",classDataOff: " + classDataOff + ",staticValueOff: " + staticValueOff;
    }

}

class_data_off指向data区里的class_data_itetm结构,class_data_item里存放着本class使用到的各种数据.

/**
 * class_data_off指向data区里的class_data_item结构,class_data_item里存放着本class使用到的各种数据
 * 
 *  struct class_data_item  {
        uleb128 static_fields_size;
        uleb128 instance_fields_size;
        uleb128 direct_methods_size;
        uleb128 virtual_methods_size;
        encoded_field static_fields [ static_fields_size ];
        encoded_field instance_fields [ instance_fields_size ];
        encoded_method direct_methods [ direct_method_size ];
        encoded_method virtual_methods [ virtual_methods_size ];
    };

 * 
 * @author mazaiting
 */
public class ClassDataItem {
    /**
     * 静态字段的大小
     */
    public int staticFieldsSize;
    /**
     * 实例化字段的大小
     */
    public int instanceFieldsSize;
    /**
     * 实现方法的大小
     */
    public int directMethodsSize;
    /**
     * 虚拟方法的大小
     */
    public int virtualMethodsSize;
    /**
     * 静态字段数组
     */
    public EncodeField[] staticFields;
    /**
     * 实例化字段数组
     */
    public EncodeField[] instanceFields;
    /**
     * 实现方法数组
     */
    public EncodeMethod[] directMethods;
    /**
     * 虚拟方法的数组
     */
    public EncodeMethod[] virtualMethods;
    
    @Override
    public String toString(){
        return "staticFieldsSize: " + staticFieldsSize + ",instanceFieldsSize: " + instanceFieldsSize
                + ",directMethodsSize:" + directMethodsSize + ",virtualMethodsSize: " + virtualMethodsSize
                + "\n" + getFieldsAndMethods();
    }



    private String getFieldsAndMethods() {
        StringBuilder sb = new StringBuilder();
        sb.append("staticFields:\n");
        for(int i=0;i<staticFields.length;i++){
            sb.append(staticFields[i]+"\n");
        }
        sb.append("instanceFields:\n");
        for(int i=0;i<instanceFields.length;i++){
            sb.append(instanceFields[i]+"\n");
        }
        sb.append("directMethods:\n");
        for(int i=0;i<directMethods.length;i++){
            sb.append(directMethods[i]+"\n");
        }
        sb.append("virtualMethods:\n");
        for(int i=0;i<virtualMethods.length;i++){
            sb.append(virtualMethods[i]+"\n");
        }
        return sb.toString();
    }

}
8). Data Section
/**
 * 代码条目
 * 
 *  (1) 一个 .dex 文件被分成了 9 个区 ,其中有一个索引区叫做class_defs , 索引了 .dex 里面用到的 class ,以及对这个 class 的描述 。
 *  (2) class_defs 区 , 里面其实是class_def_item 结构 。这个结构里描述了 LHello; 的各种信息 ,诸如名称 ,superclass , access flag,
 *       interface 等 。class_def_item 里有一个元素 class_data_off , 指向data 区里的一个 class_data_item 结构 ,
 *       用来描述 class 使用到的各种数据 。自此以后的结构都归于 data区了 。
 *  (3) class_data_item 结构 ,里描述值着 class 里使用到的 static field , instance field , direct_method ,和 virtual_method 
 *      的数目和描述 。例子 Hello.dex 里 ,只有 2 个 direct_method , 其余的 field 和method 的数目都为 0 。描述 direct_method 的结构叫
 *      做 encoded_method ,是用来详细描述某个 method的 。
 *  (4) encoded_method 结构 ,描述某个 method 的 method 类型 , access flags 和一个指向 code_item的偏移地址 ,里面存放的是该 method 
 *      的具体实现 。
 *  (5) code_item ,结构里描述着某个 method 的具体实现 。
 * 
 * struct DexCode {
        u2  registersSize;
        u2  insSize;
        u2  outsSize;
        u2  triesSize;
        u4  debugInfoOff;       /* file offset to debug info stream
        u4  insnsSize;          /* size of the insns array, in u2 units
        u2  insns[1];
        /* followed by optional u2 padding
        /* followed by try_item[triesSize]
        /* followed by uleb128 handlersSize
        /* followed by catch_handler_item[handlersSize]
    };
 * 
 * @author mazaiting
 *
 */
public class CodeItem {
    /**
     * 本段代码使用到的寄存器数目
     */
    public short registersSize;
    /**
     * method传入参数的数目
     */
    public short insSize;
    /**
     * 本段代码调用其他方法时需要的参数个数
     */
    public short outsSize;
    /**
     * try_item结构的个数
     */
    public short triesSize;
    /**
     * 偏移地址,指向本段代码的debug信息存放位置,是一个debug_info_item结构
     */
    public int debugInfoOff;
    /**
     * 指令列表的大小,以16-bit为单位。insns是instructions的缩写
     */
    public int insnsSize;
    /**
     * insns数组
     */
    public short[] insns;
    
    /**tries和handlers用于处理java中的exception*/
    
    @Override
    public String toString(){
        return "registersSize: " + registersSize + ",insSize: " + insSize + ",outsSize: " + outsSize + 
                ",triesSize: " + triesSize + ",debugInfoOff: " + debugInfoOff + ",insnsSize: " + insnsSize 
                + "\ninsns: " + getInsnsStr();
    }
    
    private String getInsnsStr(){
        StringBuilder sb = new StringBuilder();
        if (insns != null && insns.length > 0) {
            for(int i=0;i<insns.length;i++){
                sb.append(Util.bytesToHexString(Util.short2Byte(insns[i]))+",");
            }   
        }
        
        return sb.toString();
    }
}

参考文章

Android逆向之旅---解析编译之后的Dex文件格式

代码下载

目录
相关文章
|
8天前
|
设计模式 前端开发 Android开发
Android应用开发中的MVP架构模式解析
【5月更文挑战第25天】本文深入探讨了在Android应用开发中广泛采用的一种设计模式——Model-View-Presenter (MVP)。文章首先概述了MVP架构的基本概念和组件,接着分析了它与传统MVC模式的区别,并详细阐述了如何在实际开发中实现MVP架构。最后,通过一个具体案例,展示了MVP架构如何提高代码的可维护性和可测试性,以及它给开发者带来的其他潜在好处。
|
10天前
|
自然语言处理 前端开发 API
解析CSS文件
【5月更文挑战第23天】解析CSS文件。在Python中,可以使用一些第三方库来帮助解析CSS文件,例如`cssutils`。
17 2
|
11天前
|
IDE 开发工具 Python
python中SyntaxError: unexpected EOF while parsing(语法错误:解析时遇到意外的文件结束)
【5月更文挑战第14天】python中SyntaxError: unexpected EOF while parsing(语法错误:解析时遇到意外的文件结束)
37 6
|
2天前
|
Java Android开发
|
7天前
|
Android开发
【苹果安卓通用】xlsx 和 vCard 文件转换器,txt转vCard文件格式,CSV转 vCard格式,如何批量号码导入手机通讯录,一篇文章说全
本文介绍了如何快速将批量号码导入手机通讯录,适用于企业客户管理、营销团队、活动组织、团队协作和新员工入职等场景。步骤包括:1) 下载软件,提供腾讯云盘和百度网盘链接;2) 打开软件,复制粘贴号码并进行加载预览和制作文件;3) 将制作好的文件通过QQ或微信发送至手机,然后按苹果、安卓或鸿蒙系统的指示导入。整个过程简便快捷,可在1分钟内完成。
|
8天前
|
缓存 Java Android开发
构建高效的Android应用:内存优化策略解析
【5月更文挑战第25天】在移动开发领域,性能优化一直是一个不断探讨和精进的课题。特别是对于资源受限的Android设备来说,合理的内存管理直接关系到应用的流畅度和用户体验。本文深入分析了Android内存管理的机制,并提出了几种实用的内存优化技巧。通过代码示例和实践案例,我们旨在帮助开发者识别和解决内存瓶颈,从而提升应用性能。
|
8天前
|
API vr&ar 开发工具
构建未来:安卓平台上的AR应用开发全解析
【5月更文挑战第25天】随着增强现实(AR)技术的不断成熟,安卓平台上的AR应用开发正吸引着越来越多的关注。本文深入剖析了在安卓系统上开发AR应用的核心技术和流程,探讨了ARCore SDK的使用、3D渲染技术、用户交互设计以及性能优化等关键要素。通过实例演示和代码分析,揭示了创建高效、沉浸式AR体验的策略和最佳实践,为开发者提供指引,同时对未来AR应用的发展趋势做出展望。
|
11天前
|
Java Android开发
Android编译的jar里面是dex
Android编译的jar里面是dex
16 0
|
12天前
|
移动开发 Java Android开发
构建高效的Android应用:内存优化策略解析
【5月更文挑战第21天】在移动开发领域,尤其是面向资源受限的Android设备,内存管理与优化是提升应用性能和用户体验的关键因素。本文深入探讨了Android内存优化的多个方面,包括内存泄漏的预防、合理的内存分配策略、以及有效的内存回收机制。通过分析内存管理的原理和提供实用的编码实践,开发者可以显著减少其应用的内存占用,从而避免常见的性能瓶颈和应用程序崩溃问题。
|
9天前
|
移动开发 网络协议 安全
HTML5页面被运营商DNS问题及解决方案,app中h5页面源码的获取
HTML5页面被运营商DNS问题及解决方案,app中h5页面源码的获取
64 4

推荐镜像

更多