精华内容
下载资源
问答
  • 因此,如何有效合理的优化APK的体积也是在平常开发中需要留意的。一、合理选择图片类型在APP中图片就占据了很大的一部分体积,所以图片的优化是我们必须要关注的点。平常使用到的图片类型主要有jpg、png、webp、svg...

    在平常开发过程中,随着应用功能不断增加和版本功能迭代,APK的体积大小在不知不觉中不断增大,而APK体积大小会影响用户首次下载安装应用或更新应用的时长和流量消耗。因此,如何有效合理的优化APK的体积也是在平常开发中需要留意的。

    一、合理选择图片类型

    在APP中图片就占据了很大的一部分体积,所以图片的优化是我们必须要关注的点。

    平常使用到的图片类型主要有jpg、png、webp、svg等。

    jpg是有损压缩格式,使用的一种失真压缩标准方法,24 bit真彩色,内容比GIF丰富,不支持动画、不支持透明色。

    png是无损压缩格式,PNG格式有8位、24位、32位三种形式,其中8位PNG支持两种不同的透明形式(索引透明和alpha透明),24位PNG不支持透明,32位PNG在24位基础上增加了8位透明通道(32-24=8),因此可展现256级透明程度。

    webp的优势体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性。

    svg是可缩放矢量图,SVG不会像位图一样因为缩放而让图片质量下降。优点在于可以减小APK的大小,在使用过程中可以使用tink属性变化颜色,建议用于图标大小在200dp*200dp的简单小图标以内。

    因此,小图标的文件类型我们优先选择svg相对其他文件类型可以有效减少APK大小。

    此外,在Android中使用svg格式的图片需要留意以下两点:

    1.svg是由xml定义的,标准svg根节点为。Android中只支持,我们需要通过 vector 将svg的根节点 转换为 。

    2.Android 5.0之前的版本不支持矢量图,需要向后兼容。

    2.1.在 build.gradle 中配置如下,

    android{

    // Gradle Plugin 2.0+

    defaultConfig{

    // 利用支持库中的 VectorDrawableCompat 类,可实现 2.1 版本及更高版本中支持 VectorDrawable

    vectorDrawables.useSupportLibrary = true

    }

    }

    dependencies {

    // 支持库版本需要是 23.2 或更高版本

    compile 'com.android.support:appcompat-v7:23.2.0'

    }

    2.2 使用 app:srcCompat 属性代替 android:src

    二、移除无用资源

    在需求功能变更和版本迭代的过程中,难免会产生无用的旧资源

    1.通过Android Studio提供的Remove Unused Resources可以帮助我们查找出无用资源。

    5c4b85af24b4

    Remove Unused Resources.png

    5c4b85af24b4

    选项.png

    点击Preview按钮预览一下查询出来的资源。搜索结果如下:

    5c4b85af24b4

    搜索结果.png

    2.通过Lint工具查找无用资源

    5c4b85af24b4

    Lint.png

    输入Unused resources快速找到Lint工具

    5c4b85af24b4

    Unused resources.png

    默认查询范围整个项目,直接点OK

    5c4b85af24b4

    默认.png

    查找结果如下

    5c4b85af24b4

    查找结果.png

    左边列表展示搜索到的无用资源,右边列表可以对资源单独做处理,具体如下:

    5c4b85af24b4

    处理.png

    第二种方式的可操作性比第一种好,因此推荐使用Lint方式去移除无效资源

    三、压缩代码

    在app目录下的build.gradle打开压缩代码功能

    buildTypes {

    release {

    minifyEnabled true //打开代码压缩

    proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'

    }

    debug {

    minifyEnabled true//打开代码压缩

    proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'

    }

    }

    对比如下:

    5c4b85af24b4

    未开启代码压缩.png

    5c4b85af24b4

    开启代码压缩.png

    四、压缩资源

    压缩资源功能需要搭配压缩代码一起使用

    buildTypes {

    release {

    shrinkResources true//打开资源压缩

    minifyEnabled true//打开代码压缩

    proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'

    }

    debug {

    shrinkResources true//打开资源压缩

    minifyEnabled true//打开代码压缩

    proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'

    }

    }

    效果如下:

    5c4b85af24b4

    代码压缩和资源压缩.png

    压缩资源可以自定义要保留的资源,具体步骤为创建keep.xml文件,存放位置为res/raw/keep.xml(构建系统不会将此文件打包到 APK 中)。在 tools:keep 属性中指定每个要保留的资源,在 tools:discard 属性中指定每个要舍弃的资源。这两个属性都接受以逗号分隔的资源名称列表。您可以将星号字符用作通配符。如下:

    tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"

    tools:discard="@layout/unused2" />

    五、资源混淆压缩

    资源混淆的原理是通过修改APK包里的resource.arsc文件,修改文件中的字符串池中的字符串,将其修改为简单短小的字符串,以此来减少文件大小,如下图:

    5c4b85af24b4

    资源混淆压缩原理.png

    可以使用微信的开源的AndResGuard进行压缩,具体步骤如下

    1.修改project目录下的build.gradle

    dependencies {

    classpath "com.android.tools.build:gradle:4.0.0"

    //引入AndResGuard

    classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.18'

    }

    2.project目录下新建andresguard.gradle

    5c4b85af24b4

    andresguard.gradle.png

    内容如下:

    apply plugin: 'AndResGuard'

    andResGuard {

    mappingFile = null

    use7zip = true

    useSign = true

    keepRoot = false

    compressFilePattern = [

    "*.png",

    "*.jpg",

    "*.jpeg",

    "*.gif",

    "resources.arsc"

    ]

    whiteList = [

    // your icon

    "R.drawable.icon",

    // for fabric

    "R.string.com.crashlytics.*",

    // for umeng update

    "R.string.tb_*",

    "R.layout.tb_*",

    "R.drawable.tb_*",

    "R.drawable.u1*",

    "R.drawable.u2*",

    "R.color.tb_*",

    // umeng share for sina

    "R.drawable.sina*",

    // for google-services.json

    "R.string.google_app_id",

    "R.string.gcm_defaultSenderId",

    "R.string.default_web_client_id",

    "R.string.ga_trackingId",

    "R.string.firebase_database_url",

    "R.string.google_api_key",

    "R.string.google_crash_reporting_api_key",

    ]

    sevenzip {

    artifact = 'com.tencent.mm:SevenZip:1.2.10'

    //path = "/usr/local/bin/7za"

    }

    }

    3.app目录下的build.gradle引入andresguard.gradle

    apply from: "${rootProject.rootDir}/andresguard.gradle"

    配置编译完成后可以看到增加了andresguard的相关Task

    5c4b85af24b4

    Taskpng

    点击resguradDebug打包,跟之前的apk做对比,结果如下:

    5c4b85af24b4

    未压缩.png

    5c4b85af24b4

    压缩后.png

    以上五种优化方式基本所有APP都通用,以下的优化方式需要根据项目实际应用情况合理选择。

    六、国际化配置优化

    假如APP只在一个国家或者只针对一种语言做适配,不需要做国际化配置的情况下,可以在app下的build.gradle增加如下配置,移除第三方SDK的国际化配置,减少APP体积:

    android{

    defaultConfig{

    // 适配默认语言英语

    resConfigs 'en'

    }

    }

    配置后APK大小前后对比如下:

    5c4b85af24b4

    未做国际化配置优化.png

    5c4b85af24b4

    做国际化配置优化后.png

    七、so库打包优化

    so文件是由ndk编译出来的动态库,每一个平台需要使用对应的so库。

    ABI 是应用程序二进制接口简称(Application Binary Interface),定义了二进制文件(尤其是.so文件)如何运行在相应的系统平台上,从使用的指令集,内存对齐到可用的系统函数库。

    在Android 系统上,每一个CPU架构对应一个ABI:armeabi,armeabi-v7a,arm64- v8a,x86,x86_64,mips,mips64。

    目前市面上主流的手机中,基本都是使用基于armeabi-v7a的CPU架构,因此我们只需要配置armeabi-v7a就可以了。

    如果已经确定APP只运行在某个设备上,也可以直接配置对应的ABI。

    配置代码如下:

    android{

    defaultConfig{

    ndk{

    abiFilters "armeabi-v7a"

    }

    }

    }

    展开全文
  • 从今天开始我们将进行Android优化技术方面的总结,我们将从不同的视角进行优化技术的分析总结。这一篇是从代码视角进行优化,这些都是平时编码中总结的经验,如有错误或更好的方法建议欢迎各位批评指证。接下来,让...

    从今天开始我们将进行Android优化技术方面的总结,我们将从不同的视角进行优化技术的分析总结。这一篇是从代码视角进行优化,这些都是平时编码中总结的经验,如有错误或更好的方法建议欢迎各位批评指证。接下来,让我们进入主题吧。

    一、循环的优化

    1、不要在for循环的第二部分语句调用函数

    在开发过程中,我们经常会用for循环来遍历数组操作,在程序中我们有可能会这样写:

    for(int i = 0;i < list.size();i++)

    {

    //执行重复操作

    }

    这样的写法会一直执行list.size()方法,从而会消耗更多的性能,最优写法应改为:

    for(int i =0,len = list.size();i < len;i++)

    {

    //执行循环操作

    }

    2、不要在循环体内实例化变量

    在实际开发中,我们经常会遇到要把一系列对象添加到List当中,比如我们在给Adapter类提供数据源的场景下,我们通常会这样做:

    for(int i = 0;i < count;i++)

    {

    //创建对象

    MsgBean bean = new MsgBean();

    //添加bean到list

    list.add(bean);

    }

    这样的写法,会重复定义大量变量,消耗内存资源,所以我们应该在循环体外面定义引用变量,最优写法应改为:

    MsgBean bean;

    for(int i = 0;i < count;i++)

    {

    //创建对象

    bean = new MsgBean();

    //添加bean到list

    list.add(bean);

    }

    二、尽量使用clone()代替new的方式创建对象

    我们在使用new关键字创建对象时,继承树里的整个构造函数链都会被自动调用,并且总是从Object类开始执行直到当前类,因为java里所有类都继承自Object类。但,如果使用clone()方法来实例化一个对象则不会调用构造函数。前提是该类要实现Cloneable接口。在使用工厂设计模式的场景下,应该使用clone()方法代替new关键字,例如:

    public static Message getInstance(){

    return new Message();

    }

    最优写法应改为:

    private static Message msg = new Message();

    public static Message getInstance(){

    return (Message)msg.clone();

    }

    三、字符串拼接操作优化

    在java中跟字符串相关的类主要有:String、StringBuilder和StringBuffer。关于这三个类的区别在前面的文章《从Java源码角度彻底理解String,StringBuilder和StringBuffer的区别》里已经做过分析,此处不再赘述。如果是静态字符串拼接,则使用“+”,例如:

    String str = "hello" + " world" + " test";

    因为,这种形式的字符串拼接编译器最终会优化成“hello world test”,先检查常量池中是否已经有“hello world test”字符串,如果有则不再创建对象。如果是动态拼接,即拼接的字符串中有变量,则使用StringBuilder和StringBuffer,其中,StringBuffer是线程安全的,所以稍微要比StringBuilder要慢点。例如,

    String result = "";

    StringBuilder sb = new StringBuilder();

    sb.append("hello")

    .append("world")

    .append("Android");

    result = sb.toString();

    四、若一个成员方法不需要Override,也不需要访问成员变量,则用static方法

    因为,要实现多态功能,java里要维护一张类似C++里的虚函数表一样的表,所以,当一个方法不需要Override的时候则尽量使用static来修饰,如我们经常使用工具类Utils里的方法则可以全部定义成static方法。例如,

    public static String getName(){

    return "hello";

    }

    五、数据复制使用System.arrayCopy()方法

    在开发过程中,我们经常会用到数据复制功能,在这里我们推荐System.arrayCopy()这个native方法。因为是native方法,其性能要比普通方法高得多。像上面提到的StringBuilder、StringBuffer里底层也使用了System.arrayCopy(),还有ArrayList和Vector底层也使用该native方法,来实现数据的复制操作。

    public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

    参数说明:

    src:源数组; srcPos:源数组要复制的起始位置;

    dest:目的数组; destPos:目的数组放置的起始位置; length:复制的长度。

    最后,此处只是简单的列举了日常编码中经常用到的一些优化技巧,并不是全部,还有很多代码优化技巧就不再罗列了,比如I/O操作,网络请求等等。不过,不管何种优化技巧都有一个不变的宗旨可遵循,就是一起围绕时间和空间的角度来优化:

    (1)尽最大可能减少语句的执行条数;

    (2)尽最大可能减少变量的创建数目。

    (3)防止内存泄露

    关于前2条,在上述的优化方法技巧中都得到了体现。关于接下来Android优化技术的系列文章,我们将从以下几个视角去分析总结,敬请关注:

    1.内存管理,即内存的分配和回收

    2.程序设计,即常用数据结构与算法,设计模式

    3.运算符使用技巧

    4.UI布局优化

    文/kinbos(简书作者) 原文链接:http://www.jianshu.com/p/83e159cdb32d 著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

    展开全文
  • android 优化system分区空间小方法

    千次阅读 2021-10-25 19:25:01
    为何需要采用此种方式 ,随着android系统大版本的升级,系统本身的体积越来越大,对于必须要内置GMS包的升级项目,system分区的大小因为之前在低版本时,给得不够大,为了OTA升级,又不能修改分区的大小,那就只能...

    背景

    为何需要采用此种方式 ,随着android系统大版本的升级,系统本身的体积越来越大,对于必须要内置GMS包的升级项目,system分区的大小因为之前在低版本时,给得不够大,为了OTA升级,又不能修改分区的大小,那就只能各种裁剪,尝试各种减少system分区占用的方法(裁剪app及so、关闭部分app的预编译等). 经历各种折腾后,还没有达到预期的效果,最后找到了apk gz压缩编译的方式节省分区空间.

    原理

    此方案android源码很早就已经支持了,猜想此方案并未被广泛应用的原因,一方面,增加分区大小比较稳妥,另一方面,大部分场景下,大家以裁剪为主,不太会考虑此种方式;补充一点,apk最终解压缩安装到data分区,会占用userdata的空间.

    通过对代码的追踪和实践,归纳为两个方面:

    1. 压缩编译apk为.gz,达到减少apk体积的目的;

    2.开机启动时,解压缩apk,安装到data分区.

    方案

    提到具体的方案,因为android已经实现,并未有多少难点,还是直接贴出代码位置,便于大家实操,另外,调试中也遇到一些问题.

    • 压缩编译apk代码

    build/make/core/app_prebuilt_internal.mk

    ...
    
    ifdef LOCAL_COMPRESSED_MODULE
      ifneq (true,$(LOCAL_COMPRESSED_MODULE))
        $(call pretty-error, Unknown value for LOCAL_COMPRESSED_MODULE $(LOCAL_COMPRESSED_MODULE))
      endif
      LOCAL_BUILT_MODULE_STEM := package.apk.gz
      ifndef LOCAL_INSTALLED_MODULE_STEM
        PACKAGES.$(LOCAL_MODULE).COMPRESSED := gz
        LOCAL_INSTALLED_MODULE_STEM := $(LOCAL_MODULE).apk.gz
      endif
    else  # LOCAL_COMPRESSED_MODULE
      LOCAL_BUILT_MODULE_STEM := package.apk
      ifndef LOCAL_INSTALLED_MODULE_STEM
        LOCAL_INSTALLED_MODULE_STEM := $(LOCAL_MODULE).apk
      endif
    endif  # LOCAL_COMPRESSED_MODULE
    ...

    针对内置的app,无论是打包好的apk或者是源码编译,mk中添加LOCAL_COMPRESSED_MODULE := true即可编译为package.apk.gz包,另外,当搜索LOCAL_COMPRESSED_MODULE关键字时,发现必须关闭apk预编译.

    # If the module is a compressed module, we don't pre-opt it because its final
    # installation location will be the data partition.
    # 启用压缩编译apk时,必须关闭apk的预编译
    ifdef LOCAL_COMPRESSED_MODULE
    LOCAL_DEX_PREOPT := false
    endif
    

    内置apk,Android.mk添加

    LOCAL_COMPRESSED_MODULE := true

    LOCAL_DEX_PREOPT := false

    • 内置stub app并解压缩apk.gz安装到data分区

    做壳Stub apk比较关键,apk需要安装成功,就必定要走PKMS的流程,此处不对安装apk的正常流程做解析,只对安装stub app做些流程上的梳理.

    直观的PackageManagerService中的构造方法中调用

    1. installStubPackageLI(pkg, 0, scanFlags);

                // install the package to replace the stub on /system
                try {
                    installStubPackageLI(pkg, 0, scanFlags);
                    ps.setEnabled(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
                            UserHandle.USER_SYSTEM, "android");
                    systemStubPackageNames.remove(i);
                } catch (PackageManagerException e) {
                    Slog.e(TAG, "Failed to parse uncompressed system package: " + e.getMessage());
                }

     2.通过遍历包名,得到哪些是stub app的包,然后执行stub app的安装;

    /** 
    * 解压安装stub app就是为了节省system分区的空间,并且注释告知我们stub app可以用一个仅仅只拥* *  
    * 有AndroidManifest的app即可,如果stub app有问题,将不会使能该app并防止解压缩
    */
    
        private void installSystemStubPackages(@NonNull List<String> systemStubPackageNames,
                @ScanFlags int scanFlags) {
            for (int i = systemStubPackageNames.size() - 1; i >= 0; --i) {
                final String packageName = systemStubPackageNames.get(i);
                // skip if the system package is already disabled
                if (mSettings.isDisabledSystemPackageLPr(packageName)) {
                    systemStubPackageNames.remove(i);
                    continue;
                }
                // skip if the package isn't installed (?!); this should never happen
                final AndroidPackage pkg = mPackages.get(packageName);
                if (pkg == null) {
                    systemStubPackageNames.remove(i);
                    continue;
                }
                // skip if the package has been disabled by the user
                final PackageSetting ps = mSettings.mPackages.get(packageName);
                if (ps != null) {
                    final int enabledState = ps.getEnabled(UserHandle.USER_SYSTEM);
                    if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
                        systemStubPackageNames.remove(i);
                        continue;
                    }
                }
    
                // install the package to replace the stub on /system
                try {
                    installStubPackageLI(pkg, 0, scanFlags);
                    ps.setEnabled(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
                            UserHandle.USER_SYSTEM, "android");
                    systemStubPackageNames.remove(i);
                } catch (PackageManagerException e) {
                    Slog.e(TAG, "Failed to parse uncompressed system package: " + e.getMessage());
                }
    
                // any failed attempt to install the package will be cleaned up later
            }
    
            // disable any stub still left; these failed to install the full application
            for (int i = systemStubPackageNames.size() - 1; i >= 0; --i) {
                final String pkgName = systemStubPackageNames.get(i);
                final PackageSetting ps = mSettings.mPackages.get(pkgName);
                ps.setEnabled(PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                        UserHandle.USER_SYSTEM, "android");
                logCriticalInfo(Log.ERROR, "Stub disabled; pkg: " + pkgName);
            }
        }
    

    3.扫描安装,如果package为stub app,即可解压缩目录;

    /*****************************************************************************
     *  扫描安装Stubpackage的方法,直接完成调用decompressPackage方法对stub app解压缩
     *
     ****************************************************************************/   
     private AndroidPackage installStubPackageLI(AndroidPackage stubPkg,
                @ParseFlags int parseFlags, @ScanFlags int scanFlags)
                        throws PackageManagerException {
            if (DEBUG_COMPRESSION) {
                Slog.i(TAG, "Uncompressing system stub; pkg: " + stubPkg.getPackageName());
            }
            // uncompress the binary to its eventual destination on /data
            final File scanFile = decompressPackage(stubPkg.getPackageName(), stubPkg.getCodePath());
            if (scanFile == null) {
                throw new PackageManagerException(
                        "Unable to decompress stub at " + stubPkg.getCodePath());
            }
            synchronized (mLock) {
                mSettings.disableSystemPackageLPw(stubPkg.getPackageName(), true /*replaced*/);
            }
            removePackageLI(stubPkg, true /*chatty*/);
            try {
                return scanPackageTracedLI(scanFile, parseFlags, scanFlags, 0, null);
            } catch (PackageManagerException e) {
                Slog.w(TAG, "Failed to install compressed system package:" + stubPkg.getPackageName(),
                        e);
                // Remove the failed install
                removeCodePathLI(scanFile);
                throw e;
            }
        }

    4. 解压缩.gz 的apk,创建相应的目录,是stub app安装到data分区,直接调用了PackageManagerServiceUtils.getCompressedFiles(codePath);

        /**
         * Decompresses the given package on the system image onto
         * the /data partition.
         * @return The directory the package was decompressed into. Otherwise, {@code null}.
         */
    
        /** 解压压缩编译apk后,安装到data分区下的*/
    
        private File decompressPackage(String packageName, String codePath) {
            final File[] compressedFiles = getCompressedFiles(codePath);
            if (compressedFiles == null || compressedFiles.length == 0) {
                if (DEBUG_COMPRESSION) {
                    Slog.i(TAG, "No files to decompress: " + codePath);
                }
                return null;
            }
            final File dstCodePath =
                    getNextCodePath(Environment.getDataAppDirectory(null), packageName);
            int ret = PackageManager.INSTALL_SUCCEEDED;
            try {
                makeDirRecursive(dstCodePath, 0755);
                for (File srcFile : compressedFiles) {
                    final String srcFileName = srcFile.getName();
                    final String dstFileName = srcFileName.substring(
                            0, srcFileName.length() - COMPRESSED_EXTENSION.length());
                    final File dstFile = new File(dstCodePath, dstFileName);
                    ret = decompressFile(srcFile, dstFile);
                    if (ret != PackageManager.INSTALL_SUCCEEDED) {
                        logCriticalInfo(Log.ERROR, "Failed to decompress"
                                + "; pkg: " + packageName
                                + ", file: " + dstFileName);
                        break;
                    }
                }
            } catch (ErrnoException e) {
                logCriticalInfo(Log.ERROR, "Failed to decompress"
                        + "; pkg: " + packageName
                        + ", err: " + e.errno);
            }
            if (ret == PackageManager.INSTALL_SUCCEEDED) {
                final File libraryRoot = new File(dstCodePath, LIB_DIR_NAME);
                NativeLibraryHelper.Handle handle = null;
                try {
                    handle = NativeLibraryHelper.Handle.create(dstCodePath);
                    ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
                            null /*abiOverride*/, false /*isIncremental*/);
                } catch (IOException e) {
                    logCriticalInfo(Log.ERROR, "Failed to extract native libraries"
                            + "; pkg: " + packageName);
                    ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
                } finally {
                    IoUtils.closeQuietly(handle);
                }
            }
            if (ret != PackageManager.INSTALL_SUCCEEDED) {
                if (!dstCodePath.exists()) {
                    return null;
                }
                removeCodePathLI(dstCodePath);
                return null;
            }
    
            return dstCodePath;
        }

    5.系统如何判定stub app的条件,相当于告知我们如何创建一个stub app.

        public static File[] getCompressedFiles(String codePath) {
            final File stubCodePath = new File(codePath);
            final String stubName = stubCodePath.getName();
    
            // The layout of a compressed package on a given partition is as follows :
            //
            // Compressed artifacts:
            //
            // /partition/ModuleName/foo.gz
            // /partation/ModuleName/bar.gz
            //
            // Stub artifact:
            //
            // /partition/ModuleName-Stub/ModuleName-Stub.apk
            //
            // In other words, stub is on the same partition as the compressed artifacts
            // and in a directory that's suffixed with "-Stub".
    
           //上面的注释很关键,告知我们如何创建一个stub app的方法
            int idx = stubName.lastIndexOf(STUB_SUFFIX);
            if (idx < 0 || (stubName.length() != (idx + STUB_SUFFIX.length()))) {
                return null;
            }
    
            final File stubParentDir = stubCodePath.getParentFile();
            if (stubParentDir == null) {
                Slog.e(TAG, "Unable to determine stub parent dir for codePath: " + codePath);
                return null;
            }
    
            final File compressedPath = new File(stubParentDir, stubName.substring(0, idx));
            final File[] files = compressedPath.listFiles(new FilenameFilter() {
                @Override
                public boolean accept(File dir, String name) {
                    return name.toLowerCase().endsWith(COMPRESSED_EXTENSION);
                }
            });
    
            if (DEBUG_COMPRESSION && files != null && files.length > 0) {
                Slog.i(TAG, "getCompressedFiles[" + codePath + "]: " + Arrays.toString(files));
            }
    
            return files;
        }

    以上,基本思路是很简单的,主要是Google都已经给实现了,只不过很少被人这样来操作罢了.

    总结

    思路不难,但是因为开发过程中,思考的方式或者说习惯以增加system分区来解决空间吃紧问题,google大版本升级,他们自然也考虑到了这一点,包括android10上启用的动态分区,都是体现.当然,如果预置签名的app,这个也是需要考虑到的状态,本人就有遇到,采取绕过签名的方式处理,具体细节就不在此赘述. 如果有朋友有需要,可以私聊,这个稍微看下代码即可搞定.

    展开全文
  • Android 优化Handler防止内存泄露Demo描述:Handler可能导致的内存泄露及其优化1 关于常见的Handler的用法但是可能导致内存泄露2 优化方式请参考BetterHandler和BetterRunnable的实现package cc.cc;import java.lang....

    Android 优化Handler防止内存泄露

    Demo描述:

    Handler可能导致的内存泄露及其优化

    1 关于常见的Handler的用法但是可能导致内存泄露

    2 优化方式请参考BetterHandler和BetterRunnable的实现

    package cc.cc;

    import java.lang.ref.WeakReference;

    import android.os.Bundle;

    import android.os.Handler;

    import android.os.Message;

    import android.app.Activity;

    /**

    * Demo描述:

    * Handler可能导致的内存泄露及其优化

    *

    * 1 关于常见的Handler的用法但是可能导致内存泄露

    * 请参考方法initHandler()

    * 2 优化方式请参考BetterHandler和BetterRunnable的实现

    *

    *

    *

    */

    public class MainActivity extends Activity {

    private Handler mHandler;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    }

    /**

    * 常见的Handler的用法但是可能导致内存泄露

    *

    * 比如在旋转屏幕时该Activity重新绘制.

    * 但是因为mHandler发送了一个延迟消息,所以消息队列持有mHandler对象

    * 又由于new Runnable(){}持有外部类MainActivity的引用

    * 所以Activity所占内存并不能向期望的那样被回收,这样就可能会造成内存泄漏.

    *

    * 这个例子中Handler的延迟时间比较久有20S,有点极端了,一般不会这么干;

    * 这里只是为了更好地说明这个问题就这么写代码了。

    *

    */

    private void initHandler() {

    mHandler = new Handler() {

    @Override

    public void handleMessage(Message msg) {

    super.handleMessage(msg);

    }

    };

    // ......doing something

    // ......doing something

    // ......doing something

    // 发送延迟消息

    mHandler.postDelayed(new Runnable() {

    @Override

    public void run() {

    }

    }, 1000 * 20);

    }

    /**

    * 以下为优化方式

    * 1 在此处把BetterHandler和BetterRunnable都设计为静态类,

    * 这样它们就不会持有外部类的引用了.

    * 2 在BetterHandler中利用WeakReference持有Activity.

    * 常听说:"如果一个对象具有弱引用,那么当GC线程扫描的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存"

    * 其实准备地说应该是"如果一个对象只具有弱引用.........",即仅有弱引用而不存在对其的强引用才会将其回收.

    * 那么此处对Activity采用了弱引用,会不会导致该Activity被回收呢?

    * 答案是否定的。因为此处的Activity还在显示界面,当然存在其他对象对它的强引用。所以不会对其回收。

    *

    * 经过这样的优化,当旋转屏幕时需要销毁原Activity时;消息队列持有Handler对象.但此时Handler对象不再持有Activity的引用.

    * 所以系统会回收该Activity所占内存.所以在handleMessage()中处理消息时需要判断Activity是否为空.

    * 比如此处20秒后才处理消息 这个时候Activity为空.

    */

    private static class BetterHandler extends Handler{

    private final WeakReference activityWeakReference;

    public BetterHandler(Activity activity){

    activityWeakReference=new WeakReference(activity);

    }

    @Override

    public void handleMessage(Message msg) {

    super.handleMessage(msg);

    if (activityWeakReference.get()!=null) {

    //.....handle message

    } else {

    System.out.println("Activity==null");

    }

    }

    }

    //同样采用静态内部类

    private static class BetterRunnable implements Runnable{

    @Override

    public void run() {

    // ......doing something

    }

    }

    //发送延迟消息

    private void sendMessage(){

    BetterHandler betterHandler=new BetterHandler(MainActivity.this);

    betterHandler.postDelayed(new BetterRunnable(), 1000 * 20);

    }

    }

    如有疑问请留言或者到本站社区交流讨论,感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

    展开全文
  • 优化好处 包体积减小,易于升级 多市场渠道有体积限制,避免二次处理 apk安装时间减小 运行时内存占用小 磁盘空间占用小,odex二进制文件小。 APK组成及分析 APK组成 assets: 开发目录下assets目录 lib:所需要的...
  • 前言:最近准备着手优化APP的工作了这篇文章关于Android7.0上的后台优化,并且我们需要使用什么方案来替代以前的一些做法。Android N 对以下三种广播通知的改动:**CONNECTIVITY_ACTION: 网络发生变化ACTION_NEW_...
  • WakeLockAndroid 系统本身为了优化电量的使用,会在没有操作时进入休眠状态,来节省电量。当然,为了便于开发(很多应用不可避免的希望在灭屏后还能运行一些事儿,或是要保持屏幕一直亮着--比如播放视频),Android ...
  • 2020-2021最新Android性能优化总结【附:1586页PDF分享】

    多人点赞 热门讨论 2021-06-01 21:59:29
    APP优化是我们进阶高级开发工程师的必经之路, 而APP启动速度的优化,也是我们开启APP优化的第一步。 用户在使用我们的软件时,交互最多最频繁的也就是APP的启动页面,如果启动页面加载过慢,很可能造成用户对我们...
  • 前提在Android中,每个应用程序中储存的数据文件都会被多个进程访问:安装程序会读取应用程序的manifest文件来处理与之相关的权限问题;Home应用程序会读取资源文件来获取应用程序的名和图标;系统服务会因为很多种...
  • android工程在越来越复杂后,编译速度会越来越慢,逐渐对开发效率带来很大的影响。 本篇记录一些常见的编译速度优化的方式。 一、 各种常见编译参数的配置和gradle、kotlin版本的更新 网上关于这些编译参数的文档...
  • android:id="@+id/button_capture" android:text=“照相” android:layout_width=“wrap_content” android:layout_height=“wrap_content” android:layout_gravity=“center” /> java代码 想要对摄像头进行...
  • 作为移动应用开发者,我们总希望发布的apk文件越小越好,不希望...Android lint是在ADT 16提供的新工具,它是一个代码扫描工具,能够帮助我们识别代码结构存在的问题,主要包括:1)布局性能(以前是 layoutopt工具...
  • Android的开机速度,基本上没人说快的,通常移植完系统后,马上要看的事情就是优化开机时间,以下是简单回忆以下以前做优化的那些事。开机时间都花在哪?优化开机时间,通常做的首先是那有有没有BUG,明显不合理的先...
  • Android性能优化之启动优化android 性能优化App启动流程首先要了解App的启动流程,详情参考面试之Android进阶第一个Activity的优化启动时间的量化对于Activity来说,启动时,首先执行的是onCreate()、onStart()、...
  • 魔鬼藏于细节之中,每一段粗心的代码都可能影响软件的性能,本文主要记录一些不那么常见的性能优化知识点。每次打开、读写文件,操作系统需要从用户态切换到内核态,这种状态切换很消耗性能。优化核心减少磁盘 I/O ...
  • 开头 互联网时代的到来,让我们获取知识变...B4A是Android的基础版,这是一种可简化编程的Android的应用程序开发工具。这是一个IDE,可以允许开发者使用Basic语言来创建Android移动应用。Basic语言是一种过程化编程语言
  • Android 开机速度优化

    2021-05-26 15:05:43
    开机速度经常被用来做为竞品比较的一个参数,开机快容易给用户一种机器运行较快的...本文着力于Android上开机速度的优化。从开机的流程上来耗时较长的有以下几点:==preload classes和resource==所有的Android应用程...
  • Android优化学习总结

    2021-03-17 11:05:08
    Android优化方向可系统划分为如下四大块: 安装包: 启动耗时 运行:界面卡顿、内存高、耗电快 运行异常
  • 前言在 Android开发中,性能优化策略十分重要本文主要讲解性能优化中的布局优化,希望你们会喜欢。目录1. 影响的性能布局性能的好坏 主要影响 :Android应用中的页面显示速度2. 如何影响性能布局影响Android性能的...
  • Android 开发的小伙伴都知道, 典型的开发场景就是用 Handler 来更新UI或者做一些简单耗时的操作。 举个例子,这样一写就完成了。 Handler mHandler = new Handler(){ @Override public void handleMessage...
  • Android包体积优化指南

    2021-06-05 01:46:03
    Android 安装包主要由 libs(就是so文件)、assets、res以及code组成。大部分应用中,这些内容都占了安装包99%的体积。下面我们逐步分析每一种类型文件的瘦身方案。libslibs也就是so文件。大部分流行应用都会包含libs...
  • Android 电量优化

    2021-06-06 03:12:15
    为什么优化现在智能手机基本能一天一充,如果一个应用耗电过多,肯定是有问题的,而开发中电量的优化可能是最容易被忽略的。如何检测1、手机设置-电池使用情况,查看电量消耗;2、使用Battery Historian配置电池使用...
  • 文章目录线程调度的原理Android的线程调度 线程调度的原理 任意时刻,只有一个线程占用CPU,处于运行状态 多线程并发:轮流获取CPU使用权 Android的线程调度
  • Android 优化 图标文字 iconfont使用图标文字代替图片的实践进入项目的main文件夹创建一个assets文件夹,并在assets下创建fonts文件夹, 把非系统字体放在fonts下Snip20170218_2.png使用:先在.xml文件,设置好文本...
  • 前面介绍过使用HierarchyViewer和Android lint来优化我们的程序,这一篇算是总结性的,借助一个小例子来说用怎么优化应用布局。这个例子是android官网给出的,作者也当一把翻译。多数开发者可能会这样认为,使用基本...
  • 刷内核效果很好仅仅刷手机的ROM是不够的,虽然多了很多自定义的功能,流畅度已经高于官方的ROM,但依旧有很大提升的空间,这时候我们就需要通过刷内核来进一步优化,刷内核所能带来的提升是相当明显的,但是对于刷...
  • 建议进入Recovery模式执行双清wipe操作。Recovery模式指的是一种可以对安卓机内部的数据或系统进行修改的模式(类似于windowsPE或DOS)。在这个模式下可以刷入新的安卓系统,或者对已有的系统进行备份或升级,也可以在...
  • Android 如何优化开屏广告?

    千次阅读 2021-03-23 10:12:52
    而优量汇已经自动的帮我们做了优化。 查看完整示例,请点击阅读全文,或访问链接:TogetherAd https://github.com/ifmvo/TogetherAd 关注我的公众号,持续更新优质文章。 微信扫一扫下方二维码即可关注:
  • Android优化之Systrace

    2021-06-06 11:53:09
    首先我们需要了解一下什么是systracesystrace命令允许您在系统级别的设备上...(摘自 Android Developers)图1简言之,systrace是一种代码追踪手段,他能将代码的运行情况,以图文的形式传达出来。因此,我们通过使用s...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 253,801
精华内容 101,520
关键字:

android优化