feat : first

This commit is contained in:
eggmanQQQ2
2025-07-07 10:26:00 +08:00
commit 69ff71e027
17 changed files with 2163 additions and 0 deletions

64
DeleteEmptyFolders.java Normal file
View File

@@ -0,0 +1,64 @@
import java.io.File;
import java.util.HashSet;
import java.util.Set;
public class DeleteEmptyFolders {
// 白名单路径集合
private static final Set<String> WHITELIST_PATHS = new HashSet<>();
static {
// 添加白名单路径,请根据实际需求修改
// WHITELIST_PATHS.add("/Users/zhanglaiman/WorkSpace/Molistar/core/src/main/java/com");
}
public static void main(String[] args) {
String targetDir = "/Users/zhanglaiman/WorkSpace/Molistar/app";
File rootDir = new File(targetDir);
deleteEmptyFolders(rootDir);
}
private static boolean isDirectoryEmpty(File dir) {
File[] files = dir.listFiles();
if (files == null || files.length == 0) {
return true;
}
for (File file : files) {
if (file.isDirectory()) {
if (!isDirectoryEmpty(file)) {
return false;
}
} else if (file.getName().endsWith(".java")) {
return false;
}
}
return true;
}
private static void deleteEmptyFolders(File dir) {
if (dir == null || !dir.exists() || dir.isFile()) {
return;
}
// 跳过白名单中的路径
if (WHITELIST_PATHS.contains(dir.getAbsolutePath())) {
System.out.println("跳过白名单路径: " + dir.getAbsolutePath());
return;
}
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
deleteEmptyFolders(file);
}
}
}
if (isDirectoryEmpty(dir)) {
System.out.println("删除空文件夹: " + dir.getAbsolutePath());
dir.delete();
}
}
}

998
apkinfo.py Normal file
View File

@@ -0,0 +1,998 @@
# This file is part of Androguard.
#
# Copyright (C) 2012, Anthony Desnos <desnos at t0t0.fr>
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 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.
import StringIO
from struct import pack, unpack
from xml.sax.saxutils import escape
from zlib import crc32
import re
import collections
import sys
import os
import logging
import types
import random
import string
import imp
from xml.dom import minidom
NS_ANDROID_URI = 'http://schemas.android.com/apk/res/android'
ZIPMODULE = 1
def read(filename, binary=True):
with open(filename, 'rb' if binary else 'r') as f:
return f.read()
def sign_apk(filename, keystore, storepass):
from subprocess import Popen, PIPE, STDOUT
compile = Popen([CONF["PATH_JARSIGNER"], "-sigalg", "MD5withRSA",
"-digestalg", "SHA1", "-storepass", storepass, "-keystore",
keystore, filename, "alias_name"],
stdout=PIPE,
stderr=STDOUT)
stdout, stderr = compile.communicate()
class Error(Exception):
"""Base class for exceptions in this module."""
pass
class FileNotPresent(Error):
pass
######################################################## APK FORMAT ########################################################
class APK(object):
"""
This class can access to all elements in an APK file
:param filename: specify the path of the file, or raw data
:param raw: specify if the filename is a path or raw data (optional)
:param mode: specify the mode to open the file (optional)
:param magic_file: specify the magic file (optional)
:param zipmodule: specify the type of zip module to use (0:chilkat, 1:zipfile, 2:patch zipfile)
:type filename: string
:type raw: boolean
:type mode: string
:type magic_file: string
:type zipmodule: int
:Example:
APK("myfile.apk")
APK(read("myfile.apk"), raw=True)
"""
def __init__(self,
filename,
raw=False,
mode="r",
magic_file=None,
zipmodule=ZIPMODULE):
self.filename = filename
self.xml = {}
self.axml = {}
self.arsc = {}
self.package = ""
self.androidversion = {}
self.permissions = []
self.declared_permissions = {}
self.valid_apk = False
self.files = {}
self.files_crc32 = {}
self.magic_file = magic_file
if raw is True:
self.__raw = filename
else:
self.__raw = read(filename)
self.zipmodule = zipmodule
if zipmodule == 0:
self.zip = ChilkatZip(self.__raw)
elif zipmodule == 2:
from androguard.patch import zipfile
self.zip = zipfile.ZipFile(StringIO.StringIO(self.__raw), mode=mode)
else:
import zipfile
self.zip = zipfile.ZipFile(StringIO.StringIO(self.__raw), mode=mode)
for i in self.zip.namelist():
if i == "AndroidManifest.xml":
self.axml[i] = AXMLPrinter(self.zip.read(i))
try:
self.xml[i] = minidom.parseString(self.axml[i].get_buff())
except:
self.xml[i] = None
if self.xml[i] != None:
self.package = self.xml[i].documentElement.getAttribute(
"package")
self.androidversion[
"Code"
] = self.xml[i].documentElement.getAttributeNS(
NS_ANDROID_URI, "versionCode")
self.androidversion[
"Name"
] = self.xml[i].documentElement.getAttributeNS(
NS_ANDROID_URI, "versionName")
self.valid_apk = True
def get_AndroidManifest(self):
"""
Return the Android Manifest XML file
:rtype: xml object
"""
return self.xml["AndroidManifest.xml"]
def is_valid_APK(self):
"""
Return true if the APK is valid, false otherwise
:rtype: boolean
"""
return self.valid_apk
def get_filename(self):
"""
Return the filename of the APK
:rtype: string
"""
return self.filename
def get_package(self):
"""
Return the name of the package
:rtype: string
"""
return self.package
def get_version_code(self):
"""
Return the android version code
:rtype: string
"""
return self.androidversion["Code"]
def get_version_name(self):
"""
Return the android version name
:rtype: string
"""
return self.androidversion["Name"]
def get_raw(self):
"""
Return raw bytes of the APK
:rtype: string
"""
return self.__raw
def get_elements(self, tag_name, attribute):
"""
Return elements in xml files which match with the tag name and the specific attribute
:param tag_name: a string which specify the tag name
:param attribute: a string which specify the attribute
"""
l = []
for i in self.xml:
for item in self.xml[i].getElementsByTagName(tag_name):
value = item.getAttributeNS(NS_ANDROID_URI, attribute)
value = self.format_value(value)
l.append(str(value))
return l
def format_value(self, value):
if len(value) > 0:
if value[0] == ".":
value = self.package + value
else:
v_dot = value.find(".")
if v_dot == 0:
value = self.package + "." + value
elif v_dot == -1:
value = self.package + "." + value
return value
def get_element(self, tag_name, attribute, **attribute_filter):
"""
Return element in xml files which match with the tag name and the specific attribute
:param tag_name: specify the tag name
:type tag_name: string
:param attribute: specify the attribute
:type attribute: string
:rtype: string
"""
for i in self.xml:
for item in self.xml[i].getElementsByTagName(tag_name):
skip_this_item = False
for attr, val in attribute_filter.items():
attr_val = item.getAttributeNS(NS_ANDROID_URI, attr)
if attr_val != val:
skip_this_item = True
break
if skip_this_item:
continue
value = item.getAttributeNS(NS_ANDROID_URI, attribute)
if len(value) > 0:
return value
return None
def get_max_sdk_version(self):
"""
Return the android:maxSdkVersion attribute
:rtype: string
"""
return self.get_element("uses-sdk", "maxSdkVersion")
def get_min_sdk_version(self):
"""
Return the android:minSdkVersion attribute
:rtype: string
"""
return self.get_element("uses-sdk", "minSdkVersion")
def get_target_sdk_version(self):
"""
Return the android:targetSdkVersion attribute
:rtype: string
"""
return self.get_element("uses-sdk", "targetSdkVersion")
def get_android_manifest_axml(self):
"""
Return the :class:`AXMLPrinter` object which corresponds to the AndroidManifest.xml file
:rtype: :class:`AXMLPrinter`
"""
try:
return self.axml["AndroidManifest.xml"]
except KeyError:
return None
def get_android_manifest_xml(self):
"""
Return the xml object which corresponds to the AndroidManifest.xml file
:rtype: object
"""
try:
return self.xml["AndroidManifest.xml"]
except KeyError:
return None
def show(self):
print "PACKAGE: ", self.get_package()
print "VERSION NAME:", self.get_version_name()
print "VERSION CODE:", self.get_version_code()
################################## AXML FORMAT ########################################
# Translated from
# http://code.google.com/p/android4me/source/browse/src/android/content/res/AXmlResourceParser.java
UTF8_FLAG = 0x00000100
CHUNK_STRINGPOOL_TYPE = 0x001C0001
CHUNK_NULL_TYPE = 0x00000000
class StringBlock(object):
def __init__(self, buff):
self.start = buff.get_idx()
self._cache = {}
self.header_size, self.header = self.skipNullPadding(buff)
self.chunkSize = unpack('<i', buff.read(4))[0]
self.stringCount = unpack('<i', buff.read(4))[0]
self.styleOffsetCount = unpack('<i', buff.read(4))[0]
self.flags = unpack('<i', buff.read(4))[0]
self.m_isUTF8 = ((self.flags & UTF8_FLAG) != 0)
self.stringsOffset = unpack('<i', buff.read(4))[0]
self.stylesOffset = unpack('<i', buff.read(4))[0]
self.m_stringOffsets = []
self.m_styleOffsets = []
self.m_charbuff = ""
self.m_styles = []
for i in range(0, self.stringCount):
self.m_stringOffsets.append(unpack('<i', buff.read(4))[0])
for i in range(0, self.styleOffsetCount):
self.m_styleOffsets.append(unpack('<i', buff.read(4))[0])
size = self.chunkSize - self.stringsOffset
if self.stylesOffset != 0:
size = self.stylesOffset - self.stringsOffset
# FIXME
if (size % 4) != 0:
warning("ooo")
self.m_charbuff = buff.read(size)
if self.stylesOffset != 0:
size = self.chunkSize - self.stylesOffset
# FIXME
if (size % 4) != 0:
warning("ooo")
for i in range(0, size / 4):
self.m_styles.append(unpack('<i', buff.read(4))[0])
def skipNullPadding(self, buff):
def readNext(buff, first_run=True):
header = unpack('<i', buff.read(4))[0]
if header == CHUNK_NULL_TYPE and first_run:
info("Skipping null padding in StringBlock header")
header = readNext(buff, first_run=False)
elif header != CHUNK_STRINGPOOL_TYPE:
warning("Invalid StringBlock header")
return header
header = readNext(buff)
return header >> 8, header & 0xFF
def getString(self, idx):
if idx in self._cache:
return self._cache[idx]
if idx < 0 or not self.m_stringOffsets or idx >= len(
self.m_stringOffsets):
return ""
offset = self.m_stringOffsets[idx]
if self.m_isUTF8:
self._cache[idx] = self.decode8(offset)
else:
self._cache[idx] = self.decode16(offset)
return self._cache[idx]
def getStyle(self, idx):
# FIXME
return self.m_styles[idx]
def decode8(self, offset):
str_len, skip = self.decodeLength(offset, 1)
offset += skip
encoded_bytes, skip = self.decodeLength(offset, 1)
offset += skip
data = self.m_charbuff[offset: offset + encoded_bytes]
return self.decode_bytes(data, 'utf-8', str_len)
def decode16(self, offset):
str_len, skip = self.decodeLength(offset, 2)
offset += skip
encoded_bytes = str_len * 2
data = self.m_charbuff[offset: offset + encoded_bytes]
return self.decode_bytes(data, 'utf-16', str_len)
def decode_bytes(self, data, encoding, str_len):
string = data.decode(encoding, 'replace')
if len(string) != str_len:
warning("invalid decoded string length")
return string
def decodeLength(self, offset, sizeof_char):
length = ord(self.m_charbuff[offset])
sizeof_2chars = sizeof_char << 1
fmt_chr = 'B' if sizeof_char == 1 else 'H'
fmt = "<2" + fmt_chr
length1, length2 = unpack(fmt, self.m_charbuff[offset:(offset + sizeof_2chars)])
highbit = 0x80 << (8 * (sizeof_char - 1))
if (length & highbit) != 0:
return ((length1 & ~highbit) << (8 * sizeof_char)) | length2, sizeof_2chars
else:
return length1, sizeof_char
def show(self):
print "StringBlock(%x, %x, %x, %x, %x, %x" % (
self.start,
self.header,
self.header_size,
self.chunkSize,
self.stringsOffset,
self.flags)
for i in range(0, len(self.m_stringOffsets)):
print i, repr(self.getString(i))
START_DOCUMENT = 0
END_DOCUMENT = 1
START_TAG = 2
END_TAG = 3
TEXT = 4
ATTRIBUTE_IX_NAMESPACE_URI = 0
ATTRIBUTE_IX_NAME = 1
ATTRIBUTE_IX_VALUE_STRING = 2
ATTRIBUTE_IX_VALUE_TYPE = 3
ATTRIBUTE_IX_VALUE_DATA = 4
ATTRIBUTE_LENGHT = 5
CHUNK_AXML_FILE = 0x00080003
CHUNK_RESOURCEIDS = 0x00080180
CHUNK_XML_FIRST = 0x00100100
CHUNK_XML_START_NAMESPACE = 0x00100100
CHUNK_XML_END_NAMESPACE = 0x00100101
CHUNK_XML_START_TAG = 0x00100102
CHUNK_XML_END_TAG = 0x00100103
CHUNK_XML_TEXT = 0x00100104
CHUNK_XML_LAST = 0x00100104
class AXMLParser(object):
def __init__(self, raw_buff):
self.reset()
self.valid_axml = True
self.buff = BuffHandle(raw_buff)
axml_file = unpack('<L', self.buff.read(4))[0]
if axml_file == CHUNK_AXML_FILE:
self.buff.read(4)
self.sb = StringBlock(self.buff)
self.m_resourceIDs = []
self.m_prefixuri = {}
self.m_uriprefix = {}
self.m_prefixuriL = []
self.visited_ns = []
else:
self.valid_axml = False
warning("Not a valid xml file")
def is_valid(self):
return self.valid_axml
def reset(self):
self.m_event = -1
self.m_lineNumber = -1
self.m_name = -1
self.m_namespaceUri = -1
self.m_attributes = []
self.m_idAttribute = -1
self.m_classAttribute = -1
self.m_styleAttribute = -1
def next(self):
self.doNext()
return self.m_event
def doNext(self):
if self.m_event == END_DOCUMENT:
return
event = self.m_event
self.reset()
while True:
chunkType = -1
# Fake END_DOCUMENT event.
if event == END_TAG:
pass
# START_DOCUMENT
if event == START_DOCUMENT:
chunkType = CHUNK_XML_START_TAG
else:
if self.buff.end():
self.m_event = END_DOCUMENT
break
chunkType = unpack('<L', self.buff.read(4))[0]
if chunkType == CHUNK_RESOURCEIDS:
chunkSize = unpack('<L', self.buff.read(4))[0]
# FIXME
if chunkSize < 8 or chunkSize % 4 != 0:
warning("Invalid chunk size")
for i in range(0, chunkSize / 4 - 2):
self.m_resourceIDs.append(
unpack('<L', self.buff.read(4))[0])
continue
# FIXME
if chunkType < CHUNK_XML_FIRST or chunkType > CHUNK_XML_LAST:
warning("invalid chunk type")
# Fake START_DOCUMENT event.
if chunkType == CHUNK_XML_START_TAG and event == -1:
self.m_event = START_DOCUMENT
break
self.buff.read(4) # /*chunkSize*/
lineNumber = unpack('<L', self.buff.read(4))[0]
self.buff.read(4) # 0xFFFFFFFF
if chunkType == CHUNK_XML_START_NAMESPACE or chunkType == CHUNK_XML_END_NAMESPACE:
if chunkType == CHUNK_XML_START_NAMESPACE:
prefix = unpack('<L', self.buff.read(4))[0]
uri = unpack('<L', self.buff.read(4))[0]
self.m_prefixuri[prefix] = uri
self.m_uriprefix[uri] = prefix
self.m_prefixuriL.append((prefix, uri))
self.ns = uri
else:
self.ns = -1
self.buff.read(4)
self.buff.read(4)
(prefix, uri) = self.m_prefixuriL.pop()
continue
self.m_lineNumber = lineNumber
if chunkType == CHUNK_XML_START_TAG:
self.m_namespaceUri = unpack('<L', self.buff.read(4))[0]
self.m_name = unpack('<L', self.buff.read(4))[0]
# FIXME
self.buff.read(4) # flags
attributeCount = unpack('<L', self.buff.read(4))[0]
self.m_idAttribute = (attributeCount >> 16) - 1
attributeCount = attributeCount & 0xFFFF
self.m_classAttribute = unpack('<L', self.buff.read(4))[0]
self.m_styleAttribute = (self.m_classAttribute >> 16) - 1
self.m_classAttribute = (self.m_classAttribute & 0xFFFF) - 1
for i in range(0, attributeCount * ATTRIBUTE_LENGHT):
self.m_attributes.append(unpack('<L', self.buff.read(4))[0])
for i in range(ATTRIBUTE_IX_VALUE_TYPE, len(self.m_attributes),
ATTRIBUTE_LENGHT):
self.m_attributes[i] = self.m_attributes[i] >> 24
self.m_event = START_TAG
break
if chunkType == CHUNK_XML_END_TAG:
self.m_namespaceUri = unpack('<L', self.buff.read(4))[0]
self.m_name = unpack('<L', self.buff.read(4))[0]
self.m_event = END_TAG
break
if chunkType == CHUNK_XML_TEXT:
self.m_name = unpack('<L', self.buff.read(4))[0]
# FIXME
self.buff.read(4)
self.buff.read(4)
self.m_event = TEXT
break
def getPrefixByUri(self, uri):
try:
return self.m_uriprefix[uri]
except KeyError:
return -1
def getPrefix(self):
try:
return self.sb.getString(self.m_uriprefix[self.m_namespaceUri])
except KeyError:
return u''
def getName(self):
if self.m_name == -1 or (self.m_event != START_TAG and
self.m_event != END_TAG):
return u''
return self.sb.getString(self.m_name)
def getText(self):
if self.m_name == -1 or self.m_event != TEXT:
return u''
return self.sb.getString(self.m_name)
def getNamespacePrefix(self, pos):
prefix = self.m_prefixuriL[pos][0]
return self.sb.getString(prefix)
def getNamespaceUri(self, pos):
uri = self.m_prefixuriL[pos][1]
return self.sb.getString(uri)
def getXMLNS(self):
buff = ""
for i in self.m_uriprefix:
if i not in self.visited_ns:
buff += "xmlns:%s=\"%s\"\n" % (
self.sb.getString(self.m_uriprefix[i]),
self.sb.getString(self.m_prefixuri[self.m_uriprefix[i]]))
self.visited_ns.append(i)
return buff
def getNamespaceCount(self, pos):
pass
def getAttributeOffset(self, index):
# FIXME
if self.m_event != START_TAG:
warning("Current event is not START_TAG.")
offset = index * 5
# FIXME
if offset >= len(self.m_attributes):
warning("Invalid attribute index")
return offset
def getAttributeCount(self):
if self.m_event != START_TAG:
return -1
return len(self.m_attributes) / ATTRIBUTE_LENGHT
def getAttributePrefix(self, index):
offset = self.getAttributeOffset(index)
uri = self.m_attributes[offset + ATTRIBUTE_IX_NAMESPACE_URI]
prefix = self.getPrefixByUri(uri)
if prefix == -1:
return ""
return self.sb.getString(prefix)
def getAttributeName(self, index):
offset = self.getAttributeOffset(index)
name = self.m_attributes[offset + ATTRIBUTE_IX_NAME]
if name == -1:
return ""
res = self.sb.getString(name)
if not res:
attr = self.m_resourceIDs[name]
if attr in SYSTEM_RESOURCES['attributes']['inverse']:
res = 'android:' + SYSTEM_RESOURCES['attributes']['inverse'][
attr
]
return res
def getAttributeValueType(self, index):
offset = self.getAttributeOffset(index)
return self.m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE]
def getAttributeValueData(self, index):
offset = self.getAttributeOffset(index)
return self.m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA]
def getAttributeValue(self, index):
offset = self.getAttributeOffset(index)
valueType = self.m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE]
if valueType == TYPE_STRING:
valueString = self.m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING]
return self.sb.getString(valueString)
# WIP
return ""
# resource constants
TYPE_ATTRIBUTE = 2
TYPE_DIMENSION = 5
TYPE_FIRST_COLOR_INT = 28
TYPE_FIRST_INT = 16
TYPE_FLOAT = 4
TYPE_FRACTION = 6
TYPE_INT_BOOLEAN = 18
TYPE_INT_DEC = 16
TYPE_INT_HEX = 17
TYPE_LAST_COLOR_INT = 31
TYPE_LAST_INT = 31
TYPE_NULL = 0
TYPE_REFERENCE = 1
TYPE_STRING = 3
TYPE_TABLE = {
TYPE_ATTRIBUTE: "attribute",
TYPE_DIMENSION: "dimension",
TYPE_FLOAT: "float",
TYPE_FRACTION: "fraction",
TYPE_INT_BOOLEAN: "int_boolean",
TYPE_INT_DEC: "int_dec",
TYPE_INT_HEX: "int_hex",
TYPE_NULL: "null",
TYPE_REFERENCE: "reference",
TYPE_STRING: "string",
}
RADIX_MULTS = [0.00390625, 3.051758E-005, 1.192093E-007, 4.656613E-010]
DIMENSION_UNITS = ["px", "dip", "sp", "pt", "in", "mm"]
FRACTION_UNITS = ["%", "%p"]
COMPLEX_UNIT_MASK = 15
def complexToFloat(xcomplex):
return (float)(xcomplex & 0xFFFFFF00) * RADIX_MULTS[(xcomplex >> 4) & 3]
def getPackage(id):
if id >> 24 == 1:
return "android:"
return ""
def format_value(_type, _data, lookup_string=lambda ix: "<string>"):
if _type == TYPE_STRING:
return lookup_string(_data)
elif _type == TYPE_ATTRIBUTE:
return "?%s%08X" % (getPackage(_data), _data)
elif _type == TYPE_REFERENCE:
return "@%s%08X" % (getPackage(_data), _data)
elif _type == TYPE_FLOAT:
return "%f" % unpack("=f", pack("=L", _data))[0]
elif _type == TYPE_INT_HEX:
return "0x%08X" % _data
elif _type == TYPE_INT_BOOLEAN:
if _data == 0:
return "false"
return "true"
elif _type == TYPE_DIMENSION:
return "%f%s" % (complexToFloat(_data), DIMENSION_UNITS[_data & COMPLEX_UNIT_MASK])
elif _type == TYPE_FRACTION:
return "%f%s" % (complexToFloat(_data) * 100, FRACTION_UNITS[_data & COMPLEX_UNIT_MASK])
elif _type >= TYPE_FIRST_COLOR_INT and _type <= TYPE_LAST_COLOR_INT:
return "#%08X" % _data
elif _type >= TYPE_FIRST_INT and _type <= TYPE_LAST_INT:
return "%d" % long2int(_data)
return "<0x%X, type 0x%02X>" % (_data, _type)
class AXMLPrinter(object):
def __init__(self, raw_buff):
self.axml = AXMLParser(raw_buff)
self.xmlns = False
self.buff = u''
while True and self.axml.is_valid():
_type = self.axml.next()
if _type == START_DOCUMENT:
self.buff += u'<?xml version="1.0" encoding="utf-8"?>\n'
elif _type == START_TAG:
self.buff += u'<' + self.getPrefix(self.axml.getPrefix(
)) + self.axml.getName() + u'\n'
self.buff += self.axml.getXMLNS()
for i in range(0, self.axml.getAttributeCount()):
self.buff += "%s%s=\"%s\"\n" % (
self.getPrefix(
self.axml.getAttributePrefix(i)),
self.axml.getAttributeName(i),
self._escape(self.getAttributeValue(i)))
self.buff += u'>\n'
elif _type == END_TAG:
self.buff += "</%s%s>\n" % (
self.getPrefix(self.axml.getPrefix()), self.axml.getName())
elif _type == TEXT:
self.buff += "%s\n" % self.axml.getText()
elif _type == END_DOCUMENT:
break
# pleed patch
def _escape(self, s):
s = s.replace("&", "&amp;")
s = s.replace('"', "&quot;")
s = s.replace("'", "&apos;")
s = s.replace("<", "&lt;")
s = s.replace(">", "&gt;")
return escape(s)
def get_buff(self):
return self.buff.encode('utf-8')
def get_xml(self):
return minidom.parseString(self.get_buff()).toprettyxml(
encoding="utf-8")
def get_xml_obj(self):
return minidom.parseString(self.get_buff())
def getPrefix(self, prefix):
if prefix is None or len(prefix) == 0:
return u''
return prefix + u':'
def getAttributeValue(self, index):
_type = self.axml.getAttributeValueType(index)
_data = self.axml.getAttributeValueData(index)
return format_value(_type, _data, lambda _: self.axml.getAttributeValue(index))
class SV(object):
def __init__(self, size, buff):
self.__size = size
self.__value = unpack(self.__size, buff)[0]
def _get(self):
return pack(self.__size, self.__value)
def __str__(self):
return "0x%x" % self.__value
def __int__(self):
return self.__value
def get_value_buff(self):
return self._get()
def get_value(self):
return self.__value
def set_value(self, attr):
self.__value = attr
class SVs(object):
def __init__(self, size, ntuple, buff):
self.__size = size
self.__value = ntuple._make(unpack(self.__size, buff))
def _get(self):
l = []
for i in self.__value._fields:
l.append(getattr(self.__value, i))
return pack(self.__size, *l)
def _export(self):
return [x for x in self.__value._fields]
def get_value_buff(self):
return self._get()
def get_value(self):
return self.__value
def set_value(self, attr):
self.__value = self.__value._replace(**attr)
def __str__(self):
return self.__value.__str__()
class BuffHandle(object):
def __init__(self, buff):
self.__buff = buff
self.__idx = 0
def size(self):
return len(self.__buff)
def set_idx(self, idx):
self.__idx = idx
def get_idx(self):
return self.__idx
def readNullString(self, size):
data = self.read(size)
return data
def read_b(self, size):
return self.__buff[self.__idx:self.__idx + size]
def read_at(self, offset, size):
return self.__buff[offset:offset + size]
def read(self, size):
if isinstance(size, SV):
size = size.value
buff = self.__buff[self.__idx:self.__idx + size]
self.__idx += size
return buff
def end(self):
return self.__idx == len(self.__buff)
class Buff(object):
def __init__(self, offset, buff):
self.offset = offset
self.buff = buff
self.size = len(buff)
def long2int(l):
if l > 0x7fffffff:
l = (0x7fffffff & l) - 0x80000000
return l

50
build.gradle Normal file
View File

@@ -0,0 +1,50 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.8.22'
println "\n\n\n"
println '当前选择版本 Version Name'+ version_name
println '当前选择版本 Version Code'+ Integer.valueOf(version_code)
println "\n\n\n"
repositories {
mavenCentral()
mavenLocal()
jcenter()
google()
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://jitpack.io' }
maven { url 'https://repo1.maven.org/maven2/' }
maven { url 'https://raw.githubusercontent.com/martinloren/AabResGuard/mvn-repo' }
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
// aRouter
classpath "com.alibaba:arouter-register:1.0.2"
//realm 数据库插件
classpath "io.realm:realm-gradle-plugin:10.16.1"
// android 资源混淆插件
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.tencent.vasdolly:plugin:3.0.6'
classpath "com.bytedance.android:aabresguard-plugin:0.1.10"
classpath "com.github.liujingxing:XmlClassGuard:1.2.6"
}
}
allprojects {
repositories {
mavenCentral()
google()
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://jitpack.io' }
maven { url 'https://repo1.maven.org/maven2/' }
}
//网络慢的话就去 https://maven.aliyun.com/mvn/view 里面找个代理的仓库。
}
task clean(type: Delete) {
delete rootProject.buildDir
}

1
channel.txt Normal file
View File

@@ -0,0 +1 @@
official

39
gradle.properties Normal file
View File

@@ -0,0 +1,39 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx4096M -Dkotlin.daemon.jvm.options\="-Xmx4096M" -XX\:MaxMetaspaceSize\=4096m
org.gradle.daemon=true
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.useAndroidX=true
android.enableJetifier=true
android.enableResourceOptimizations=false
android.injected.testOnly=false
# mob
MobSDK.spEdition=FP
minify_enabled=false
# 是否隔离模式换包名、换签名、隐藏部分SDK-KEY信息、去掉部分SDKGoogle相关
isolation_mode=true
channel_file=channel.txt
CRASHLYTICS_COLLECTION_ENABLED=false
COMPILE_SDK_VERSION=34
MIN_SDK_VERSION=21
TARGET_SDK_VERSION=34
version_name=1.0.26
version_code=42
#systemProp.https.proxyHost=127.0.0.1
#systemProp.https.proxyPort=7890

160
gradlew vendored Normal file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

8
local.properties Normal file
View File

@@ -0,0 +1,8 @@
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Mon Feb 26 14:07:21 CST 2024
sdk.dir=/Users/zhanglaiman/Library/Android/sdk

79
mode.json Normal file
View File

@@ -0,0 +1,79 @@
{
"code": 200,
"message": "success",
"data": {
"bigList": [],
"customChargeProd": [],
"defaultPay": 2,
"defaultPayH5": 2,
"list": [
{
"chargeProdId": "coin_charge_01",
"prodName": "7000 Coins",
"money": 1.0,
"giftGoldNum": 0,
"channel": 1,
"seqNo": 1,
"chargeGoldNum": 7000,
"currencySign": "USD",
"payChannel": "google_play_billing"
},
{
"chargeProdId": "coin_charge_02",
"prodName": "70000 Coins",
"money": 10.0,
"giftGoldNum": 0,
"channel": 1,
"seqNo": 2,
"chargeGoldNum": 70000,
"currencySign": "USD",
"payChannel": "google_play_billing"
},
{
"chargeProdId": "coin_charge_03",
"prodName": "140000 Coins",
"money": 20.0,
"giftGoldNum": 0,
"channel": 1,
"seqNo": 3,
"chargeGoldNum": 140000,
"currencySign": "USD",
"payChannel": "google_play_billing"
},
{
"chargeProdId": "coin_charge_04",
"prodName": "350000 Coins",
"money": 50.0,
"giftGoldNum": 0,
"channel": 1,
"seqNo": 4,
"chargeGoldNum": 350000,
"currencySign": "USD",
"payChannel": "google_play_billing"
},
{
"chargeProdId": "coin_charge_05",
"prodName": "560000 Coins",
"money": 80.0,
"giftGoldNum": 0,
"channel": 1,
"seqNo": 5,
"chargeGoldNum": 560000,
"currencySign": "USD",
"payChannel": "google_play_billing"
},
{
"chargeProdId": "coin_charge_06",
"prodName": "700000 Coins",
"money": 100.0,
"giftGoldNum": 0,
"channel": 1,
"seqNo": 6,
"chargeGoldNum": 700000,
"currencySign": "USD",
"payChannel": "google_play_billing"
}
]
},
"timestamp": 1751363110053
}

BIN
molistar.jks Normal file

Binary file not shown.

BIN
molistar_debug.jks Normal file

Binary file not shown.

17
project.gradle Normal file
View File

@@ -0,0 +1,17 @@
ext {
isolationMode = Boolean.valueOf(isolation_mode)
println("是否隔离模式:" + isolationMode)
package_name = project.hasProperty('package_name') ? package_name
: (isolationMode ? 'com.example.gogo.none' : 'com.example.live')
println("applicationId is " + package_name)
}
boolean isReleaseBuildType() {
for (String s : gradle.startParameter.taskNames) {
if (s.contains("Release") | s.contains("release")) {
return true
}
}
return false
}

52
readme.md Normal file
View File

@@ -0,0 +1,52 @@
# 初始化项目
命令行教学
```
git clone git@code.aliyun.com:qingxun/qx_android_client.git
```
# 拉取或推送
拉取
```
git subtree pull --prefix={dirName} {repoUrl} {branchName}
```
推送
```
git subtree push --prefix={dirName} {repoUrl} {branchName}
```
>{dirName} 为 core 、library 或者 nim_uikit
>{repoUrl} 为对应的子仓库地址
>{branchName} 为要拉取或者推送的分支名
## 指定渠道打包
```
java -jar packer-ng-2.0.0.jar generate --channels=yingyongbao --output=build/jiaku/apk --input=/Users/MadisonRong/dev/Android/erban-release/2.4.3/erban_client-release_aligned_signed-yingyongbao_243_jiagu_sign.apk
```
## python查看渠道信息
支持查看单个apk文件或者文件夹下的所有apk文件。
```
python packer-ng-v2.py xxxx.apk
```
##应用框架
- MVP(按照 <a href="https://blog.csdn.net/yulong0809/article/details/78622428">刘镓旗的博客</a> 的来封装)
- CoreEvent(yy的框架?有预料不到的bug)
- rxJava
##sdk的应用场景
- 声网(开闭麦),声网有关房间操作封装在RtcEngineManager
- 云信(朋友,粉丝,黑名单[房间,关系],队列,自定义消息,最近联系人,监听信息),云信有关房间的操作封装在IMNeteaseManager
##功能
- 礼物(GiftModel和GiftDialog包换获取数据和显示数据所有逻辑)
- 魔法(MagicModel,MagicDialog包换获取数据和显示数据所有逻辑)
- 座驾(商城,车库,CarActvity)
- 贵族(NobleUtil,NobleDataManager里面实现了贵族资源,权限的获取)
- 表情(FaceCoreImpl,下载和配置)
- 房间(房间设置,跳坑,上麦,下麦,锁坑,禁言,显示在MicroView和MicroViewAdapter,实现逻辑在AvRoomModel和AvRoomPresenter和ButtonItemFactory里面)
- 公屏(MessageView云信自定义消息的显示)
- 动画(帧动画[表情],属性动画[礼物],SVGA[座驾,房间背景,礼物特效,魔法])
- 统计(未完善,入口StatisticManager)
- 云信自定义信息(CustomAttachment和CustomAttachParser)

54
screenMatch.properties Normal file
View File

@@ -0,0 +1,54 @@
############################################################################
# Start with '#' is annotate. #
# In front of '=' is key, cannot be modified. #
# More information to visit: #
# http://blog.csdn.net/fesdgasdgasdg/article/details/52325590 #
# http://download.csdn.net/detail/fesdgasdgasdg/9913744 #
# https://github.com/mengzhinan/PhoneScreenMatch #
############################################################################
#
# You need to refresh or reopen the project every time you modify the configuration,
# or you can't get the latest configuration parameters.
#
#############################################################################
#
# Base dp value for screen match. Cut the screen into [base_dp] parts.
# Data type is double. System default value is 360.
# I advise you not to modify the value, be careful !!!!!!!!! _^_ *_*
base_dp=360
# Also need to match the phone screen of [match_dp].
# If you have another dp values.
# System default values is 240,320,384,392,400,410,411,480,533,592,600,640,662,720,768,800,811,820,960,961,1024,1280,1365
match_dp=
# If you not wanna to match dp values above. Write some above values here, append value with "," .
# For example: 811,961,1365
ignore_dp=
# They're not android module name. If has more<72><65>split with , Symbol.
# If you set, it will not show in SelectDialog.
# If you have, write here and append value with "," .
# For example: testLibrary,commonModule
# System default values is .gradle, gradle, .idea, build, .git
ignore_module_name=
# Use which module under the values/dimen.xml file to do the base file,
# and generated dimen.xml file store in this module?
# Default value is 'app'.
match_module=app
# Don't show select dialog again when use this plugin.
# System screen match will use the last selected module name or default module name.
# You can give value true or false. Default value is false.
not_show_dialog=false
# Do you want to generate the default example dimens.xml file?
# In path of .../projectName/screenMatch_example_dimens.xml, It does not affect your project code.
# You can give value true or false. Default value is false.
not_create_default_dimens=false
# Does the font scale the same size as the DP? May not be accuracy.
# You can give value true or false. Default value is true. Also need scaled.
is_match_font_sp=true
# Do you want to create values-wXXXdp folder or values-swXXXdp folder ?
# I suggest you create values-swXXXdp folder,
# because I had a problem when I was working on the horizontal screen adapter.
# values-swXXXdp folder can solve my problem.
# If you want create values-swXXXdp folder, set "create_values_sw_folder=true",
# otherwise set "create_values_sw_folder=true".
# Default values is true.
create_values_sw_folder=true

View File

@@ -0,0 +1,431 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<!-- Your custom size defind by references, can be writted in anywhere, any module, any values/*.xml, for example: -->
<dimen name="card_common_margin_left">@dimen/dp_15</dimen>
<!-- dp and sp values, must be defind in this file! -->
<!-- view size,you can add if there is no one -->
<dimen name="dp_m_60">-60dp</dimen>
<dimen name="dp_m_30">-30dp</dimen>
<dimen name="dp_m_20">-20dp</dimen>
<dimen name="dp_m_12">-12dp</dimen>
<dimen name="dp_m_10">-10dp</dimen>
<dimen name="dp_m_8">-8dp</dimen>
<dimen name="dp_m_5">-5dp</dimen>
<dimen name="dp_m_2">-2dp</dimen>
<dimen name="dp_m_1">-1dp</dimen>
<dimen name="dp_0">0dp</dimen>
<dimen name="dp_0_1">0.1dp</dimen>
<dimen name="dp_0_5">0.5dp</dimen>
<dimen name="dp_1">1dp</dimen>
<dimen name="dp_1_5">1.5dp</dimen>
<dimen name="dp_2">2dp</dimen>
<dimen name="dp_2_5">2.5dp</dimen>
<dimen name="dp_3">3dp</dimen>
<dimen name="dp_3_5">3.5dp</dimen>
<dimen name="dp_4">4dp</dimen>
<dimen name="dp_4_5">4.5dp</dimen>
<dimen name="dp_5">5dp</dimen>
<dimen name="dp_6">6dp</dimen>
<dimen name="dp_7">7dp</dimen>
<dimen name="dp_7_5">7.5dp</dimen>
<dimen name="dp_8">8dp</dimen>
<dimen name="dp_9">9dp</dimen>
<dimen name="dp_10">10dp</dimen>
<dimen name="dp_11">11dp</dimen>
<dimen name="dp_12">12dp</dimen>
<dimen name="dp_13">13dp</dimen>
<dimen name="dp_14">14dp</dimen>
<dimen name="dp_15">15dp</dimen>
<dimen name="dp_16">16dp</dimen>
<dimen name="dp_17">17dp</dimen>
<dimen name="dp_18">18dp</dimen>
<dimen name="dp_19">19dp</dimen>
<dimen name="dp_20">20dp</dimen>
<dimen name="dp_21">21dp</dimen>
<dimen name="dp_22">22dp</dimen>
<dimen name="dp_23">23dp</dimen>
<dimen name="dp_24">24dp</dimen>
<dimen name="dp_25">25dp</dimen>
<dimen name="dp_26">26dp</dimen>
<dimen name="dp_27">27dp</dimen>
<dimen name="dp_28">28dp</dimen>
<dimen name="dp_29">29dp</dimen>
<dimen name="dp_30">30dp</dimen>
<dimen name="dp_31">31dp</dimen>
<dimen name="dp_32">32dp</dimen>
<dimen name="dp_33">33dp</dimen>
<dimen name="dp_34">34dp</dimen>
<dimen name="dp_35">35dp</dimen>
<dimen name="dp_36">36dp</dimen>
<dimen name="dp_37">37dp</dimen>
<dimen name="dp_38">38dp</dimen>
<dimen name="dp_39">39dp</dimen>
<dimen name="dp_40">40dp</dimen>
<dimen name="dp_41">41dp</dimen>
<dimen name="dp_42">42dp</dimen>
<dimen name="dp_43">43dp</dimen>
<dimen name="dp_44">44dp</dimen>
<dimen name="dp_45">45dp</dimen>
<dimen name="dp_46">46dp</dimen>
<dimen name="dp_47">47dp</dimen>
<dimen name="dp_48">48dp</dimen>
<dimen name="dp_49">49dp</dimen>
<dimen name="dp_50">50dp</dimen>
<dimen name="dp_51">51dp</dimen>
<dimen name="dp_52">52dp</dimen>
<dimen name="dp_53">53dp</dimen>
<dimen name="dp_54">54dp</dimen>
<dimen name="dp_55">55dp</dimen>
<dimen name="dp_56">56dp</dimen>
<dimen name="dp_57">57dp</dimen>
<dimen name="dp_58">58dp</dimen>
<dimen name="dp_59">59dp</dimen>
<dimen name="dp_60">60dp</dimen>
<dimen name="dp_61">61dp</dimen>
<dimen name="dp_62">62dp</dimen>
<dimen name="dp_63">63dp</dimen>
<dimen name="dp_64">64dp</dimen>
<dimen name="dp_65">65dp</dimen>
<dimen name="dp_66">66dp</dimen>
<dimen name="dp_67">67dp</dimen>
<dimen name="dp_68">68dp</dimen>
<dimen name="dp_69">69dp</dimen>
<dimen name="dp_70">70dp</dimen>
<dimen name="dp_71">71dp</dimen>
<dimen name="dp_72">72dp</dimen>
<dimen name="dp_73">73dp</dimen>
<dimen name="dp_74">74dp</dimen>
<dimen name="dp_75">75dp</dimen>
<dimen name="dp_76">76dp</dimen>
<dimen name="dp_77">77dp</dimen>
<dimen name="dp_78">78dp</dimen>
<dimen name="dp_79">79dp</dimen>
<dimen name="dp_80">80dp</dimen>
<dimen name="dp_81">81dp</dimen>
<dimen name="dp_82">82dp</dimen>
<dimen name="dp_83">83dp</dimen>
<dimen name="dp_84">84dp</dimen>
<dimen name="dp_85">85dp</dimen>
<dimen name="dp_86">86dp</dimen>
<dimen name="dp_87">87dp</dimen>
<dimen name="dp_88">88dp</dimen>
<dimen name="dp_89">89dp</dimen>
<dimen name="dp_90">90dp</dimen>
<dimen name="dp_91">91dp</dimen>
<dimen name="dp_92">92dp</dimen>
<dimen name="dp_93">93dp</dimen>
<dimen name="dp_94">94dp</dimen>
<dimen name="dp_95">95dp</dimen>
<dimen name="dp_96">96dp</dimen>
<dimen name="dp_97">97dp</dimen>
<dimen name="dp_98">98dp</dimen>
<dimen name="dp_99">99dp</dimen>
<dimen name="dp_100">100dp</dimen>
<dimen name="dp_101">101dp</dimen>
<dimen name="dp_102">102dp</dimen>
<dimen name="dp_103">103dp</dimen>
<dimen name="dp_104">104dp</dimen>
<dimen name="dp_104_5">104.5dp</dimen>
<dimen name="dp_105">105dp</dimen>
<dimen name="dp_106">106dp</dimen>
<dimen name="dp_107">107dp</dimen>
<dimen name="dp_108">108dp</dimen>
<dimen name="dp_109">109dp</dimen>
<dimen name="dp_110">110dp</dimen>
<dimen name="dp_111">111dp</dimen>
<dimen name="dp_112">112dp</dimen>
<dimen name="dp_113">113dp</dimen>
<dimen name="dp_114">114dp</dimen>
<dimen name="dp_115">115dp</dimen>
<dimen name="dp_116">116dp</dimen>
<dimen name="dp_117">117dp</dimen>
<dimen name="dp_118">118dp</dimen>
<dimen name="dp_119">119dp</dimen>
<dimen name="dp_120">120dp</dimen>
<dimen name="dp_121">121dp</dimen>
<dimen name="dp_122">122dp</dimen>
<dimen name="dp_123">123dp</dimen>
<dimen name="dp_124">124dp</dimen>
<dimen name="dp_125">125dp</dimen>
<dimen name="dp_126">126dp</dimen>
<dimen name="dp_127">127dp</dimen>
<dimen name="dp_128">128dp</dimen>
<dimen name="dp_129">129dp</dimen>
<dimen name="dp_130">130dp</dimen>
<dimen name="dp_131">131dp</dimen>
<dimen name="dp_132">132dp</dimen>
<dimen name="dp_133">133dp</dimen>
<dimen name="dp_134">134dp</dimen>
<dimen name="dp_134_5">134.5dp</dimen>
<dimen name="dp_135">135dp</dimen>
<dimen name="dp_136">136dp</dimen>
<dimen name="dp_137">137dp</dimen>
<dimen name="dp_138">138dp</dimen>
<dimen name="dp_139">139dp</dimen>
<dimen name="dp_140">140dp</dimen>
<dimen name="dp_141">141dp</dimen>
<dimen name="dp_142">142dp</dimen>
<dimen name="dp_143">143dp</dimen>
<dimen name="dp_144">144dp</dimen>
<dimen name="dp_145">145dp</dimen>
<dimen name="dp_146">146dp</dimen>
<dimen name="dp_147">147dp</dimen>
<dimen name="dp_148">148dp</dimen>
<dimen name="dp_149">149dp</dimen>
<dimen name="dp_150">150dp</dimen>
<dimen name="dp_151">151dp</dimen>
<dimen name="dp_152">152dp</dimen>
<dimen name="dp_153">153dp</dimen>
<dimen name="dp_154">154dp</dimen>
<dimen name="dp_155">155dp</dimen>
<dimen name="dp_156">156dp</dimen>
<dimen name="dp_157">157dp</dimen>
<dimen name="dp_158">158dp</dimen>
<dimen name="dp_159">159dp</dimen>
<dimen name="dp_160">160dp</dimen>
<dimen name="dp_161">161dp</dimen>
<dimen name="dp_162">162dp</dimen>
<dimen name="dp_163">163dp</dimen>
<dimen name="dp_164">164dp</dimen>
<dimen name="dp_165">165dp</dimen>
<dimen name="dp_166">166dp</dimen>
<dimen name="dp_167">167dp</dimen>
<dimen name="dp_168">168dp</dimen>
<dimen name="dp_169">169dp</dimen>
<dimen name="dp_170">170dp</dimen>
<dimen name="dp_171">171dp</dimen>
<dimen name="dp_172">172dp</dimen>
<dimen name="dp_173">173dp</dimen>
<dimen name="dp_174">174dp</dimen>
<dimen name="dp_175">175dp</dimen>
<dimen name="dp_176">176dp</dimen>
<dimen name="dp_177">177dp</dimen>
<dimen name="dp_178">178dp</dimen>
<dimen name="dp_179">179dp</dimen>
<dimen name="dp_180">180dp</dimen>
<dimen name="dp_181">181dp</dimen>
<dimen name="dp_182">182dp</dimen>
<dimen name="dp_183">183dp</dimen>
<dimen name="dp_184">184dp</dimen>
<dimen name="dp_185">185dp</dimen>
<dimen name="dp_186">186dp</dimen>
<dimen name="dp_187">187dp</dimen>
<dimen name="dp_188">188dp</dimen>
<dimen name="dp_189">189dp</dimen>
<dimen name="dp_190">190dp</dimen>
<dimen name="dp_191">191dp</dimen>
<dimen name="dp_191_25">191.25dp</dimen>
<dimen name="dp_192">192dp</dimen>
<dimen name="dp_193">193dp</dimen>
<dimen name="dp_194">194dp</dimen>
<dimen name="dp_195">195dp</dimen>
<dimen name="dp_196">196dp</dimen>
<dimen name="dp_197">197dp</dimen>
<dimen name="dp_198">198dp</dimen>
<dimen name="dp_199">199dp</dimen>
<dimen name="dp_200">200dp</dimen>
<dimen name="dp_201">201dp</dimen>
<dimen name="dp_202">202dp</dimen>
<dimen name="dp_203">203dp</dimen>
<dimen name="dp_204">204dp</dimen>
<dimen name="dp_205">205dp</dimen>
<dimen name="dp_206">206dp</dimen>
<dimen name="dp_207">207dp</dimen>
<dimen name="dp_208">208dp</dimen>
<dimen name="dp_209">209dp</dimen>
<dimen name="dp_210">210dp</dimen>
<dimen name="dp_211">211dp</dimen>
<dimen name="dp_212">212dp</dimen>
<dimen name="dp_213">213dp</dimen>
<dimen name="dp_214">214dp</dimen>
<dimen name="dp_215">215dp</dimen>
<dimen name="dp_216">216dp</dimen>
<dimen name="dp_217">217dp</dimen>
<dimen name="dp_218">218dp</dimen>
<dimen name="dp_219">219dp</dimen>
<dimen name="dp_220">220dp</dimen>
<dimen name="dp_221">221dp</dimen>
<dimen name="dp_222">222dp</dimen>
<dimen name="dp_223">223dp</dimen>
<dimen name="dp_224">224dp</dimen>
<dimen name="dp_225">225dp</dimen>
<dimen name="dp_226">226dp</dimen>
<dimen name="dp_227">227dp</dimen>
<dimen name="dp_228">228dp</dimen>
<dimen name="dp_229">229dp</dimen>
<dimen name="dp_230">230dp</dimen>
<dimen name="dp_231">231dp</dimen>
<dimen name="dp_232">232dp</dimen>
<dimen name="dp_233">233dp</dimen>
<dimen name="dp_234">234dp</dimen>
<dimen name="dp_235">235dp</dimen>
<dimen name="dp_236">236dp</dimen>
<dimen name="dp_237">237dp</dimen>
<dimen name="dp_238">238dp</dimen>
<dimen name="dp_239">239dp</dimen>
<dimen name="dp_240">240dp</dimen>
<dimen name="dp_241">241dp</dimen>
<dimen name="dp_242">242dp</dimen>
<dimen name="dp_243">243dp</dimen>
<dimen name="dp_244">244dp</dimen>
<dimen name="dp_245">245dp</dimen>
<dimen name="dp_246">246dp</dimen>
<dimen name="dp_247">247dp</dimen>
<dimen name="dp_248">248dp</dimen>
<dimen name="dp_249">249dp</dimen>
<dimen name="dp_250">250dp</dimen>
<dimen name="dp_251">251dp</dimen>
<dimen name="dp_252">252dp</dimen>
<dimen name="dp_253">253dp</dimen>
<dimen name="dp_254">254dp</dimen>
<dimen name="dp_255">255dp</dimen>
<dimen name="dp_256">256dp</dimen>
<dimen name="dp_257">257dp</dimen>
<dimen name="dp_258">258dp</dimen>
<dimen name="dp_259">259dp</dimen>
<dimen name="dp_260">260dp</dimen>
<dimen name="dp_261">261dp</dimen>
<dimen name="dp_262">262dp</dimen>
<dimen name="dp_263">263dp</dimen>
<dimen name="dp_264">264dp</dimen>
<dimen name="dp_265">265dp</dimen>
<dimen name="dp_266">266dp</dimen>
<dimen name="dp_267">267dp</dimen>
<dimen name="dp_268">268dp</dimen>
<dimen name="dp_269">269dp</dimen>
<dimen name="dp_270">270dp</dimen>
<dimen name="dp_271">271dp</dimen>
<dimen name="dp_272">272dp</dimen>
<dimen name="dp_273">273dp</dimen>
<dimen name="dp_274">274dp</dimen>
<dimen name="dp_275">275dp</dimen>
<dimen name="dp_276">276dp</dimen>
<dimen name="dp_277">277dp</dimen>
<dimen name="dp_278">278dp</dimen>
<dimen name="dp_279">279dp</dimen>
<dimen name="dp_280">280dp</dimen>
<dimen name="dp_281">281dp</dimen>
<dimen name="dp_282">282dp</dimen>
<dimen name="dp_283">283dp</dimen>
<dimen name="dp_284">284dp</dimen>
<dimen name="dp_285">285dp</dimen>
<dimen name="dp_286">286dp</dimen>
<dimen name="dp_287">287dp</dimen>
<dimen name="dp_288">288dp</dimen>
<dimen name="dp_289">289dp</dimen>
<dimen name="dp_290">290dp</dimen>
<dimen name="dp_291">291dp</dimen>
<dimen name="dp_292">292dp</dimen>
<dimen name="dp_293">293dp</dimen>
<dimen name="dp_294">294dp</dimen>
<dimen name="dp_295">295dp</dimen>
<dimen name="dp_296">296dp</dimen>
<dimen name="dp_297">297dp</dimen>
<dimen name="dp_298">298dp</dimen>
<dimen name="dp_299">299dp</dimen>
<dimen name="dp_300">300dp</dimen>
<dimen name="dp_301">301dp</dimen>
<dimen name="dp_302">302dp</dimen>
<dimen name="dp_303">303dp</dimen>
<dimen name="dp_304">304dp</dimen>
<dimen name="dp_305">305dp</dimen>
<dimen name="dp_306">306dp</dimen>
<dimen name="dp_307">307dp</dimen>
<dimen name="dp_308">308dp</dimen>
<dimen name="dp_309">309dp</dimen>
<dimen name="dp_310">310dp</dimen>
<dimen name="dp_311">311dp</dimen>
<dimen name="dp_312">312dp</dimen>
<dimen name="dp_313">313dp</dimen>
<dimen name="dp_314">314dp</dimen>
<dimen name="dp_315">315dp</dimen>
<dimen name="dp_316">316dp</dimen>
<dimen name="dp_317">317dp</dimen>
<dimen name="dp_318">318dp</dimen>
<dimen name="dp_319">319dp</dimen>
<dimen name="dp_320">320dp</dimen>
<dimen name="dp_321">321dp</dimen>
<dimen name="dp_322">322dp</dimen>
<dimen name="dp_323">323dp</dimen>
<dimen name="dp_324">324dp</dimen>
<dimen name="dp_325">325dp</dimen>
<dimen name="dp_326">326dp</dimen>
<dimen name="dp_327">327dp</dimen>
<dimen name="dp_328">328dp</dimen>
<dimen name="dp_329">329dp</dimen>
<dimen name="dp_330">330dp</dimen>
<dimen name="dp_331">331dp</dimen>
<dimen name="dp_332">332dp</dimen>
<dimen name="dp_333">333dp</dimen>
<dimen name="dp_334">334dp</dimen>
<dimen name="dp_335">335dp</dimen>
<dimen name="dp_336">336dp</dimen>
<dimen name="dp_337">337dp</dimen>
<dimen name="dp_338">338dp</dimen>
<dimen name="dp_339">339dp</dimen>
<dimen name="dp_340">340dp</dimen>
<dimen name="dp_341">341dp</dimen>
<dimen name="dp_342">342dp</dimen>
<dimen name="dp_343">343dp</dimen>
<dimen name="dp_344">344dp</dimen>
<dimen name="dp_345">345dp</dimen>
<dimen name="dp_346">346dp</dimen>
<dimen name="dp_347">347dp</dimen>
<dimen name="dp_348">348dp</dimen>
<dimen name="dp_349">349dp</dimen>
<dimen name="dp_350">350dp</dimen>
<dimen name="dp_351">351dp</dimen>
<dimen name="dp_352">352dp</dimen>
<dimen name="dp_353">353dp</dimen>
<dimen name="dp_354">354dp</dimen>
<dimen name="dp_355">355dp</dimen>
<dimen name="dp_356">356dp</dimen>
<dimen name="dp_357">357dp</dimen>
<dimen name="dp_358">358dp</dimen>
<dimen name="dp_359">359dp</dimen>
<dimen name="dp_360">360dp</dimen>
<dimen name="dp_365">365dp</dimen>
<dimen name="dp_370">370dp</dimen>
<dimen name="dp_400">400dp</dimen>
<dimen name="dp_410">410dp</dimen>
<dimen name="dp_422">422dp</dimen>
<dimen name="dp_472">472dp</dimen>
<dimen name="dp_500">500dp</dimen>
<dimen name="dp_600">600dp</dimen>
<dimen name="dp_640">640dp</dimen>
<dimen name="dp_720">720dp</dimen>
<!-- font size,you can add if there is no one -->
<dimen name="sp_6">6sp</dimen>
<dimen name="sp_7">7sp</dimen>
<dimen name="sp_8">8sp</dimen>
<dimen name="sp_9">9sp</dimen>
<dimen name="sp_10">10sp</dimen>
<dimen name="sp_11">11sp</dimen>
<dimen name="sp_12">12sp</dimen>
<dimen name="sp_13">13sp</dimen>
<dimen name="sp_14">14sp</dimen>
<dimen name="sp_15">15sp</dimen>
<dimen name="sp_16">16sp</dimen>
<dimen name="sp_17">17sp</dimen>
<dimen name="sp_18">18sp</dimen>
<dimen name="sp_19">19sp</dimen>
<dimen name="sp_20">20sp</dimen>
<dimen name="sp_21">21sp</dimen>
<dimen name="sp_22">22sp</dimen>
<dimen name="sp_23">23sp</dimen>
<dimen name="sp_24">24sp</dimen>
<dimen name="sp_25">25sp</dimen>
<dimen name="sp_28">28sp</dimen>
<dimen name="sp_30">30sp</dimen>
<dimen name="sp_32">32sp</dimen>
<dimen name="sp_34">34sp</dimen>
<dimen name="sp_36">36sp</dimen>
<dimen name="sp_38">38sp</dimen>
<dimen name="sp_40">40sp</dimen>
<dimen name="sp_42">42sp</dimen>
<dimen name="sp_48">48sp</dimen>
</resources>

12
settings.gradle Normal file
View File

@@ -0,0 +1,12 @@
include ':app'
include ':core'
include ':library'
include ':nim_uikit'
include ':easyphotos'
if (file("./modules/module_google/build.gradle").exists()) {
include ':modules:module_google'
}
include ':modules:module_base'
include ':libs:lib_core'
include ':libs:lib_utils'
include ':libs:lib_encipher'

108
方案.txt Normal file
View File

@@ -0,0 +1,108 @@
# 项目重构技术方案
## 一、目标
- **语言迁移**:将原有 Java 代码逐步迁移到 Kotlin。
- **架构升级**:采用 MVVMModel-View-ViewModel架构提高可维护性和可测试性。
- **现代化异步处理**:使用 Kotlin 协程和 Flow 替代 RxJava简化异步编程模型。
- **事件总线更换**:使用 LiveDataBus , 替换现EventBus , LiveDataBus具备生命周期管理,不再需要单独注册回收,可跨进程通讯,可发粘性事件
- **RecyclerView Adapter 优化** 引入 DiffUtil, 提升全局刷新的效率,避免不必要的消耗
- **更换屏幕适配方案** 使用头条的适配方案 AndroidAutoSize 替换现屏幕适配方案
- **模块化设计**:按功能模块划分代码结构,增强模块解耦和复用能力。
- **模块路由TheRouter**:货拉拉提供的模块路由方案,替换掉已经不再更新维护的ARouter
- **Glide**:升级版本,尝试兼容 GZip ,提升图片加载效率
- **混淆方案**ProGuard + aabResGuard + xmlClassGuard+包名隔离
---
## 二、技术选型
### 1. 编程语言
- **Kotlin**:全面使用 Kotlin 替代 Java利用其简洁语法、空安全等特性。
### 2. 架构模式
- **MVVMModel-View-ViewModel**
- **Model**:数据层,包含网络请求、本地存储、业务逻辑等。
- **View**UI 层,由 Activity 和 Fragment 实现。
- **ViewModel**:连接 Model 和 View 的桥梁,持有 UI 数据并提供生命周期感知的数据绑定。
### 3. 异步处理
- **Kotlin Coroutines + Flow**:替代 RxJava简化异步操作和响应式编程。
- **LiveData/StateFlow**:用于在 ViewModel 和 View 之间传递 UI 数据变化。
### 4. RecyclerView
- **融云Provider方案/BaseRecyclerViewAdapterHelper**:选一个替代 原有adapter
- **DiffUtil**:工具会自动计算,差异化对比后只刷新对应的item,大幅度提升效率
### 5. 网络请求
- **Retrofit + OkHttp**:保留现有 Retrofit 网络框架,结合 Kotlin 协程进行优化。
- **协程封装**:统一封装网络请求,简化调用方式。
### 6. 数据库与本地缓存
- **Room**:本地持久化数据使用 Room 数据库。
- **CacheManager**:统一的缓存策略封装。//考虑
### 7. 事件总线
- **LiveDataBus** :替代 EventBus/RxBus实现跨组件跨进程通信,事件生命周期管理,粘性事件收发。
### 8. 混淆
- **ProGuard** :基础混淆,自定义规则在 proguard-rules.pro 中。
- **aabResGuard** :资源文件名混淆保护,白名单机制保留关键资源,支持多语言保护
- **xmlClassGuard** :布局文件类名混淆,目录结构混淆,生成映射文件
- **包名隔离** :测试包跟正式包采用不同 包名
### 9. 工具库
- **Glide**图片加载。尝试兼容GZip,因 Glide还充当下载 svga,mp4,如果GZip不能兼容这两种情况,那就只好放弃
- **TheRouter**:货拉拉团队开源的路由框架,使用ktps效率,编译效率远高于ARouter
- **uCrop**:裁剪图片框架,打算集成源码进项目,好应对各种需求
- **LeakCanary**:侦测内存泄漏
- **AndroidAutoSize**:头条适配方案
- **XCrash**:拦截 java层崩溃,so层崩溃,ANR,OOM 等,输出日志到本地,方便开发调试
- **饺子播放器/ExoPlayer**优先ExoPlayer,谷歌推荐,且不加入FFmpeg扩展的情况下1MB左右
....
---
## 三、预计模块划分
| 模块名 | 功能说明 |
|--------|----------|
| [core] | 基础库,如网络、数据库、全局工具类等 |
| [common] | 公共库 |
| `app` | app层 |
| `login` | 登录注册相关逻辑 |
| `home` | 首页相关逻辑 |
| `dynamic` | 社区模块 |
| `game` | 游戏模块 |
| `message` | 云信模块|
| `mine` | 用户模块 |
| `room` | 房间模块 |
| `pay` | 支付相关模块 |
| `google` | 谷歌代码模块 |
| `develop` | 开发模块,存放一些不上线的代码,正式包不引入该模块 |(非必要)
| `library` | 工具库模块 |
---
---
## 四、迁移计划
| 阶段 | 内容 |
|------|------|----------|
| Phase 0 | 必要库,第三方引入,gradle升级,混淆方案出包,等兼容测试(优先保障混淆插件能运行,其他可做取舍,不断试错找到最优解)
| Phase 1 | 基础设施搭建Kotlin 支持,协程封装,模块划分,启动页搭建,路由搭建...
| Phase 2 | 用户登录注册、社区模块,用户模块
| Phase 3 | 云信模块
| Phase 4 | 游戏模块
| Phase 5 | 房间模块
| Phase 6 | 支付模块
| Phase 7 | 后续工作...
....
---
## 注意事项
1. **保持兼容性**:新旧代码共存期间需做好桥接,避免破坏已有流程。
2. **代码质量保障**
3. **文档同步更新**:重构过程中同步更新文档。
---