安卓app启动优化

1. 安卓app启动优化

安卓app启动优化是指提高应用在用户点击图标到显示主界面之间的速度,提升用户体验和留存率。应用启动时,系统会为其创建进程、加载类、初始化资源等,这些操作都会消耗时间和内存。因此,我们需要从多个方面对应用启动进行优化,包括:

  • 降低进程创建时间
  • 减少类加载时间
  • 延迟初始化资源
  • 优化启动窗口
  • 使用多进程或多线程

下面我们将分别介绍这些方面的具体方法和实践。

1.1 降低进程创建时间

应用启动时,如果后台没有该应用的进程存在,则系统会为其创建一个新的进程。进程创建涉及到fork、execve等系统调用,以及内存映射、复制等操作,这些都会增加启动时间。因此,我们可以通过以下几种方式来降低进程创建时间:

  • 减少应用依赖的第三方库或模块。每个库或模块都会增加应用的大小和复杂度,导致更多的类需要加载和初始化。我们可以尽量使用系统提供的API或轻量级的库,并移除不必要或重复的依赖。
  • 减少Application类中的代码量。Application类是应用最先执行的类,它会在主线程中进行初始化,并影响后续Activity等组件的加载。我们可以将Application类中不必要或可延迟执行的代码移出去,例如第三方SDK初始化、数据库操作等。
  • 使用dex分包技术。dex分包是指将一个大型dex文件拆分成多个小型dex文件,并按需加载。这样可以减少主dex文件中需要加载和验证的类数量,从而缩短进程创建时间。

下面是一个使用dex分包技术来优化启动时间的示例代码:

// 在build.gradle文件中配置multiDexEnabled为true
android {
    defaultConfig {
        ...
        multiDexEnabled true
    }
}

// 在Application类中重写attachBaseContext方法,并调用MultiDex.install方法
public class MyApplication extends Application {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        // 加载其他dex文件
        MultiDex.install(this);
    }
}

1.2 减少类加载时间

应用启动时,系统会通过ClassLoader来加载并验证应用所需的所有类。这个过程也会消耗一定时间和内存。因此,我们可以通过以下几种方式来减少类加载时间:

  • 使用懒加载或按需加载技术。懒加载是指只在需要时才去实例化对象或执行方法;按需加载是指只在特定场景下才去加载某些模块或功能。这样可以避免一次性加载过多无关紧要或暂时不需要使用到得对象。
  • 使用静态内部类来实现单例模式。单例模式是指保证一个类只有一个实例,并提供一个全局访问点
    使用静态内部类来实现单例模式。单例模式是指保证一个类只有一个实例,并提供一个全局访问点。使用静态内部类可以实现线程安全和延迟加载的效果,因为静态内部类只有在被调用时才会被加载,而且由于类加载机制的原因,不需要额外的同步措施。
    下面是一个使用静态内部类来实现单例模式的示例代码:
// 定义一个单例类
public class Singleton {
    // 私有化构造方法
    private Singleton() {}
    // 定义一个静态内部类,持有唯一的单例对象
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
    // 提供一个公共的获取单例对象的方法
    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

使用ProGuard或R8等工具进行代码混淆和优化。代码混淆和优化可以减少应用中无用或重复的代码,缩短方法名和变量名,从而减少dex文件的大小和类加载时间。
下面是一个使用ProGuard进行代码混淆和优化的示例配置:

#在build.gradle文件中开启minifyEnabled选项
android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

在proguard-rules.pro文件中添加自定义规则,例如保留某些不想被混淆或优化的类或方法

-keep class com.example.MyClass { *; }
-keepclassmembers class com.example.MyClass {
  public void myMethod();
}

1.3 延迟初始化资源

应用启动时,除了需要加载类之外,还需要初始化一些资源,例如布局、图片、音频、视频等。这些资源也会占用一定时间和内存。因此,我们可以通过以下几种方式来延迟初始化资源:

使用懒加载或按需加载技术。同样地,我们可以只在需要时才去初始化或加载某些资源,并及时回收不再使用到得资源。
使用异步或并行方式来初始化资源。我们可以将一些耗时较长或不影响主界面显示得资源放到子线程中去初始化或加载,并在合适得时机回到主线程更新UI。
使用缓存技术来存储常用得资源。我们可以将一些频繁使用到得资源缓存在本地存储空间中,并在启动时快速读取出来。
下面是一个使用异步方式来初始化音频资源得示例代码:

// 定义一个音频管理器类
public class AudioManager {
    // 定义一个音频播放器对象
    private MediaPlayer mediaPlayer;
    // 定义一个上下文对象
    private Context context;
    
    // 构造方法,传入上下文参数
    public AudioManager(Context context) {
        this.context = context;
        // 在子线程中初始化音频播放器对象,并设置数据源为本地raw目录下得audio.mp3文件
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    mediaPlayer = new MediaPlayer();
                    AssetFileDescriptor afd = context.getResources().openRawResourceFd(R.raw.audio);
                    mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
                    afd.close();
                    mediaPlayer.prepare();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
}

// 定义一个播放音频的方法
public void playAudio() {
    // 判断音频播放器对象是否已经初始化完成
    if (mediaPlayer != null && mediaPlayer.isPlaying() == false) {
        // 在主线程中播放音频
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                mediaPlayer.start();
            }
        });
    }
}

// 定义一个释放资源的方法
public void release() {
    // 判断音频播放器对象是否存在
    if (mediaPlayer != null) {
        // 停止播放并释放资源
        mediaPlayer.stop();
        mediaPlayer.release();
        mediaPlayer = null;
    }
}
}

1.4 优化启动窗口

启动窗口,也叫启动页、SplashWindow、StartingWindow等,指的是应用启动时候的预览窗口。iOS App强制有一个启动页,用户点击桌面App图标之后,系统会立即显示这个启动窗口,等App主页加载好之后再显示主页面。Android也有类似的机制(启动窗口),但是不同厂商或版本可能有不同的实现方式和效果。一般来说,Android的启动窗口分为两种:

  • 系统默认的白色或黑色背景。这种启动窗口比较简单,但是可能会给用户一种闪屏或卡顿的感觉。
  • 自定义的主题背景。这种启动窗口可以根据应用的风格和需求来设计,例如显示应用logo、名称、广告等内容。

我们可以通过以下几种方式来优化启动窗口:

  • 使用合适大小和格式的图片资源。我们可以根据不同分辨率和密度的设备来提供合适大小和格式(例如png、webp等)得图片资源,并避免使用过大或过多得图片资源。
  • 使用简单而美观得设计风格。我们可以使用简单而美观得颜色、图形、文字等元素来设计启动窗口,并保持与应用主界面得一致性和连贯性。
  • 使用渐变或过渡效果。我们可以使用渐变或过渡效果来平滑地切换从启动窗口到主界面,并避免使用复杂或耗时得动画效果。

下面是一个使用自定义主题背景来优化启动窗口得示例代码:

<!-- 在styles.xml文件中定义一个自定义主题 -->
<style name="SplashTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- 设置状态栏颜色 -->
    <item name="android:statusBarColor">@color/colorPrimary</item>
    <!-- 设置背景图片 -->
    <item name="android:windowBackground">@drawable/splash_bg</item>
</style>

<!-- 在splash_bg.xml文件中定义一个背景图片 -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 设置背景颜色 -->
    <item android:drawable="@color/colorPrimary"/>
    <!-- 设置logo图片 -->
    <item>
        <bitmap android:src="@drawable/logo"
                android:gravity="center"/>
    </item>
</layer-list>

<!-- 在AndroidManifest.xml文件中为SplashActivity设置自定义主题 -->
<activity android:name=".SplashActivity"
          android:theme="@style/SplashTheme">
</activity>

在SplashActivity.java文件中实现跳转到MainActivity

public class SplashActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      // 跳转到MainActivity并结束当前Activity
      startActivity(new Intent(this, MainActivity.class));
      finish();
  }
}

1.5 使用多进程或多线程

应用启动时,默认情况下只有一个主进程和一个主线程。主进程负责运行应用的所有组件,例如Activity、Service、BroadcastReceiver等。主线程负责处理所有的UI操作,例如绘制界面、响应用户输入等。如果在主进程或主线程中执行一些耗时或阻塞的操作,例如网络请求、数据库操作、文件读写等,就会导致应用启动变慢或卡顿。

因此,我们可以通过以下几种方式来使用多进程或多线程:

使用多进程来隔离一些不需要与UI交互的组件,例如Service、ContentProvider等。这样可以减少主进程的负担和内存占用,并提高应用的稳定性和安全性。
使用多线程来执行一些耗时或阻塞的操作,例如网络请求、数据库操作、文件读写等。这样可以避免阻塞主线程,并提高应用的响应速度和用户体验。
使用合理的线程池或异步任务框架来管理多线程。我们可以根据不同类型和优先级的任务来选择合适的线程池或异步任务框架,并避免创建过多或过少的线程。
下面是一个使用多线程来执行网络请求的示例代码:

// 定义一个网络请求管理器类
public class NetworkManager {
    // 定义一个固定大小为4的线程池
    private ExecutorService executorService = Executors.newFixedThreadPool(4);
    // 定义一个回调接口
    public interface Callback {
        void onSuccess(String response);
        void onFailure(Exception e);
    }
    // 定义一个发起网络请求的方法
    public void request(String url, Callback callback) {
        // 在子线程中执行网络请求
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    // 创建一个HttpURLConnection对象并设置参数
                    HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
                    connection.setRequestMethod("GET");
                    connection.setConnectTimeout(10000);
                    connection.setReadTimeout(10000);
                    // 连接并获取响应码
                    connection.connect();
                    int responseCode = connection.getResponseCode();
                    // 判断响应码是否为200(成功)
                    if (responseCode == 200) {
                        // 获取输入流并转换为字符串
                        InputStream inputStream = connection.getInputStream();
                        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
                        StringBuilder builder = new StringBuilder();
                        String line;
                        while ((line = reader.readLine()) != null) {
                            builder.append(line);
                        }
                        reader.close();
                        inputStream.close();
                        // 回调onSuccess方法并传入响应字符串
callback.onSuccess(builder.toString());
                } else {
                    // 回调onFailure方法并传入异常对象
                    callback.onFailure(new Exception("response code: " + responseCode));
                }
                // 断开连接
                connection.disconnect();
            } catch (Exception e) {
                // 回调onFailure方法并传入异常对象
                callback.onFailure(e);
            }
        }
    });
}
}

在MainActivity.java文件中使用网络请求管理器类

 public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // 创建一个网络请求管理器对象
    NetworkManager networkManager = new NetworkManager();
    // 发起一个网络请求并传入一个回调对象
    networkManager.request("https://example.com/api", new NetworkManager.Callback() {
        @Override
        public void onSuccess(String response) {
            // 在主线程中更新UI或处理数据
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    // 显示响应字符串或解析为JSON对象等操作
                    Toast.makeText(MainActivity.this, response, Toast.LENGTH_SHORT).show();
                }
            });
        }

        @Override
        public void onFailure(Exception e) {
            // 在主线程中显示错误信息或重试等操作
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    // 显示异常信息或提示用户等操作
                    Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
                }
            });
        }
    });
}
}

1.6 使用懒加载或预加载技术

懒加载或预加载技术,指的是根据应用的启动流程和用户的使用习惯,来合理地安排一些数据或资源的加载时机,以提高应用的启动速度和用户体验。懒加载是指在需要使用某些数据或资源时才进行加载,而不是一开始就全部加载。预加载是指在应用启动时或空闲时提前加载一些可能会用到的数据或资源,并缓存起来,以便后续快速使用。

我们可以通过以下几种方式来使用懒加载或预加载技术:

使用懒加载来延迟一些不必要或不紧急的数据或资源的加载,例如广告、推荐、评论等内容。这样可以减少应用启动时的网络请求和内存占用,并提高应用的响应速度。
使用预加载来提前获取一些必要或紧急的数据或资源,例如用户信息、配置文件、常用功能等内容。这样可以避免应用启动时出现空白页面或等待提示,并提高用户的满意度。
使用合理的缓存策略来管理预加载的数据或资源。我们可以根据不同类型和有效期的数据或资源来选择合适的缓存策略,并避免缓存过期、过多或过少。
下面是一个使用预加载技术来获取用户信息和配置文件的示例代码:

// 定义一个全局变量类
public class Global {
    // 定义一个静态变量保存用户信息
    public static User user;
    // 定义一个静态变量保存配置文件
    public static Config config;
}

// 在Application类中实现预加载方法
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        // 预先获取用户信息并保存到全局变量中
        getUserInfo();
        // 预先获取配置文件并保存到全局变量中
        getConfig();
    }

    // 定义一个获取用户信息的方法
    public void getUserInfo() {
        // 模拟从本地数据库或网络接口获取用户信息并赋值给全局变量user
        Global.user = new User("Alice", 18, "female");
    }

    // 定义一个获取配置文件得方法

public void getConfig() {
    // 模拟从本地文件或网络接口获取配置文件并赋值给全局变量config
    Global.config = new Config("dark", "zh", "metric");
}
}

1.7 使用启动优化工具

启动优化工具,指的是一些可以帮助我们分析和优化应用启动性能的工具,例如Android Studio自带的Profiler、Systrace、App Startup等工具。这些工具可以让我们更清晰地了解应用启动过程中发生了什么,哪些操作占用了多少时间和资源,哪些操作可以被优化或删除等。
我们可以通过以下几种方式来使用启动优化工具:

1.使用Profiler来监控应用启动时的CPU、内存、网络等资源的使用情况,并找出可能存在的性能瓶颈或内存泄漏等问题。
2.使用Systrace来跟踪应用启动时的系统事件和方法调用,并生成一个可视化的时间轴图表,以便分析应用启动流程和耗时。

下面是一个使用Systrace来跟踪应用启动性能的示例步骤:

  • 在Android Studio中打开命令行终端,并输入以下命令:
adb shell atrace --async_start -a com.example.myapp -b 4096
  • 这个命令会在设备上开始异步地记录com.example.myapp这个包名对应的应用的系统事件和方法调用,并设置缓冲区大小为4096KB。
  • 然后在设备上手动启动这个应用,并等待它完全加载完成。
  • 再回到命令行终端,并输入以下命令:
adb shell atrace --async_stop -o trace.txt 
  • 这个命令会在设备上停止记录并输出一个名为trace.txt的文件到当前目录中。 - 最后,在Android Studio中打开Profiler窗口,并点击左上角的File按钮,然后选择Import Trace File...选项,导入刚才生成的trace.txt文件。
  • 这样就可以在Profiler窗口中看到一个类似于下图所示的时间轴图表,展示了应用启动过程中各种事件和方法调用所占用的时间和线程。我们可以通过放大缩小、拖拽、点击等操作来查看和分析这个图表,并找出可能存在的性能问题或优化空间。
3.使用App Startup来管理应用启动时初始化的组件,并避免不必要或过早地初始化一些组件。

1.8 使用多进程或多线程技术

多进程或多线程技术,指的是一些可以让我们在应用启动时并行地执行一些任务的技术,例如使用Service、IntentService、AsyncTask、Thread等类来创建和管理进程或线程。这些技术可以让我们充分利用设备的多核CPU和内存资源,并提高应用启动的效率和速度。

我们可以通过以下几种方式来使用多进程或多线程技术:

使用Service或IntentService来在后台进程中执行一些不需要与用户交互的任务,例如检查更新、同步数据、上传日志等操作。这样可以避免阻塞主进程和主线程,并提高应用的响应速度。
使用AsyncTask或Thread来在子线程中执行一些需要与用户交互但耗时较长的任务,例如网络请求、数据库操作、图片加载等操作。这样可以避免卡顿主线程并造成ANR(Application Not Responding)错误,并提高用户的满意度。
使用合理的进程或线程优先级和调度策略来管理多个任务之间的执行顺序和资源分配。我们可以根据不同类型和重要性的任务来选择合适的优先级和调度策略,并避免出现过度竞争或饥饿现象。
下面是一个使用AsyncTask来在子线程中执行网络请求并更新UI的示例代码:

public class MainActivity extends AppCompatActivity {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textView);
        // 创建一个子线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 在子线程中执行网络请求
                String result = getHttpResult("https://www.bing.com");
                // 在主线程中更新UI
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText(result);
                    }
                });
            }
        }).start();
    }

    // 模拟一个网络请求方法,返回网页内容
    private String getHttpResult(String url) {
        try {
            URL u = new URL(url);
            HttpURLConnection conn = (HttpURLConnection) u.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(5000);
            if (conn.getResponseCode() == 200) {
                InputStream is = conn.getInputStream();
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte[] buffer = new byte[1024];
                int len;
                while ((len = is.read(buffer)) != -1) {
                    baos.write(buffer, 0, len);
                }
                is.close();
                baos.close();
                return baos.toString();
            } else {
                return "Error: " + conn.getResponseCode();
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "Exception: " + e.getMessage();
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,667评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,361评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,700评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,027评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,988评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,230评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,705评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,366评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,496评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,405评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,453评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,725评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,803评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,015评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,514评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,111评论 2 341

推荐阅读更多精彩内容