안 드 로 이 드 성능 최적화 시작 최적화

1.왜 시동 최적화
인터넷 에 서 는 8 초 법칙 이라는 말 이 유행 하고 있다.사용자 가 한 페이지 를 열 고 8 초 동안 열 리 지 않 으 면 사용 자 는 대개 포기 하고 한 사용자 의 유실 을 의미한다.여기 서 시동 최적화 의 중요성 을 알 수 있다.
2.시작 하 는 분류
2.1 콜 드 시동
먼저 냉 가동 의 흐름 도 를 살 펴 보 겠 습 니 다.

그림 에서 알 수 있 듯 이 앱 이 시작 되 는 과정 은 Activity Manager Proxy 는 IPC 를 통 해 AMS(Activity Manager Service)를 호출 하고 AMS 는 IPC 를 통 해 앱 프로 세 스 를 시작 하 며 ApplicationThread 는 반 사 를 통 해 애플 리 케 이 션 을 만 들 고 연결 하 며 마지막 으로 Activity Thread 를 통 해 activity 의 생명 주 기 를 제어 한다.관련 페이지 의 수명 주기 에서 ViewRootImpl 을 통 해 view 를 실현 합 니 다.응용 프로그램의 시작 을 완료 합 니 다.
2.2 열 가동
열 시작 속도 가 가장 빠 릅 니 다.프로 세 스 가 배경 에서 프론트 로 전환 하 는 과정 입 니 다.
2.3 온도 가동
온도 시작 은 페이지 의 수명 주 기 를 한 번 만 다시 걸 을 수 있 지만 프로 세 스 에 대해 서 는 애플 리 케 이 션 이 다시 만 들 지 않 습 니 다.
3.방향 최적화
위 에서 작 동 하 는 몇 가지 방식 을 소개 한 것 을 보면 우 리 는 작 동 최적화 에 대해 기본적으로 냉 작 동 만 최적화 하면 된다 는 것 을 알 수 있다.그러나 콜 드 가동 의 시작 절차 에서 시스템 이 많이 만들어 져 서 우 리 는 조작 할 방법 이 없다.우리 가 할 수 있 는 일 은 바로 application 의 생명 주기 와 activity 의 생명 주기 라 는 부분 입 니 다.작 동 최적화 는 흔히 이 두 부분 에서 시작 합 니 다.
4.시작 시간의 측정 방식
4.1 adb 명령 방식 사용(오프라인 사용 이 편리 함)
adb shell am start-W 가방 명/가방 명+클래스 명

This Time:마지막 activity 의 시작 시간
TotalTime:모든 activity 의 시작 시간
WaitTime:AMS 가 activity 를 시작 하 는 데 걸 리 는 총 시간
여 기 는 제 가 메 인 화면 에 직접 들 어 갔 기 때문에 중간 에 Splash Activity 가 없습니다.모든 This Time 과 Total Time 의 시간 은 같 습 니 다.
장점:오프라인 에서 사용 하기에 편리 하고 오프라인 에서 사용 하기에 적합 한 제품 과 경쟁 품 을 얻 는 시간 을 비교 합 니 다.
단점:온라인 으로 가 져 갈 수 없고 얻 는 시간 은 대략적인 시간 이 라 고 할 수 있 을 뿐 엄밀 하지 않다.
4.2 수 동 타 점 방식

System.currentTimeMillis()를 통 해 타임 스탬프 를 찍 습 니 다.
단점:분명 합 니 다.코드 에 대한 침입 성 이 매우 큽 니 다.모든 작업 을 하 는 데 걸 리 는 시간 이 라면 코드 가 징 그 러 워 보 입 니 다.
5,우아 한 획득 방법 시간 소모
5.1 AOP Aspect Oriented Programming 절단면 프로 그래 밍
AOP:사전 컴 파일 방식 과 런 타임 동적 프 록 시 를 통 해 프로그램 기능 을 통합 적 으로 유지 하 는 기술 입 니 다.그의 핵심 사상 은 응용 프로그램의 업무 논리 처리 부분 을 유 니 버 설 서비스 부분 인'횡 절 관심 사'와 분리 하 는 것 이다.
OOP:패키지,계승,다 중 등 개념 을 도입 하여 대상 차원 구 조 를 구축 하고 개발 자 들 이 수직 관 계 를 정의 할 수 있 지만 수평적 관계 에 적합 하지 않 습 니 다.
AOP 는 OOP 의 보완 과 보완 이 라 고 할 수 있다.
5.2 aspectj 의 사용
AspectJ 는 절단면 프로 그래 밍 을 위 한 프레임 워 크 로 자바 의 확장 과 호 환 자바 입 니 다.AspectJ 는 AOP 문법 을 정 의 했 습 니 다.자바 바이트 인 코딩 규범 을 지 키 는 Class 파일 을 만 드 는 전문 컴 파일 러 가 있 습 니 다.
프로젝트 의 루트 디 렉 터 리 에 build.gradle 의존 도 를 추가 합 니 다:
dependencies {
    classpath 'com.android.tools.build:gradle:3.5.2'
    classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'
    // NOTE: Do not place your application dependencies here; they belong
    // in the individual module build.gradle files
}
app 의 build.gradle 에 의존 도 를 추가 합 니 다.
apply plugin: 'android-aspectjx'
dependencies 에 추가
implementation 'org.aspectj:aspectjrt:1.9.4'
그리고 클래스 를 만 듭 니 다.

package com.noahedu.myapplication.aspectj;

import android.util.Log;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class MyApplicationAspectj {

    @Around("call(* com.noahedu.myapplication.MyApplication.**(..))")
    public void getTime(ProceedingJoinPoint joinPoint){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        long time = System.currentTimeMillis();
        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        Log.e("MyApplicationAspectj " ,(name + " cost " + (System.currentTimeMillis() - time)));
    }
}
이렇게 하면 우리 가 실행 할 때 logcat 에서 application 의 onCreate 방법 에서 모든 호출 방법 을 출력 하 는 데 시간 이 걸 립 니 다.
2020-07-10 14:22:27.151 1619-1619/? E/MyApplicationAspectj: taskOne cost 150
2020-07-10 14:22:29.203 1619-1619/com.noahedu.myapplication E/MyApplicationAspectj: taskTwo cost 2052
2020-07-10 14:22:29.554 1619-1619/com.noahedu.myapplication E/MyApplicationAspectj: taskThrid cost 351
2020-07-10 14:22:30.556 1619-1619/com.noahedu.myapplication E/MyApplicationAspectj: taskFour cost 1001
이렇게 하면 우 리 는 애플 리 케 이 션 의 어떠한 코드 도 건 드 리 지 않 았 고 각 방법의 시간 을 얻 을 수 있 으 며 코드 에 거의 침입 하지 않 았 다.
6.최 적 화 된 도구 선택 시작
6.1 traceview
TraceView 는 Android SDK 에 내 장 된 도구 입 니 다.그 는 trace 파일 을 불 러 와 해당 코드 의 실행 시간,횟수 와 호출 스 택 을 도형 화 된 형식 으로 보 여 줌 으로 써 우리 가 분석 할 수 있 습 니 다.

 Debug.startMethodTracing("MyApplication");
      //TODO
 Debug.stopMethodTracing();
프로젝트 를 실행 하면 SD 카드 에서 해당 하 는 trace 파일 을 찾 을 수 있 습 니 다.Android studio 라면 오른쪽 아래 에서 직접 찾 을 수 있 습 니 다.
DeviceFileExporer-->sdcard-->Android-->data-->files--->자신의 프로젝트 의 패키지 이름
그리고 더 블 클릭 하면 파일 을 볼 수 있 습 니 다.
장점:간단 하고 도형 형식 으로 실 행 된 시간,스 택 호출 등 을 보 여 줍 니 다.
단점:우리 가 최적화 하 는 방향 에 영향 을 줄 수 있 습 니 다.도형 화 전시 이자 CPU 자원 을 차지 하기 때문에 얻 는 시간 이 실제 보다 큽 니 다.
7.시동
위 에서 임 무 를 수행 하 는 시간 을 얻 는 방식 과 도 구 를 몇 개 더 소개 했다.그러면 우리 가 어떤 방법 을 알 게 되 었 을 때 우 리 는 어떻게 처리 해 야 합 니까?

package com.noahedu.myapplication;

import android.app.Application;
import android.os.Debug;
import android.util.Log;

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        Debug.startMethodTracing("MyApplication");
        taskOne();
        taskTwo();
        taskThrid();
        taskFour();
        Debug.stopMethodTracing();
    }

    public void taskOne(){
        try {
            Thread.sleep(150);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void taskTwo(){
        try {
            Thread.sleep(2050);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void taskThrid(){
        try {
            Thread.sleep(350);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void taskFour(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
현재 애플 리 케 이 션 의 onCreate 방법 중 몇 가지 작업 이 있 습 니 다.각각 시간 이 다 릅 니 다.많은 학생 들 이'비동기 처리'라 고 말 할 수 있 습 니 다.스 레 드 를 열 고 스 레 드 탱크 에 넣 거나 Intent Service 를 만들어 서 실행 합 니 다.그러면 저희 가 몇 가지 생각 을 해 봐 야 될 것 같 아 요.
첫째:비동기 처리 입 니 다.한 페이지 에 SDK 를 사용 해 야 하 는데 초기 화 되 지 않 았 습 니까?
둘째:taskTwo 에 taskOne 의 반환 값 이 필요 하 다 면?taskOne 이 taskTwo 전에 실 행 될 것 이 라 고 어떻게 보장 합 니까?
셋째:스 레 드 를 열 고 몇 개의 스 레 드 를 열 까요?많 으 면 자원 낭 비 를 초래 하고 자원 이 적 으 면 합 리 적 인 이용 도 없다.
저 는 개인 적 으로 시작 최적화 에 대해 application 에서 onCreate()에서 초기 화 작업 을 해 야 한다 고 생각 합 니 다.우 리 는 먼저 이 작업 들 에 대해 우선 순위 구분 을 해 야 합 니 다.우선 순위 가 높 은 작업 에 대해 우 리 는 우선 처리 할 수 있 습 니 다.우선 순위 가 낮은 것 에 대해 우 리 는 적당 한 지연 으로 로드 할 수 있 습 니 다.
그 다음 에 많은 학생 들 이 우선 순위 가 낮은 임 무 를 지연 로 딩 하 는 것 을 좋아 합 니 다.예 를 들 어 new Handler().postDelayed().이런 것 은 매우 바람 직 하지 않다 고 생각 합 니 다.만약 에 postDelayed 에 놓 인 임 무 는 2s 가 걸 리 고 1s 가 지연 되 어 처리 된다 면 2s 임 무 를 수행 하 는 과정 에서 사용자 가 조작 하 는 것 이 매우 카드 가 되 지 않 습 니까?분명 합 니 다.이것 은 지표 가 근본 을 다스 리 지 않 는 것 이다.
7.1 시동기 의 사상
위 에서 말 한 몇 개의 통 증 점 에 대해 어떻게 위의 몇 개의 통 증 점 을 처리 하고 코드 의 유지 가능성 을 확보 할 수 있 습 니까?다시 말 하면 신인 이 전체 과정 을 이해 하지 않 고 바로 일 을 할 수 있다 는 것 일 까?그럼 시동 이 왔 습 니 다.
시동기 핵심 사상:CPU 다 핵 을 충분히 이용 하여 작업 순 서 를 자동 으로 정리 합 니 다.
7.2 시동기 의 원리
1.퀘 스 트 는 모두 Task 대상 으로 봉 하여 집합 에 전송 합 니 다.
2.모든 임무 의존 관계 에 따라 방향 무 환 도 를 형성 한 다음 에 토폴로지 순 서 를 통 해 임무 의 집행 절 차 를 배열 한다.
3.Count Downlatch 를 통 해 어떤 임무 가 수행 되 었 는 지 여 부 를 제어 하고 다음 단 계 를 진행 합 니 다.
4.스 레 드 탱크 가 핵심 스 레 드 를 만 드 는 수량 은 핸드폰 의 핵 수량 에 의 해 결정 된다.
7.3 시동기 사용 방식

7.4 시동기 핵심 코드

작업 정렬 진행

package com.noahedu.launchertool.launchstarter.sort;


import com.noahedu.launchertool.launchstarter.task.Task;
import com.noahedu.launchertool.launchstarter.utils.DispatcherLog;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import androidx.annotation.NonNull;
import androidx.collection.ArraySet;


public class TaskSortUtil {

    private static List<Task> sNewTasksHigh = new ArrayList<>();//      Task

    /**
     *              
     *
     * @return
     */
    public static synchronized List<Task> getSortResult(List<Task> originTasks,
                                                        List<Class<? extends Task>> clsLaunchTasks) {
        long makeTime = System.currentTimeMillis();

        Set<Integer> dependSet = new ArraySet<>();
        Graph graph = new Graph(originTasks.size());
        for (int i = 0; i < originTasks.size(); i++) {
            Task task = originTasks.get(i);
            if (task.isSend() || task.dependsOn() == null || task.dependsOn().size() == 0) {
                continue;
            }
            for (Class cls : task.dependsOn()) {
                int indexOfDepend = getIndexOfTask(originTasks, clsLaunchTasks, cls);
                if (indexOfDepend < 0) {
                    throw new IllegalStateException(task.getClass().getSimpleName() +
                            " depends on " + cls.getSimpleName() + " can not be found in task list ");
                }
                dependSet.add(indexOfDepend);
                graph.addEdge(indexOfDepend, i);
            }
        }
        List<Integer> indexList = graph.topologicalSort();
        List<Task> newTasksAll = getResultTasks(originTasks, dependSet, indexList);

        DispatcherLog.i("task analyse cost makeTime " + (System.currentTimeMillis() - makeTime));
        printAllTaskName(newTasksAll);
        return newTasksAll;
    }

    @NonNull
    private static List<Task> getResultTasks(List<Task> originTasks,
                                             Set<Integer> dependSet, List<Integer> indexList) {
        List<Task> newTasksAll = new ArrayList<>(originTasks.size());
        List<Task> newTasksDepended = new ArrayList<>();//       
        List<Task> newTasksWithOutDepend = new ArrayList<>();//      
        List<Task> newTasksRunAsSoon = new ArrayList<>();//           ,   (             )
        for (int index : indexList) {
            if (dependSet.contains(index)) {
                newTasksDepended.add(originTasks.get(index));
            } else {
                Task task = originTasks.get(index);
                if (task.needRunAsSoon()) {
                    newTasksRunAsSoon.add(task);
                } else {
                    newTasksWithOutDepend.add(task);
                }
            }
        }
        //   :      ――――》          ――――》      ――――》     
        sNewTasksHigh.addAll(newTasksDepended);
        sNewTasksHigh.addAll(newTasksRunAsSoon);
        newTasksAll.addAll(sNewTasksHigh);
        newTasksAll.addAll(newTasksWithOutDepend);
        return newTasksAll;
    }

    private static void printAllTaskName(List<Task> newTasksAll) {
        if (true) {
            return;
        }
        for (Task task : newTasksAll) {
            DispatcherLog.i(task.getClass().getSimpleName());
        }
    }

    public static List<Task> getTasksHigh() {
        return sNewTasksHigh;
    }

    /**
     *            index
     *
     * @param originTasks
     * @return
     */
    private static int getIndexOfTask(List<Task> originTasks,
                                      List<Class<? extends Task>> clsLaunchTasks, Class cls) {
        int index = clsLaunchTasks.indexOf(cls);
        if (index >= 0) {
            return index;
        }

        //         
        final int size = originTasks.size();
        for (int i = 0; i < size; i++) {
            if (cls.getSimpleName().equals(originTasks.get(i).getClass().getSimpleName())) {
                return i;
            }
        }
        return index;
    }
}
작업 코드 실행

package com.noahedu.launchertool.launchstarter.task;

import android.os.Looper;
import android.os.Process;

import com.noahedu.launchertool.launchstarter.TaskDispatcher;
import com.noahedu.launchertool.launchstarter.stat.TaskStat;
import com.noahedu.launchertool.launchstarter.utils.DispatcherLog;

/**
 *          
 */

public class DispatchRunnable implements Runnable {
    private Task mTask;
    private TaskDispatcher mTaskDispatcher;

    public DispatchRunnable(Task task) {
        this.mTask = task;
    }
    public DispatchRunnable(Task task,TaskDispatcher dispatcher) {
        this.mTask = task;
        this.mTaskDispatcher = dispatcher;
    }

    @Override
    public void run() {
        DispatcherLog.i(mTask.getClass().getSimpleName()
                + " begin run" + "  Situation  " + TaskStat.getCurrentSituation());

        Process.setThreadPriority(mTask.priority());

        long startTime = System.currentTimeMillis();

        mTask.setWaiting(true);
        mTask.waitToSatisfy();

        long waitTime = System.currentTimeMillis() - startTime;
        startTime = System.currentTimeMillis();

        //   Task
        mTask.setRunning(true);
        mTask.run();

        //   Task     
        Runnable tailRunnable = mTask.getTailRunnable();
        if (tailRunnable != null) {
            tailRunnable.run();
        }

        if (!mTask.needCall() || !mTask.runOnMainThread()) {
            printTaskLog(startTime, waitTime);

            TaskStat.markTaskDone();
            mTask.setFinished(true);
            if(mTaskDispatcher != null){
                mTaskDispatcher.satisfyChildren(mTask);
                mTaskDispatcher.markTaskDone(mTask);
            }
            DispatcherLog.i(mTask.getClass().getSimpleName() + " finish");
        }
    }

    /**
     *     Task     
     *
     * @param startTime
     * @param waitTime
     */
    private void printTaskLog(long startTime, long waitTime) {
        long runTime = System.currentTimeMillis() - startTime;
        if (DispatcherLog.isDebug()) {
            DispatcherLog.i(mTask.getClass().getSimpleName() + "  wait " + waitTime + "    run "
                    + runTime + "   isMain " + (Looper.getMainLooper() == Looper.myLooper())
                    + "  needWait " + (mTask.needWait() || (Looper.getMainLooper() == Looper.myLooper()))
                    + "  ThreadId " + Thread.currentThread().getId()
                    + "  ThreadName " + Thread.currentThread().getName()
                    + "  Situation  " + TaskStat.getCurrentSituation()
            );
        }
    }

}
기본 핵심 코드 는 위 에 있 는 이 몇 개 입 니 다.완전한 코드 는 뒤의 demo 에서 제 시 됩 니 다.
8.기타 최적화 방안
8.1 지연 작업 분할 초기 화
IdleHandler 기능 을 사용 하여 남 은 실행(우선 순위 가 높 지 않 고 초기 화 를 서 두 르 지 않 는 제3자 SDK 에 적합)
IdleHandler:IdleHandler 는 성능 을 향상 시 키 는 데 사용 할 수 있 습 니 다.주로 현재 스 레 드 메시지 대기 열 이 비어 있 을 때 일 을 하고 싶 은 경우(예 를 들 어 UI 스 레 드 가 표 시 된 후에 스 레 드 가 비어 있 으 면 다른 내용 을 미리 준비 할 수 있 습 니 다)에 사용 되 지만 시간 소모 작업 을 하지 않 는 것 이 좋 습 니 다.쉽게 말 하면 looper 대상 이 시간 이 있 을 때 IdleHandler 의 임 무 를 수행 하 는 것 이다.
앞에서 말 했 듯 이 애플 리 케 이 션 의 작업 에 우선 순 위 를 나 누 면 우선 순위 가 낮은 작업 이 있 으 면 애플 리 케 이 션 의 onCreate 에서 초기 화 하지 않 아 도 되 지 않 을까요?activity 에 넣 어서 진행 할 수 있 나 요?activity 에서 가능 하 다 면 activity 의 그 단계 가 어 울 릴 까요?사실 onCreate()에 서 는 onResume()이 모두 가능 합 니 다.물론 view 의 getView TreeObserver().addOnPreDraw Listener 를 사용 하여 감청 할 수 있 습 니 다.이 이 벤트 는 보기 가 그 려 질 때 이 방법 을 되 돌려 줍 니 다.초기 화 할 SDK 를 리 셋 하 는 방법 으로 IdleHandler 에 확대 할 수 있 습 니 다.
8.2 SP 를 미리 불 러 오기 전에 multidex 에 불 러 올 수 있 습 니 다.이 단계 의 CPU 를 이용 하여
Shared Preferences 는 키 값 으로 데 이 터 를 저장 합 니 다.메모리 에 한꺼번에 불 러 오기 때문에 그 단계 의 CPU 가 상대 적 으로 비어 있 는 것 을 고려 할 수 있 습 니 다.multidex 이전에 불 러 올 수 있다 면 이 단계 의 CPU 를 충분히 이용 하여 진행 합 니 다.
demo 주소:[https://github.com/343661629/startOptimization]
이상 은 안 드 로 이 드 성능 최적화 의 시작 최적화 에 대한 상세 한 내용 입 니 다.안 드 로 이 드 성능 최적화 의 시작 최적화 에 관 한 자 료 는 우리 의 다른 관련 글 을 주목 하 세 요!

좋은 웹페이지 즐겨찾기