如何应对 Android 面试官 -> 启动如何进行优化(下)?玩转 Android StartUp

Android Startup实现分析 前言 Android Startup提供一种在应用启动时能够更加简单、高效的方式来初始化组件。开发人员可以使用Android Startup来简化启动序列,并显式地设置初始化顺序与组件之间的依赖关系。 与此同时,Android Startup支持同步与异步等待、手动控制依赖执行时机,并通过有向无环图拓扑排序的方式来保证内部依赖组件的初始化顺序。 Android Startup经过几轮的迭代已经更加完善了,支持的功能场景也更加多样,如果你要使用Android Startup的新特性,请将依赖升级到最新版本l. 阅读详情

前言


在这里插入图片描述

本章继续上一章的启动优化讲解,主要基于手淘全链路性能优化分析 Android StartUp 启动框架;

有向无环图(DAG)算法


我们首先去 LeetCode 上看一道算法题:课程表

在这里插入图片描述
看题目,可能好多算法薄弱的人有点懵逼,转换一下思路,例如:如果你想学习 OKHttp,那么你需要一些其他的知识来辅助你学习 OKhttp
在这里插入图片描述

那么你需要学习 java 只是,学习 Socket、Https 和设计模式,然后学习了这些知识之后,来辅助你学习 OKHttp,这样的一个依赖关系;

转换到我们进行启动优化上,我们在 Application 中初始化三方 SDK 的时候,就要考虑相互之间的依赖关系,而不是一股脑的所有 SDK 都放到子线程或者延迟初始化;

这种有依赖,有方向,没有形成回环的结构就是『有向无环图』简称 DAG;

在上面那张依赖图中,剪头我们通常叫做『边』,Socket 有一个箭头指向它,那么它的入度就是 1,OKHttp 有两个箭头指向它,那么它的入度就是 2,倒过来讲就是出度,Java 的出度是 2, OKHttp 的出度是 0;

那么,我们接下来如何通过代码来实现这个『有向无环图』我们可以借助 BFS 或者 DFS,对这两个感兴趣的可以看下这个链接:图文详解 BFS DFS

拓扑排序

拓扑排序是对一个有向图构造拓扑序列的过程;图的拓扑排序不是唯一的;实现拓扑排序的整体思路是:

  • 找出图中 0 入度的顶点;
  • 依次在图中删除这些顶点,删除后再找出图中 0 入度的顶点;
  • 然后在删除…再找出…;
  • 直至删除所有顶点,即完成拓扑排序;
    在这里插入图片描述
    在这里插入图片描述

Android StartUp 具体实现


首先我们来定一个 StartUp 接口,这个接口用来创建当前任务,获取当前任务依赖了哪些任务,以及当前任务依赖了多少个任务

任务的排序

public interface Startup<T> {

    /**
     * 创建当前任务
     * @param context context
     * @return T 任务执行结果,返回值类型
     */
    T create(Context context);

    /**
     * 当前任务依赖了哪些任务,也就是入度数
     *
     * @return List<Class<? extends StartUp<?>>>
     */
    List<Class<? extends Startup<?>>> dependencies();

    /**
     * 当前任务依赖了多少个任务
     * @return int
     */
    int getDependenciesCount();
}

然后定义一个抽象类 AndroidStartup 实现 Startup 接口

public abstract class AndroidStartup<T> implements Startup<T> {
    
    /**
     * 获取依赖的任务
     * @return 依赖的任务
     */
    @Override
    public List<Class<? extends Startup<?>>> dependencies() {
        return null;
    }

    /**
     * 获取依赖的任务数
     * @return int
     */
    @Override
    public int getDependenciesCount() {
        List<Class<? extends Startup<?>>> dependencies = dependencies();
        return dependencies == null ? 0 : dependencies.size();
    }
}

这个抽象类中只实现了 dependencies 和 getDependenciesCount 接口;

然后定义了 5 个 Task;
在这里插入图片描述

CommonParamsTaskStartup 依赖 PushTaskStartup;

CuidTaskStartup 依赖 PushTaskStartup;

DeviceIdTaskStartup 依赖 CommonParamsTaskStartup

ImTaskStartup 依赖 DeviceIdTaskStartup、CuidTaskStartup

PushTaskStartup 没有任何依赖;

所以最终的有向无环图如下:
在这里插入图片描述

那么,具体是怎么实现这个拓扑排序的呢?我们来看下排序规则:

/**
 * 拓扑排序
 * @param startupList startupList
 * @return StartupSortStore
 */
public static StartupSortStore sort(List<? extends Startup<?>> startupList) {

    // 将所有的任务存入到一个map中
    Map<Class<? extends Startup>, Startup<?>> startupMap = new HashMap<>();

    // 入读表,记录每个任务的依赖数
    Map<Class<? extends Startup>, Integer> inDegreeMap = new HashMap<>();
    // 记录每个任务的依赖任务
    Map<Class<? extends Startup>, List<Class<? extends Startup>>> startupChildrenMap = new HashMap<>();
    // 记录入度为 0 的任务
    Deque<Class<? extends Startup>> zeroDegree = new ArrayDeque<>();
    // 遍历所有的任务
    for (Startup<?> startup : startupList) {
        startupMap.put(startup.getClass(), startup);
        // 获取每个任务的依赖数
        int dependenciesCount = startup.getDependenciesCount();
        // 存入入度表中
        inDegreeMap.put(startup.getClass(), dependenciesCount);

        if (dependenciesCount == 0) {
            // 如果依赖的任务数为0,则存入0入度表
            zeroDegree.offer(startup.getClass());
        } else {
            // 获取每个任务依赖的任务,一个任务可以依赖多个任务,所以是一个集合
            // 例如获取 ImTaskStartup 的 dependencies,包含 DeviceIdTaskStartup CuidTaskStartup
            List<Class<? extends Startup<?>>> dependencies = startup.dependencies();
            // 遍历依赖的任务
            for (Class<? extends Startup<?>> parent : dependencies) {
                // parent = DeviceIdTaskStartup | parent = CuidTaskStartup
                List<Class<? extends Startup>> children = startupChildrenMap.get(parent);
                if (children == null) {
                    children = new ArrayList<>();
                    startupChildrenMap.put(parent, children);
                }
                // 也就是说 DeviceIdTaskStartup 的 child 是 ImTaskStartup
                children.add(startup.getClass());
            }
        }
    }
    // 依次
    List<Startup<?>> result = new ArrayList<>();
    // 如果 0 入度表不为空
    while (!zeroDegree.isEmpty()) {
        Class<? extends Startup> cls = zeroDegree.poll();
        Startup<?> startup = startupMap.get(cls);
        result.add(startup)
        //
        if (startupChildrenMap.containsKey(cls)) {
            List<Class<? extends Startup>> childStartup = startupChildrenMap.get(cls);
            for (Class<? extends Startup> childCls : childStartup) {
                Integer integer = inDegreeMap.get(childCls);
                inDegreeMap.put(cls, integer - 1);
                if (integer - 1 == 0) {
                    zeroDegree.offer(childCls);
                }
            }
        }
    }

    StartupSortStore startupSortStore = new StartupSortStore();
    startupSortStore.setResult(result);
    startupSortStore.setStartupMap(startupMap);
    startupSortStore.setStartupChildrenMap(startupChildrenMap);
    return startupSortStore;
}

任务的执行

任务排序排好之后,如何按顺序的执行这些任务呢?我们来定义两个任务执行管理类;

public class StartupCacheManager {

    /**
     * 执行结束的任务集合
     */
    private final ConcurrentHashMap<Class<? extends Startup>, Result> mInitializedComponents = new ConcurrentHashMap<>();
    /**
     * 单例
     */
    private static StartupCacheManager mInstance;

    /**
     * 私有构造
     */
    private StartupCacheManager() {}

    /**
     * 对外暴漏 StartupCacheManager
     * @return StartupCacheManager
     */
    public static StartupCacheManager getInstance() {
        if (mInstance == null) {
            synchronized (StartupCacheManager.class) {
                if (mInstance == null) {
                    mInstance = new StartupCacheManager();
                }
            }
        }
        return mInstance;
    }

    /**
     * save result of initialized component.
     * @param zClass zClass
     * @param result result
     */
    public void saveInitializedComponent(Class<? extends Startup> zClass, Result result) {
        mInitializedComponents.put(zClass, result);
    }

    /**
     * check initialized.
     * @param zClass zClass
     * @return true
     */
    public boolean hadInitialized(Class<? extends Startup> zClass) {
        return mInitializedComponents.containsKey(zClass);
    }

    /**
     * 获取 result
     * @param zClass zClass
     * @param <T> T
     * @return Result<T>
     */
    public <T> Result<T> obtainInitializedResult(Class<? extends Startup<T>> zClass) {
        return mInitializedComponents.get(zClass);
    }


    /**
     * 移除 result
     * @param zClass zClass
     */
    public void remove(Class<? extends Startup> zClass) {
        mInitializedComponents.remove(zClass);
    }

    /**
     * 清空 result
     */
    public void clear() {
        mInitializedComponents.clear();
    }
}

定义一个单例类 StartupCacheManager 用来缓存每一个任务的执行结果;例如,任务2 依赖任务1 的执行结果才能执行自己的任务,那么就需要缓存当前任务的执行结果;

public class StartupManager {

    /**
     * context
     */
    private final Context mContext;
    /**
     * 任务结合
     */
    private final List<AndroidStartup<?>> mStartupList;
    /**
     * 任务执行结果
     */
    private StartupSortStore mStartupSortStore;

    /**
     * 构造
     * @param mContext mContext
     * @param mStartupList mStartupList
     */
    public StartupManager(Context mContext, List<AndroidStartup<?>> mStartupList) {
        this.mContext = mContext;
        this.mStartupList = mStartupList;
    }

    /**
     * 启动任务,主线程调用
     * @return StartupManager
     */
    public StartupManager start() {
        if (Looper.myLooper() != Looper.getMainLooper()) {
            throw new RuntimeException("please start task on main thread");
        }
        // 执行拓扑排序,核心
        mStartupSortStore = TopologySort.sort(mStartupList);
        for (Startup<?> startup : mStartupSortStore.getResult()) {
            Object result = startup.create(mContext);
            StartupCacheManager.getInstance().saveInitializedComponent(startup.getClass(), new Result(result));
        }
        return this;
    }

    public static class Builder {
        private final List<AndroidStartup<?>> startupList = new ArrayList<>();

        public Builder addTask(AndroidStartup<?> task) {
            startupList.add(task);
            return this;
        }

        public Builder addAllStartup(List<Startup<?>> startups) {
            startupList.addAll(startups);
            return this;
        }

        public StartupManager build(Context context) {
            return new StartupManager(context, startupList);
        }
    }

}

线程管理

我们要想完成任务的启动,本质上还是要合理的运用 CPU,那么就有必要把一些任务的启动放到子线程中并行执行;

例如:我们需要把前面定义的 5 个 Task 都要放到子线程中去执行,那么应该怎么实现?

又或者:我们在主线程中启动了 100 个子线程,这些子线程执行完毕之后,主线程才能打印完成,那么应该怎么实现呢?

我们可以借助 join、wait/notify、juc 工具包下的类等等;

但是,假设:A、B 两个线程,A 线程分别执行第一步、第二步、第三步,当 A 线程执行到第二步的时候,执行 B 线程,如何实现?

答案:join 不可以实现,使用 wait 和 notify,但是要 B 线程先 start;

假设:A、B、C 三个线程,A、B 线程执行三步,当 A、B 线程执行完第二步的时候,执行 C 线程,如何实现?

答案:A 、B 两个线程,在执行完第二步之后,调用 CountDownLatch 的 countdown 方法,C 线程调用CountDownLatch 的 await 方法等待被唤醒;

在这里插入图片描述

那么我们如何处理任务之间的同步?

在这里插入图片描述

那么 CountDowmLatch 如何应用到任务中呢?

public interface Dispatcher {

    /**
     * 当前任务是否在主线程执行
     * @return true 主线程调用
     */
    boolean callCreateOnMainThread();

    /**
     * 主线程是否需要等待该任务执行结束
     * @return true 等待主线程结束
     */
    boolean waitOnMainThread();

    /**
     * 等待
     */
    void toWait();

    /**
     * 唤醒
     */
    void toNotify();

    /**
     * 获取当前任务被执行的时候 需要的线程池
     * @return Executor
     */
    Executor executor();

    /**
     * 线程优先级
     * @return int
     */
    int getThreadPriority();

}

然后我们的 Startup 继承这个接口,这样我们的任务就默认是 Dispatch 的实现类了;callCreateOnMainThread 表示当前任务是否在主线程执行,false 则在子线程中执行;定义了 toWait 和 toNotify 方法,这两个方法我们进入 AndroidStartup 中看下:

public abstract class AndroidStartup<T> implements Startup<T> {

    /**
     * 执行线程调度,给每个任务都创建一个闭锁,对应的状态码是依赖的任务数
     *
     * 如果状态码是0,那么调用 await 的时候,并不会被阻塞
     */
    private final CountDownLatch mWaitDownLatch = new CountDownLatch(getDependenciesCount());
    
    
    /**
     * 唤醒
     */
    @Override
    public void toNotify() {
        mWaitDownLatch.countDown();
    }

    /**
     * 等待
     */
    @Override
    public void toWait() {
        try {
            mWaitDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

通过 CountDownLatch 来控制任务的 wait 和 notify;

然后,我们在任务启动的地方,改造如下:

public class StartupManager {
    
    
    /**
     * 启动任务,主线程调用
     * @return StartupManager
     */
    public StartupManager start() {
        if (Looper.myLooper() != Looper.getMainLooper()) {
            throw new RuntimeException("please start task on main thread");
        }
        // 执行拓扑排序,核心
        mStartupSortStore = TopologySort.sort(mStartupList);
        for (Startup<?> startup : mStartupSortStore.getResult()) {
            StartupRunnable startupRunnable = new StartupRunnable(this, startup, mContext);
            // 定义了一个 StartupRunnable 将所有的任务都放到 runnable 中,如果是在主线程执行,则直接 run,否则加入到线程池中进行分发;
            if (startup.callCreateOnMainThread()) {
                startupRunnable.run();
            } else {
                startup.executor().execute(startupRunnable);
            }
        }
        return this;
    }
    
    
    /**
     * 通知依赖任务 notify
     * @param mStartup mStartup
     */
    public void notifyChildren(Startup<?> mStartup) {
        if (!mStartup.callCreateOnMainThread() && mStartup.waitOnMainThread()) {
            mCountDownLatch.countDown();
        }
        if (mStartupSortStore.getStartupChildrenMap().containsKey(mStartup.getClass())) {
            // 获取到依赖的任务
            List<Class<? extends Startup>> childStartupCls = mStartupSortStore.getStartupChildrenMap().get(mStartup.getClass());
            for (Class<? extends Startup> childStartupCl : childStartupCls) {
                // 通知子任务,父任务已经完成
                Startup<?> startup = mStartupSortStore.getStartupMap().get(childStartupCl);
                startup.toNotify();
            }
        }
    }
}

StartupRunnable 定义如下:

public class StartupRunnable implements Runnable {

    /**
     * StartupManager
     */
    private final StartupManager mStartupManager;
    /**
     * Startup
     */
    private final Startup<?> mStartup;
    /**
     * Context
     */
    private final Context mContext;

    /**
     * 构造任务启动器
     * @param mStartupManager mStartupManager
     * @param mStartup mStartup
     * @param mContext mContext
     */
    public StartupRunnable(StartupManager mStartupManager, Startup<?> mStartup, Context mContext) {
        this.mStartupManager = mStartupManager;
        this.mStartup = mStartup;
        this.mContext = mContext;
    }

    /**
     * 执行任务
     */
    @Override
    public void run() {
        Process.setThreadPriority(mStartup.getThreadPriority());
        // 所有任务执行 run 的时候都先 toWait 下,等待被唤醒,countDown 为 0 的则不会被 wait 而是直接执行;
        mStartup.toWait();
        Object result = mStartup.create(mContext);
        StartupCacheManager.getInstance().saveInitializedComponent(mStartup.getClass(), new Result(result));
        mStartupManager.notifyChildren(mStartup);
    }
}

如何处理主线程需要等待子线程执行完毕之后才能执行

如果任务5是在子线程,主线程需要等待任务5执行完成,主线程才能继续执行,应该怎么处理?
在这里插入图片描述
我们依然是借助于 juc 包下的工具类,StartupManager 中的 Builder 的 build 方法改造如下;

public StartupManager build(Context context) {
    AtomicInteger atomicInteger = new AtomicInteger();
    for (AndroidStartup<?> startup : startupList) {
        // 当前任务在线程调用,并且需要主线程等待其执行结束
        if (!startup.callCreateOnMainThread() && startup.waitOnMainThread()) {
            atomicInteger.decrementAndGet();
        }
    }
    CountDownLatch countDownLatch = new CountDownLatch(atomicInteger.get());
    return new StartupManager(context, startupList, countDownLatch);
}

这里我们就用到了 waitOnMainThread 方法,需要主线程等待当前任务执行完成才能继续往下执行;也就说这个方法只有在 callCreateOnMainThread 返回 false(也就是当前任务是在子线程执行)的时候,才有意义;

然后 StartupManager 中新增一个 await 方法;

/**
 * 等待任务 await
 */
public void await() {
    try {
        mCountDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

也就说,如果需要等待子线程任务初始化完成,那么就调用一下这个方法;

new StartupManager.Builder()
        .addStartup(CommonParamsTaskStartup)
        .addStartup(CuidTaskStartup)
        .addStartup(DeviceIdTaskStartup)
        .addStartup(ImTaskStartup)
        .addStartup(PushTaskStartup)
        .build(getContext())
        .start()
        .await();

然后 notifyChild 方法中,countDown 一下

public void notifyChildren(Startup<?> mStartup) {
    if (!mStartup.callCreateOnMainThread() && mStartup.waitOnMainThread()) {
        mCountDownLatch.countDown();
    }
    ....
}

大批量任务注册实现

当我们的程序中有很多很多的任务需要 addStartup 的时候,我们如果在 Application 中新增一个就要添加一个的话就比较繁琐,那么如何简化它的任务注册呢?能不能实现自动注册呢?

ContentProvider

这里我们可以借助 ContentProvider 来实现(思想来源于:LeakCanary),这也就是为什么 LeakCanary 不需要在 Application 中初始化依然可以使用的原因;

我们来增加一个 ContentProvider

public class StartupProvider extends ContentProvider {

    @Override
    public boolean onCreate() {
        List<Startup<?>> startups = StartupInitializer.discoverAndInitializer(this.getContext(), getClass().getName());
        new StartupManager.Builder()
                .addAllStartup(startups)
                .build(getContext())
                .start()
                .await();
        return true;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
}

以及 AndroidManifest 中进行注册(按需注册)

<provider
    android:authorities="${applicationId}.android_startup"
    android:name="com.example.startup.provider.StartupProvider"
    android:exported="false">
    <!-- 强制需要指定 meta-data 用来指定一个任务-->
    <meta-data
        android:name="com.example.startup.task.CommonParamsTaskStartup"
        android:value="android.startup" />
</provider>

然后解析 meta-data 获取指定的任务,然后就可以获取它依赖的任务,以及它依赖的任务依赖的其他任务;

public class StartupInitializer {

    public static String META_VALUE = "android.startup";

    public static List<Startup<?>> discoverAndInitializer(Context context,
                                                          String providerName) {
        try {
            Map<Class<? extends Startup>, Startup<?>> startups = new HashMap<>();
            //获得manifest contentProvider中的meta-data
            ComponentName provider = new ComponentName(context, providerName);
            ProviderInfo providerInfo = context.getPackageManager().getProviderInfo(provider, PackageManager.GET_META_DATA);
            for (String key : providerInfo.metaData.keySet()) {
                String value = providerInfo.metaData.getString(key);
                if (TextUtils.equals(META_VALUE, value)) {
                    Class<?> clazz = Class.forName(key);
                    if (Startup.class.isAssignableFrom(clazz)) {
                        doInitialize((Startup<?>) clazz.newInstance(), startups);
                    }
                }
            }
            List<Startup<?>> result = new ArrayList<>(startups.values());
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static void doInitialize(Startup<?> startup,
                                     Map<Class<? extends Startup>, Startup<?>> startups) throws Exception {
        //避免重复 不能使用List
        startups.put(startup.getClass(), startup);
        if (startup.getDependenciesCount() != 0) {
            //遍历父任务
            for (Class<? extends Startup<?>> dependency : startup.dependencies()) {
                doInitialize(dependency.newInstance(), startups);
            }
        }
    }

}

AndroidManifest 的 provider 中的 meta-data 必须指定最后执行的那个任务,这样才能向前找到所要依赖的任务;

拓扑优化

思考:同步任务阻塞异步任务怎么办?
在这里插入图片描述
我们来优化下排序算法:

public static StartupSortStore sort(List<? extends Startup<?>> startupList) {
    ```
    // 依次
    List<Startup<?>> result = new ArrayList<>();
    // 增加 主线程集合
    List<Startup<?>> main = new ArrayList<>();
    // 增加 子线程集合
    List<Startup<?>> threads = new ArrayList<>();
    // 如果 0 入度表不为空
    while (!zeroDegree.isEmpty()) {
        Class<? extends Startup> cls = zeroDegree.poll();
        Startup<?> startup = startupMap.get(cls);
        // 分别加入不到不同的集合中
        if (startup.callCreateOnMainThread()) {
            main.add(startup);
        } else {
            threads.add(startup);
        }
        //
        if (startupChildrenMap.containsKey(cls)) {
            List<Class<? extends Startup>> childStartup = startupChildrenMap.get(cls);
            for (Class<? extends Startup> childCls : childStartup) {
                Integer integer = inDegreeMap.get(childCls);
                inDegreeMap.put(cls, integer - 1);
                if (integer - 1 == 0) {
                    zeroDegree.offer(childCls);
                }
            }
        }
    }
    // 先添加子线程
    result.addAll(threads);
    // 再添加主线程
    result.addAll(main);

    StartupSortStore startupSortStore = new StartupSortStore();
    startupSortStore.setResult(result);
    startupSortStore.setStartupMap(startupMap);
    startupSortStore.setStartupChildrenMap(startupChildrenMap);
    return startupSortStore;
}

这样我们最终的排序就是 1、3、4、5 2

1 -> 子线程执行,直接执行
3 -> 子线程执行,等待 1 任务
4 -> 子线程执行,等待 2 任务
5 -> 子线程执行,等待 3、4 任务

2 -> 主线程执行,等待 1 任务

因为 result 先添加的 threads 任务,再添加的 main 任务

好了,启动优化就写到这里吧;

下一章预告


继续性能优化~

欢迎三连


来都来了,点个关注,点个赞吧,你的支持是我最大的动力~

Android 启动优化: JetPack App Startup 使用及源码浅析 (包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。 阅读详情

相关推荐

Android性能优化面试指南:从UI卡顿、内存泄漏到启动速度的实战解析

在移动应用开发中,性能优化是衡量工程师技术深度的关键标尺,它直接决定了应用的用户体验和市场竞争力。其核心原理在于深入理解系统底层机制,如UI渲染流程、内存管理(垃圾回收GC)和线程调度,通过科学的方法论定位并解决性能瓶颈。这项技术的价值在于,它能将应用从“能用”提升到“好用”的层次,减少卡顿、崩溃和耗电,从而提升用户留存和产品口碑。典型的应用场景包括解决列表滑动卡顿、防治内存泄漏导致的OOM、以及优化应用冷启动速度等。本文聚焦Android性能优化面试,系统梳理了UI渲染、内存管理及启动速度等核心维度的考察

weixin_30787531的博客 552

StartUp启动框架-Android启动性能

在 StartUp 框架中,你可以通过设置任务的优先级和管理它们之间的依赖关系来控制任务的执行顺序。为了测量每个初始化任务的执行时间,你可以在每个任务的 create 方法中记录任务开始和结束的时间戳,然后计算时间差。通过使用StartUp框架,你可以更好地管理和优化应用程序的初始化过程,确保关键任务的及时执行,同时延迟初始化不必要的操作,从而提高应用的启动性能。通过使用 StartUp 框架来测量应用的启动性能,你可以更好地理解每个初始化任务的性能影响,从而有针对性地进行优化,提升应用的启动速度。

鸿蒙的技术博客 2982

Android开发:ANR异常与性能优化实战指南

在移动开发领域,ANR(Application Not Responding)是Android系统中常见的性能瓶颈指标,其本质是主线程因阻塞导致的消息队列处理超时。理解其底层机制需要掌握Handler/Looper的消息循环原理,以及ActivityManagerService的监控体系。从技术价值看,ANR优化直接关系到用户体验评分和留存率,在电商、社交等高并发场景尤为关键。通过协程异步处理、布局层级简化等工程实践,可有效降低ANR率。类似LeakCanary等工具链的应用,则能系统性解决内存泄漏引发的O

weixin_34221036的博客 562

【Android】之 App Startup 的使用及浅析

App Startup 是一个可以用于加速 App 启动速度的一个库。平时开发中,大部分的第三方都需要在 Application 中进行初始化,但是库会借助 ContentProvider 来初始化,以便使自己的库的侵入性更低,使用更加简便。但是,一旦项目引入了使用 ContentProvider 进行初始化的库越来越多,那么应用的启动速度将会越来越慢,因为每个 ContentProvider 的初始化都是比较耗时的操作。而 App Startup,它可以将所有用于初始化的 ContentProvider

yang553566463的博客 3259

Android高级工程师面试核心考点与实战解析

Android系统开发中,Framework层原理与性能优化是构建高质量应用的技术基石。Binder机制通过内存映射实现高效跨进程通信,其设计融合了Linux内核的进程隔离与mmap技术,成为Android系统级通信的核心方案。在性能优化领域,内存泄漏检测、ANR分析等工具链的深度使用,直接影响应用流畅度与稳定性。这些技术广泛应用于电商、社交等高频场景,解决诸如界面卡顿、冷启动耗时等工程难题。本文以高级工程师面试为切入点,深入解析Handler消息机制、Flutter渲染管线等热点技术,帮助开发者系统掌握从

circularr9834的博客 402

Android高级工程师面试核心要点与系统设计解析

在移动开发领域,Android系统架构设计与性能优化是衡量工程师技术水平的关键指标。系统架构设计涉及模块化、组件化等核心概念,通过合理的架构分层和通信机制设计,可以显著提升应用的可维护性和扩展性。性能优化则聚焦内存管理、启动速度等关键技术点,利用工具链分析和解决性能瓶颈。这些技术不仅关系到应用流畅度,更直接影响用户体验和业务指标。高级工程师需要深入理解Binder机制、Handler原理等Android框架层实现,同时掌握现代开发技术栈如Kotlin协程、Jetpack组件。在工程实践中,架构决策需要平衡技

weixin_30791095的博客 386

探索高效 Android 应用启动:AndroidStartup 项目详解

探索高效 Android 应用启动:AndroidStartup 项目详解 在移动应用开发的世界中,快速启动时间是提升用户体验的关键因素之一。 是一个专门为 Android 开发者打造的库,它旨在帮助开发者实现更加优化的应用启动流程。本文将深入探讨该项目的技术原理、应用场景和主要特点,以期让更多开发者了解并利用这一工具。 项目简介 AndroidStartup 是一个基于依赖注入框架(Dagger...

gitblog_00094的博客 820

2025年Android开发者面试核心考点与趋势解析

在移动开发领域,JVM内存模型和并发编程是构建高性能应用的基础。新一代ZGC垃圾收集器通过改进内存管理策略显著提升性能,而合理的线程池配置能有效解决多线程环境下的资源竞争问题。Kotlin协程作为现代异步编程方案,其挂起机制和Flow背压处理在复杂业务场景中展现出独特优势。随着Flutter在跨平台开发中的普及,MethodChannel与EventChannel等通信机制的性能优化成为技术选型关键。对于Android开发者而言,深入理解MVI架构与Jetpack Compose的协作原理,以及掌握KMM跨

weixin_33937778的博客 398

如何应对 Android 面试官 -> 启动如何进行优化(上)?玩转 Android StartUp

本章主要围绕 App 的启动流程如何优化进行讲解;将启动优化,首先要了解的就是 app 的启动流程,只有清晰并完善的了解了 启动流程 才能更好的进行优化;

IT_Android的博客 1547

从收音机到Arduino:手把手教你用BJT三极管搭建一个简易音频放大器(附电路图与元件清单)

本文详细介绍了如何使用BJT三极管搭建简易音频放大器,涵盖电路原理、元件选择、设计步骤及调试技巧。通过实践项目帮助电子爱好者深入理解BJT的放大特性,并提供与Arduino结合的扩展应用方案,适合创客和DIY玩家。

aitie1479的博客 437

实测对比:DECIMER、Img2Mol、MolScribe,哪个化学结构识别工具更靠谱?

化学结构识别(CSR)工具的核心任务是将分子结构图像转换为SMILES(Simplified Molecular Input Line Entry System)表示。这一过程需要克服图像质量差异、绘制风格变化以及特殊基团识别等挑战。三款工具采用不同的技术路线:DECIMER:基于EfficientNetV2+Transformer架构,优势在于处理标准期刊文献中的分子图示Img2Mol。

weixin_32576389的博客 342

Android性能优化与异常处理实战指南

在移动开发领域,性能优化与异常处理是衡量工程师能力的重要指标。从技术原理来看,Android系统通过ActivityManagerService等核心服务监控应用运行状态,当主线程阻塞或内存管理不当时会触发ANR或内存泄漏。这些机制涉及Binder通信、Handler消息循环等底层技术,理解其工作原理对问题排查至关重要。工程实践中,开发者需要掌握协程调度、弱引用等编程范式,并熟练使用Android Profiler、LeakCanary等工具链。典型的应用场景包括列表流畅度优化、冷启动加速等,这些优化能显著

weixin_30613343的博客 395

新建菜单Office被替换为腾讯文档 - 解决方案

右键新建菜单中的Office被替换为腾讯文档。原因是安装了腾讯文档软件,此软件安装时会自动将新建菜单中的Office替换为腾讯文档,下文介绍解决方案。

NSJim的博客 2927

Android面试真题解析与核心技术剖析

Android开发面试中,Java并发编程和Binder机制是核心考察点。Java并发涉及线程同步、锁优化等基础概念,而Binder作为Android特有的进程通信框架,其架构设计与性能优势直接影响系统稳定性。掌握这些原理不仅能应对面试,更能指导工程实践,如优化跨进程调用性能、解决内存泄漏等问题。在Framework层,Activity启动流程和View绘制体系是理解Android运行机制的关键,结合Jetpack组件和Kotlin协程等新兴技术,可以构建更高效的应用程序。本文通过百度Android面试真题

429

从硬件异常到代码救赎:RH850内存访问错误(MAE)的深层诊断与艺术

本文深入探讨瑞萨RH850微控制器的内存访问错误(MAE)诊断与解决方法。针对程序跑飞和Flash Driver交互中的潜在陷阱,提供系统性硬件异常分析框架和防御性编程实践,帮助嵌入式开发者从底层机制到代码层面有效预防和修复MAE问题,提升系统稳定性。

pytorch8learner的博客 195

视觉语言模型对抗攻击与防御:从传统方法到多模态挑战

本文深入探讨了视觉语言模型(VLM)面临的对抗攻击与防御挑战,从传统单模态攻击到多模态威胁的演进。分析了包括FGSM、PGD等经典攻击方法,以及新兴的多模态攻击如跨模态对抗样本和越狱攻击。同时提出了跨模态一致性检查、动态防御机制等创新防御策略,并结合医疗影像诊断等实战案例,为提升AI系统安全性提供了重要见解。

weixin_29231725的博客 392

android16进制转10进制!最全面试考点与面试技巧,聪明人已经收藏了!

前言 本文将带领大家来看看启动优化相关方面的介绍以及各种优化的方法。希望你在读完本章后会有所收获。 相信很多同学都听过八秒定律,八秒定律是在互联网领域存在的一个定律,即指用户访问一个网站时,如果等待网页打开的时间超过了8秒,就有超过70%的用户放弃等待。足见启动的时间是多么的重要。放到移动APP中,那就是应用启动的时间不能太久,否则就会造成用户的流失。 谷歌官方曾给出一篇App startup time的文章,这篇文章详细介绍了关于启动优化的切入点以及思路。感兴趣的同学可以去看下。App Startup

m0_52308677的博客 345

DELL Precision T7920加装显卡遇到的坑

文章目录DELL Precision T7920加装显卡服务器原有配置介绍选购过程确认型号确认PCIE x16插槽确认电源功率和电源线安装过程电源模组线不够多单装一个3090显卡尺寸问题加装上1080Ti非常重要!官方文档 DELL Precision T7920加装显卡 实验室由于显卡计算能力不够,以及显卡显存不够,请老师花了大价钱购入了两张GeForce 3090显卡。老师赛高!记录一下这次加装显卡过程中学到的一些知识。 服务器原有配置介绍 服务器原有一张1080Ti显卡,一个16核Xeon CPU。

weixin_45520982的博客 1万+

大厂Android面试核心考点与实战解析

Android系统架构作为移动开发的基石,其核心机制如Binder通信、事件分发、内存管理等原理直接影响应用性能。理解这些底层原理不仅能解决UI卡顿、内存泄漏等常见问题,更能为组件化架构设计打下基础。在工程实践中,Jetpack组件和Kotlin协程等新技术可显著提升开发效率,而启动优化、内存优化等技巧则是大厂面试的高频考点。以事件分发机制为例,通过分析ViewGroup的onInterceptTouchEvent和View的onTouchEvent源码,开发者能掌握手势冲突的解决方案。这些知识在腾讯、字节

weixin_34082854的博客 470

SAP 批次管理(分割评估)

批次分割评估: 业务场景:企业要求各批次物料成本价格不一样。此时可以采用分割批次来实现不同成本 后台配置: (1)、激活分割评估 后台---物料管理----评估与科目设置---分割评估-----激活分割评估 (2)、配置分割评估 后台---物料管理----评估与科目设置---分割评估-----配置分割评估 进入全局性分类后,批次分割采用已有的评估类型“X自动(批)”: ...

willieyuan的博客 1万+

线程池详解(通俗易懂超级好)

目标 【理解】线程池基本概念 【理解】线程池工作原理 【掌握】自定义线程池 【应用】java内置线程池 【应用】使用java内置线程池完成综合案例 线程池 线程池基础 线程池使用 线程池综合案例 4.学员练习 5.线程池总结 概念介绍 1:什么是线程池 2:为什么使用线程池 3:线程池有哪些优势 什么是池 什么是线程池 线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runna.

拉格朗日的学习笔记 12万+

Android高级面试核心考点与实战技巧

在移动开发领域,Android系统架构与性能优化是开发者必须掌握的核心技术。理解Binder跨进程通信机制需要深入Linux内核,掌握内存映射与线程调度原理,这对实现高性能应用至关重要。Kotlin协程作为现代异步编程方案,其挂起函数通过状态机实现线程切换,与Java线程模型形成互补。在工程实践中,内存泄漏检测需结合MAT工具分析对象引用链,而冷启动优化则涉及类加载与GC策略调优。本指南针对大厂P7级Android岗位要求,重点解析Framework底层原理、协程并发处理等高频考点,通过真实性能优化案例,展

weixin_34101784的博客 301

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

高级安卓开发面试核心考察与实战解析

Android系统架构与性能优化是移动开发的核心技术领域。从Binder通信机制到View渲染管线,系统底层原理决定了应用性能上限。Jetpack组件和Kotlin协程等现代技术栈,通过标准化开发范式提升工程效率。在社交类应用场景中,音视频处理和即时通讯等技术方案需要结合SurfaceView、OpenGL等底层能力实现高性能渲染。高级开发者需具备架构设计能力,包括组件化解耦、Flutter混合开发等方案选型,并能在列表卡顿优化等实际问题中运用Systrace、Memory Profiler等工具进行系统性

weixin_30608503的博客 337

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Android开发技术栈全解析与面试指南

移动应用开发中,Android技术栈的演进从基础的Activity+Fragment架构发展到现代化开发体系。理解核心概念如Kotlin协程、Jetpack组件库和Compose声明式UI是构建高效应用的基础。技术原理如Handler机制和Binder跨进程通信支撑着系统级功能实现,而性能优化和架构设计则直接影响应用质量。在工程实践中,内存分析工具和模块化开发方案能显著提升开发效率。对于Android开发者而言,掌握这些技术不仅能够应对复杂业务需求,也是通过技术面试的关键。本文特别针对Java/Kotlin

weixin_33755557的博客 879

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Android面试全攻略:从Java基础到架构设计,助你斩获心仪Offer

在移动开发领域,扎实的技术基础和清晰的架构思维是开发者职业发展的核心。理解计算机基础原理,如JVM内存模型与垃圾回收机制,是构建高性能应用的基石。掌握Android平台核心技术,包括四大组件生命周期、View渲染流程及事件分发机制,能有效解决UI卡顿等性能问题。在工程实践中,性能优化和架构设计体现了开发者的综合能力,例如通过启动优化、内存管理及合理的组件化方案,可以显著提升应用质量与团队协作效率。这些技术最终服务于构建稳定、高效的大型应用,也是当前Android面试中的高频考点和深度考察方向。

weixin_33692284的博客 384

2025年Android开发面试核心趋势与高频考点解析

在移动开发领域,Android技术栈持续演进带来新的技术挑战与机遇。Kotlin作为现代Android开发的首选语言,其协程机制和JVM底层原理成为面试重点考察内容。跨平台开发框架如Flutter通过高效的渲染引擎实现性能突破,其与原生平台的通信机制直接影响混合开发质量。Jetpack Compose作为声明式UI框架,其智能重组机制和状态管理方案能显著提升界面性能。从工程实践角度看,Android 14引入的隐私沙盒、预测性返回手势等新特性要求开发者掌握系统级适配方案。性能优化维度也从传统内存管理扩展到启

weixin_34174322的博客 297
上一篇: 如何应对 Android 面试官 -> 启动如何进行优化(上)?玩转 Android StartUp
下一篇: 如何应对 Android 面试官 -> 卡顿和布局如何优化?玩转 BlockCanary,手写核心实现
老A说
博客等级 码龄14年 1181粉丝 85原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值