commit 69ff71e027d0fd454b86404336b908058f0b9528 Author: eggmanQQQ2 <3671373519@qq.com> Date: Mon Jul 7 10:26:00 2025 +0800 feat : first diff --git a/DeleteEmptyFolders.java b/DeleteEmptyFolders.java new file mode 100644 index 0000000..bae34d4 --- /dev/null +++ b/DeleteEmptyFolders.java @@ -0,0 +1,64 @@ + +import java.io.File; +import java.util.HashSet; +import java.util.Set; + +public class DeleteEmptyFolders { + + // 白名单路径集合 + private static final Set 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(); + } + } +} \ No newline at end of file diff --git a/apkinfo.py b/apkinfo.py new file mode 100644 index 0000000..f99f722 --- /dev/null +++ b/apkinfo.py @@ -0,0 +1,998 @@ +# This file is part of Androguard. +# +# Copyright (C) 2012, Anthony Desnos +# 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('> 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(' 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('> 16) - 1 + attributeCount = attributeCount & 0xFFFF + self.m_classAttribute = unpack('> 16) - 1 + + self.m_classAttribute = (self.m_classAttribute & 0xFFFF) - 1 + + for i in range(0, attributeCount * ATTRIBUTE_LENGHT): + self.m_attributes.append(unpack('> 24 + + self.m_event = START_TAG + break + + if chunkType == CHUNK_XML_END_TAG: + self.m_namespaceUri = unpack('= 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: ""): + 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'\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 += "\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("&", "&") + s = s.replace('"', """) + s = s.replace("'", "'") + s = s.replace("<", "<") + s = s.replace(">", ">") + 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 diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..e7cb245 --- /dev/null +++ b/build.gradle @@ -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 +} diff --git a/channel.txt b/channel.txt new file mode 100644 index 0000000..1d5e7c6 --- /dev/null +++ b/channel.txt @@ -0,0 +1 @@ +official \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..8137862 --- /dev/null +++ b/gradle.properties @@ -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信息、去掉部分SDK(Google相关) +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 diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..9d82f78 --- /dev/null +++ b/gradlew @@ -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 "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -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 diff --git a/local.properties b/local.properties new file mode 100644 index 0000000..2e57e88 --- /dev/null +++ b/local.properties @@ -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 diff --git a/mode.json b/mode.json new file mode 100644 index 0000000..84aaa19 --- /dev/null +++ b/mode.json @@ -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 +} \ No newline at end of file diff --git a/molistar.jks b/molistar.jks new file mode 100644 index 0000000..9fde063 Binary files /dev/null and b/molistar.jks differ diff --git a/molistar_debug.jks b/molistar_debug.jks new file mode 100644 index 0000000..db91987 Binary files /dev/null and b/molistar_debug.jks differ diff --git a/project.gradle b/project.gradle new file mode 100644 index 0000000..6255762 --- /dev/null +++ b/project.gradle @@ -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 +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..abade91 --- /dev/null +++ b/readme.md @@ -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(按照 刘镓旗的博客 的来封装) +- 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) \ No newline at end of file diff --git a/screenMatch.properties b/screenMatch.properties new file mode 100644 index 0000000..16c3eb4 --- /dev/null +++ b/screenMatch.properties @@ -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��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 diff --git a/screenMatch_example_dimens.xml b/screenMatch_example_dimens.xml new file mode 100644 index 0000000..026f490 --- /dev/null +++ b/screenMatch_example_dimens.xml @@ -0,0 +1,431 @@ + + + + + @dimen/dp_15 + + + + -60dp + -30dp + -20dp + -12dp + -10dp + -8dp + -5dp + -2dp + -1dp + 0dp + 0.1dp + 0.5dp + 1dp + 1.5dp + 2dp + 2.5dp + 3dp + 3.5dp + 4dp + 4.5dp + 5dp + 6dp + 7dp + 7.5dp + 8dp + 9dp + 10dp + 11dp + 12dp + 13dp + 14dp + 15dp + 16dp + 17dp + 18dp + 19dp + 20dp + 21dp + 22dp + 23dp + 24dp + 25dp + 26dp + 27dp + 28dp + 29dp + 30dp + 31dp + 32dp + 33dp + 34dp + 35dp + 36dp + 37dp + 38dp + 39dp + 40dp + 41dp + 42dp + 43dp + 44dp + 45dp + 46dp + 47dp + 48dp + 49dp + 50dp + 51dp + 52dp + 53dp + 54dp + 55dp + 56dp + 57dp + 58dp + 59dp + 60dp + 61dp + 62dp + 63dp + 64dp + 65dp + 66dp + 67dp + 68dp + 69dp + 70dp + 71dp + 72dp + 73dp + 74dp + 75dp + 76dp + 77dp + 78dp + 79dp + 80dp + 81dp + 82dp + 83dp + 84dp + 85dp + 86dp + 87dp + 88dp + 89dp + 90dp + 91dp + 92dp + 93dp + 94dp + 95dp + 96dp + 97dp + 98dp + 99dp + 100dp + 101dp + 102dp + 103dp + 104dp + 104.5dp + 105dp + 106dp + 107dp + 108dp + 109dp + 110dp + 111dp + 112dp + 113dp + 114dp + 115dp + 116dp + 117dp + 118dp + 119dp + 120dp + 121dp + 122dp + 123dp + 124dp + 125dp + 126dp + 127dp + 128dp + 129dp + 130dp + 131dp + 132dp + 133dp + 134dp + 134.5dp + 135dp + 136dp + 137dp + 138dp + 139dp + 140dp + 141dp + 142dp + 143dp + 144dp + 145dp + 146dp + 147dp + 148dp + 149dp + 150dp + 151dp + 152dp + 153dp + 154dp + 155dp + 156dp + 157dp + 158dp + 159dp + 160dp + 161dp + 162dp + 163dp + 164dp + 165dp + 166dp + 167dp + 168dp + 169dp + 170dp + 171dp + 172dp + 173dp + 174dp + 175dp + 176dp + 177dp + 178dp + 179dp + 180dp + 181dp + 182dp + 183dp + 184dp + 185dp + 186dp + 187dp + 188dp + 189dp + 190dp + 191dp + 191.25dp + 192dp + 193dp + 194dp + 195dp + 196dp + 197dp + 198dp + 199dp + 200dp + 201dp + 202dp + 203dp + 204dp + 205dp + 206dp + 207dp + 208dp + 209dp + 210dp + 211dp + 212dp + 213dp + 214dp + 215dp + 216dp + 217dp + 218dp + 219dp + 220dp + 221dp + 222dp + 223dp + 224dp + 225dp + 226dp + 227dp + 228dp + 229dp + 230dp + 231dp + 232dp + 233dp + 234dp + 235dp + 236dp + 237dp + 238dp + 239dp + 240dp + 241dp + 242dp + 243dp + 244dp + 245dp + 246dp + 247dp + 248dp + 249dp + 250dp + 251dp + 252dp + 253dp + 254dp + 255dp + 256dp + 257dp + 258dp + 259dp + 260dp + 261dp + 262dp + 263dp + 264dp + 265dp + 266dp + 267dp + 268dp + 269dp + 270dp + 271dp + 272dp + 273dp + 274dp + 275dp + 276dp + 277dp + 278dp + 279dp + 280dp + 281dp + 282dp + 283dp + 284dp + 285dp + 286dp + 287dp + 288dp + 289dp + 290dp + 291dp + 292dp + 293dp + 294dp + 295dp + 296dp + 297dp + 298dp + 299dp + 300dp + 301dp + 302dp + 303dp + 304dp + 305dp + 306dp + 307dp + 308dp + 309dp + 310dp + 311dp + 312dp + 313dp + 314dp + 315dp + 316dp + 317dp + 318dp + 319dp + 320dp + 321dp + 322dp + 323dp + 324dp + 325dp + 326dp + 327dp + 328dp + 329dp + 330dp + 331dp + 332dp + 333dp + 334dp + 335dp + 336dp + 337dp + 338dp + 339dp + 340dp + 341dp + 342dp + 343dp + 344dp + 345dp + 346dp + 347dp + 348dp + 349dp + 350dp + 351dp + 352dp + 353dp + 354dp + 355dp + 356dp + 357dp + 358dp + 359dp + 360dp + 365dp + 370dp + 400dp + 410dp + 422dp + 472dp + 500dp + 600dp + 640dp + 720dp + + + 6sp + 7sp + 8sp + 9sp + 10sp + 11sp + 12sp + 13sp + 14sp + 15sp + 16sp + 17sp + 18sp + 19sp + 20sp + 21sp + 22sp + 23sp + 24sp + 25sp + 28sp + 30sp + 32sp + 34sp + 36sp + 38sp + 40sp + 42sp + 48sp + + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..4164643 --- /dev/null +++ b/settings.gradle @@ -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' diff --git a/方案.txt b/方案.txt new file mode 100644 index 0000000..327524d --- /dev/null +++ b/方案.txt @@ -0,0 +1,108 @@ +# 项目重构技术方案 + +## 一、目标 +- **语言迁移**:将原有 Java 代码逐步迁移到 Kotlin。 +- **架构升级**:采用 MVVM(Model-View-ViewModel)架构,提高可维护性和可测试性。 +- **现代化异步处理**:使用 Kotlin 协程和 Flow 替代 RxJava,简化异步编程模型。 +- **事件总线更换**:使用 LiveDataBus , 替换现EventBus , LiveDataBus具备生命周期管理,不再需要单独注册回收,可跨进程通讯,可发粘性事件 +- **RecyclerView Adapter 优化**: 引入 DiffUtil, 提升全局刷新的效率,避免不必要的消耗 +- **更换屏幕适配方案**: 使用头条的适配方案 AndroidAutoSize 替换现屏幕适配方案 +- **模块化设计**:按功能模块划分代码结构,增强模块解耦和复用能力。 +- **模块路由TheRouter**:货拉拉提供的模块路由方案,替换掉已经不再更新维护的ARouter +- **Glide**:升级版本,尝试兼容 GZip ,提升图片加载效率 +- **混淆方案**:ProGuard + aabResGuard + xmlClassGuard+包名隔离 +--- + +## 二、技术选型 + +### 1. 编程语言 +- **Kotlin**:全面使用 Kotlin 替代 Java,利用其简洁语法、空安全等特性。 + +### 2. 架构模式 +- **MVVM(Model-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. **文档同步更新**:重构过程中同步更新文档。 + +---