10

For Android developers, it is often not enough to understand basic application development skills, because whether it is a job or an interview, developers need to know a lot of performance optimization, which is very important to improve the application experience. For Android development, performance optimization mainly revolves around the following aspects: startup optimization, rendering optimization, memory optimization, network optimization, stuck detection and optimization, power consumption optimization, installation package volume optimization, security issues, etc.

1. Start optimization

The startup speed of an application can directly affect the user's experience. If the startup is slow, it may cause the user to uninstall and abandon the application.

1.1 Optimization of cold start, hot start and warm start

1.1.1 Concept

For Android applications, according to the startup mode, it can be divided into three types: cold start, hot start and warm start.

  • Cold start: When there is no App process in the system (for example, the app is started for the first time or the app is completely killed), it is called cold start to start the app.
  • Hot start: The process of pressing the Home button or the app is switched to the background in other situations and starting the App again.
  • Warm start: Warm start includes some operations of cold start, but the App process still exists, which means it has more overhead than warm start.

It can be seen that the hot start is the fastest to start, and the warm start is a start method between cold start and hot start. The cold start is the slowest, because it involves the creation of many processes. The following is the task flow related to cold start:
在这里插入图片描述

1.1.2 Visual optimization

In cold start mode, the system will start three tasks:

  • Load and start the application.
  • A blank startup window of the application is displayed immediately after startup.
  • Create an application process.

Once the system creates the application process, the application process will enter the next stage and complete the following things.

  • Create app object
  • Start the main thread
  • Create the Activity object of the application entry
  • Fill in the loading layout View
  • Execute the drawing process of View on the screen. measure -> layout -> draw

After the application process finishes drawing for the first time, the system process will exchange the currently displayed background window and replace it with the main activity. At this point, the user can start using the application. Because the creation process of the App application process is determined by the mobile phone's software and hardware, we can only perform some visual optimizations during this creation process.

1.1.3 Start theme optimization

In the cold start, when the application process is created, you need to set the theme of the startup window. At present, most applications will first enter a splash screen page (LaunchActivity) to display application information at the startup meeting. If other third-party services are initialized in the Application, the startup white screen problem will occur.

In order to connect our splash screen page more smoothly and seamlessly, you can set the splash screen page picture in the Theme of the startup Activity, so that the picture of the startup window will be the splash screen page picture instead of the white screen.

    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowBackground">@drawable/lunch</item>  //闪屏页图片
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowDrawsSystemBarBackgrounds">false</item>
    </style>

1.2 Optimization of code

The way to set the theme can only be applied to scenarios that are not very demanding, and this optimization treats the symptoms rather than the root cause, and the key lies in the optimization of the code. In order to optimize, we need to master some basic data.

1.2.1 Cold start time consumption statistics

ADB command method
Enter the following command in the Terminal of Android Studio to view the start time of the page. The command is as follows:

adb shell am start  -W packagename/[packagename].首屏Activity

After the execution is complete, the following information will be output on the console:

Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.optimize.performance/.MainActivity }
Status: ok
Activity: com.optimize.performance/.MainActivity
ThisTime: 563
TotalTime: 563
WaitTime: 575
Complete

There are three fields of information in the above log, namely ThisTime, TotalTime and WaitTime.

  • ThisTime : The last activity takes time to start
  • TotalTime : all activities start time-consuming
  • WaitTime : the total time taken by AMS to start Activity

log method
The burying method is another way to count online time. This method records the start time and the end time, and then takes the difference between the two. First, you need to define a tool class for counting time:

class LaunchRecord {
​
    companion object {
​
        private var sStart: Long = 0

        fun startRecord() {
            sStart = System.currentTimeMillis()
        }
​
        fun endRecord() {
            endRecord("")
        }
​
        fun endRecord(postion: String) {
            val cost = System.currentTimeMillis() - sStart
            println("===$postion===$cost")
        }
    }
}

When starting up, we directly manage the points in the attachBaseContext of the Application. So where should the start-up end? The suggestion to end the burying point is to bury the point when the page data is displayed. The following methods can be used:

class MainActivity : AppCompatActivity() {
​
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
​
        mTextView.viewTreeObserver.addOnDrawListener {
            LaunchRecord.endRecord("onDraw")
        }
​
    }
​
    override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)
        LaunchRecord.endRecord("onWindowFocusChanged")
    }
}

1.2.2 Optimize detection tools

When doing startup optimization, we can use third-party tools to help us sort out the methods of each stage or the time-consuming execution of threads and CPUs. Here we mainly introduce the following two tools, TraceView and SysTrace.

TraceView

TraceView displays the execution time, call stack and other information in the form of graphics. The information is comprehensive and includes all threads, as shown in the figure below.
在这里插入图片描述
The results generated using TraceView detection will be placed in the Andrid/data/packagename/files path. Because the information collected by Traceview is relatively comprehensive, it will cause serious running overhead and slow down the overall APP operation. Therefore, we cannot distinguish whether Traceview has affected our startup time.

SysTrace
Systrace combines the Android kernel data to generate HTML reports. From the reports, we can see the execution time of each thread, the method time consumption and the CPU execution time.

在这里插入图片描述
In API 18 and above, you can directly use TraceCompat to grab data, because this is a compatible API.

开始:TraceCompat.beginSection("tag ")
结束:TraceCompat.endSection()

Then, execute the following script.

python systrace.py -b 32768 -t 10 -a packagename -o outputfile.html sched gfx view wm am app

Here you can popularize the meaning of each word end:

  • b: the size of the collected data
  • t: time
  • a: the name of the application package to be monitored
  • o: the name of the generated file

Systrace has a small overhead, is a lightweight tool, and can intuitively reflect the CPU utilization.

2. UI rendering optimization

The Android system will redraw the Activity every 16ms. Therefore, our application must complete all the logical operations of screen refresh within 16ms, and each frame can only stay for 16ms, otherwise the frame will be dropped. Whether the Android application freezes or not has a direct relationship with UI rendering.

2.1CPU、GPU

For most mobile phones, the screen refresh rate is 60hz, that is, if the task of this frame is not completed within 1000/60=16.67ms, frame loss will occur. Frame loss is the direct cause of interface freeze , Rendering operations usually rely on two core components: CPU and GPU. The CPU is responsible for computing operations including Measure and Layout, and the GPU is responsible for Rasterization (rasterization) operations.

The so-called rasterization is the process of converting vector graphics into bitmaps. The display on the mobile phone is displayed in pixels. For example, a Button, TextView and other components are split into pixels and displayed on the mobile phone screen. The purpose of UI rendering optimization is to reduce the pressure on the CPU and GPU, remove unnecessary operations, and ensure that all CPU and GPU calculations, drawing, rendering and other operations are processed within 16ms per frame, so that the UI is smooth and smooth. come out.

2.2 Overdraw

The first step in UI rendering optimization is to find Overdraw, which describes that a certain pixel on the screen has been drawn multiple times in the same frame. In overlapping UI layouts, if the invisible UI is also drawing or the next control occludes the previous control, some pixel areas will be drawn multiple times, thereby increasing the pressure on the CPU and GPU.

So how do you find out where the Overdraw is in the layout? It's very simple, just turn on the developer option in the phone, and then turn on the switch to debug GPU overdraw, and then you can see whether the layout of the application is Overdraw, as shown in the following figure.
在这里插入图片描述
Blue, light green, light red, and deep red represent 4 different degrees of Overdraw. 1x, 2x, 3x, and 4x indicate that the same pixel has been drawn multiple times in the same frame time, and 1x means once (the most Ideal situation), 4x means 4 times (worst case), and what we need to eliminate is 3x and 4x.

2.3 Solve OverDraw of Custom View

We know that sometimes the onDraw method is rewritten when customizing the View, but the Android system cannot detect the specific operations that will be performed in onDraw, so the system cannot do some optimizations for us. This puts high demands on programmers. If the View has a large amount of overlap, it will cause waste of CPU and GPU resources. At this time, we can use canvas.clipRect() to help the system identify those visible areas.

This method can specify a rectangular area, only in this area will be drawn, other areas will be ignored. Below we use a small Demo provided by Google to further illustrate the use of OverDraw.
在这里插入图片描述
In the code below, the DroidCard class encapsulates the card information, the code is as follows.

public class DroidCard {

public int x;//左侧绘制起点
public int width;
public int height;
public Bitmap bitmap;

public DroidCard(Resources res,int resId,int x){
this.bitmap = BitmapFactory.decodeResource(res,resId);
this.x = x;
this.width = this.bitmap.getWidth();
this.height = this.bitmap.getHeight();
 }
}

The code for custom View is as follows:

public class DroidCardsView extends View {
//图片与图片之间的间距
private int mCardSpacing = 150;
//图片与左侧距离的记录
private int mCardLeft = 10;

private List<DroidCard> mDroidCards = new ArrayList<DroidCard>();

private Paint paint = new Paint();

public DroidCardsView(Context context) {
super(context);
initCards();
}

public DroidCardsView(Context context, AttributeSet attrs) {
super(context, attrs);
initCards();
}
/**
* 初始化卡片集合
*/
protected void initCards(){
Resources res = getResources();
mDroidCards.add(new DroidCard(res,R.drawable.alex,mCardLeft));

mCardLeft+=mCardSpacing;
mDroidCards.add(new DroidCard(res,R.drawable.claire,mCardLeft));

mCardLeft+=mCardSpacing;
mDroidCards.add(new DroidCard(res,R.drawable.kathryn,mCardLeft));
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (DroidCard c : mDroidCards){
drawDroidCard(canvas, c);
}
invalidate();
}

/**
* 绘制DroidCard
*/
private void drawDroidCard(Canvas canvas, DroidCard c) {
canvas.drawBitmap(c.bitmap,c.x,0f,paint);
}
}

Then, we run the code and turn on the phone's overdraw switch, the effect is as follows:
在这里插入图片描述
It can be seen that the light red area is obviously drawn three times because of the overlap of the pictures. How to solve this problem? In fact, the analysis shows that only one third of the bottom picture needs to be drawn, and it is enough to ensure that only one third of the bottom two pictures need to be returned and the top picture is completely drawn. The optimized code is as follows:

public class DroidCardsView extends View {

//图片与图片之间的间距
private int mCardSpacing = 150;
//图片与左侧距离的记录
private int mCardLeft = 10;

private List<DroidCard> mDroidCards = new ArrayList<DroidCard>();

private Paint paint = new Paint();

public DroidCardsView(Context context) {
super(context);
initCards();
}

public DroidCardsView(Context context, AttributeSet attrs) {
super(context, attrs);
initCards();
}
/**
* 初始化卡片集合
*/
protected void initCards(){
Resources res = getResources();
mDroidCards.add(new DroidCard(res, R.drawable.alex,mCardLeft));

mCardLeft+=mCardSpacing;
mDroidCards.add(new DroidCard(res, R.drawable.claire,mCardLeft));

mCardLeft+=mCardSpacing;
mDroidCards.add(new DroidCard(res, R.drawable.kathryn,mCardLeft));
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < mDroidCards.size() - 1; i++){
drawDroidCard(canvas, mDroidCards,i);
}
drawLastDroidCard(canvas,mDroidCards.get(mDroidCards.size()-1));
invalidate();
}

/**
* 绘制最后一个DroidCard
* @param canvas
* @param c
*/
private void drawLastDroidCard(Canvas canvas,DroidCard c) {
canvas.drawBitmap(c.bitmap,c.x,0f,paint);
}

/**
* 绘制DroidCard
* @param canvas
* @param mDroidCards
* @param i
*/
private void drawDroidCard(Canvas canvas,List<DroidCard> mDroidCards,int i) {
DroidCard c = mDroidCards.get(i);
canvas.save();
canvas.clipRect((float)c.x,0f,(float)(mDroidCards.get(i+1).x),(float)c.height);
canvas.drawBitmap(c.bitmap,c.x,0f,paint);
canvas.restore();
 }
}

In the above code, we use Canvas' clipRect method to crop an area before drawing, so that when drawing, only draw in this area, and the excess part will not be drawn. Re-run the above code, the effect is shown in the figure below.
在这里插入图片描述

2.4 Hierarchy Viewer

Hierarchy Viewer is a tool built into Android Device Monitor that allows developers to measure the layout speed of each view in the layout hierarchy and helps developers find performance bottlenecks caused by the view hierarchy. Hierarchy Viewer can distinguish the relative performance of Measure, Layout, and Executive of the layout through three different colors of red, yellow and green.

turn on

  1. Connect the device to the computer. If a dialog box is displayed on the device prompting you to allow USB debugging? , Tap OK.
  2. Open your project in Android Studio, build and run the project on your device.
  3. Launch Android Device Monitor. Android Studio may display the Disable adb integration dialog box because only one process can connect to the device via adb at a time, and Android Device Monitor is requesting a connection. Therefore, click Yes.
  4. In the menu bar, select Window> Open Perspective, and then click Hierarchy View.
  5. Double-click the application package name in the Windows tab on the left. This will populate the relevant panes with the application's view hierarchy.

在这里插入图片描述
The key to improving layout performance is to keep the layout level as flat as possible to avoid repeated nested layouts. If the layout level we write is deeper, it will seriously increase the burden on the CPU and cause severe performance . use of Hierarchy Viewer, please refer to: 160bc9cb34dd6c Use Hierarchy Viewer to analyze the layout 160bc9cb34dd6d.

2.5 Memory jitter

After we have optimized the tree structure and overdraw of the view, we may still feel that our app has issues such as freezing, frame loss, or slow sliding. We need to check whether there is memory jitter. The so-called memory jitter refers to the frequent blocking of UI threads caused by frequent memory creation and GC.

Android has a mechanism to automatically manage memory, but improper use of memory can still cause serious performance problems. Creating too many objects in the same frame is something that requires special attention. Creating a large number of objects in the same frame may cause non-stop GC operations. When performing GC operations, any operation of all threads will need to be suspended until GC The operation is complete. A large number of non-stop GC operations will significantly occupy the frame interval time. If too many GC operations are done in the frame interval, it will cause page freezes.
在这里插入图片描述
In Android development, there are two main reasons for frequent GC operations:

Android's memory jitter can be detected using Android Studio's Profiler.
在这里插入图片描述
Then, click record to record the memory information to find the location where the memory jitter occurs. Of course, you can also locate the code location directly through Jump to Source.
在这里插入图片描述

In order to avoid memory jitter, we need to avoid allocating objects in the for loop to occupy memory. We need to try to move the creation of the object outside the loop body. The onDraw method in the custom View also needs to be paid attention to. Every time the screen is drawn and animated During the execution process, the onDraw method will be called to avoid performing complex operations in the onDraw method and avoid creating objects. For those situations where the need to create objects is unavoidable, we can consider the object pool model to solve the problem of frequent creation and destruction through the object pool, but here we need to pay attention to the need to manually release the objects in the object pool after the end of use.

3. Memory optimization

3.1 Memory management

In front of the Java basic link model for Java memory management, we have also done a basic introduction, reference links: Android Interview will ask Java foundation

3.1.1 Memory area

In the Java memory model, the memory area is divided into five areas: method area, heap, program counter, local method stack, and virtual machine stack, as shown in the figure below.
在这里插入图片描述
Method area

  • The thread shared area is used to store class information, static variables, constants, and code data compiled by the just-in-time compiler.
  • OOM occurs when memory allocation requirements cannot be met.

heap

  • The thread shared area is the largest piece of memory managed by the JAVA virtual machine and is created when the virtual machine starts.
  • Storage of object instances, almost all object instances are allocated on the heap, the main area of GC management.

virtual machine stack

  • Thread private area, each java method will create a stack frame to store the local variable table, operand stack, dynamic link, method export and other information when it is executed. The process from the beginning of the execution to the end of the method is the process of stacking and popping the stack frame in the virtual machine stack.
  • The local variable table stores the basic data types, object references, and returnAddress types that are known at compile time. The required memory space will be allocated during compilation. When entering a method, the space of the local variable table in the frame is completely determined and does not need to be changed at runtime.
  • If the stack depth requested by the thread is greater than the maximum depth allowed by the virtual machine, a SatckOverFlowError error will be thrown.
  • When the virtual machine is dynamically expanded, if enough memory cannot be applied for, an OutOfMemoryError will be thrown.

local method stack

  • Serving the native method in the virtual machine, there are no mandatory regulations on the language, data structure, and usage mode used in the local method stack, and the virtual machine can be implemented by itself.
  • The size of the occupied memory area is not fixed and can be dynamically expanded as needed.

Program counter

  • A small memory space, thread private, stores the bytecode line number indicator of the current thread execution.
  • The bytecode interpreter selects the next bytecode instruction to be executed by changing the value of this counter: branch, loop, jump, etc.
  • Each thread has an independent program counter
  • The only area that is not OOM in the java virtual machine

3.1.2 Garbage Collection

Mark removal algorithm
The mark removal algorithm is mainly divided into two stages. First, mark the objects that need to be recycled, and then uniformly recycle all marked objects after the marking is completed;
Disadvantages:

  • Efficiency problem: The two processes of marking and clearing are not very efficient.
  • Space problem: After the mark is cleared, it will cause a lot of discontinuous memory fragmentation, which will cause the problem of not finding enough contiguous space when large objects need to be allocated and having to trigger the GC.

replication algorithm
Divide the available memory into two small blocks of the same size according to the space, and use only one of them at a time. When this memory is used up, copy the surviving objects to the other memory, and then clear the entire memory area objects. . Reclaiming the memory of the entire half area each time will not cause fragmentation problems, and the implementation is simple and efficient.
Disadvantages:
The memory needs to be reduced to half of the original size, and the space cost is too high.

tag sorting algorithm
Marking and sorting algorithm The marking process is the same as the mark clearing algorithm, but the clearing process does not directly clean up recyclable objects, but moves all surviving objects like one end, and then concentrates on clearing the memory outside the end boundary.

Generational Recovery Algorithm
Modern virtual machine garbage collection algorithms all use generational collection algorithms to collect, divide the memory into the young and old generations according to the life cycle of the object, and then use the most suitable recycling algorithm according to the characteristics of each era.

  • There are few surviving objects in the new generation, and a large number of objects die in each garbage collection. Generally, the replication algorithm is adopted, and garbage collection can be realized only by paying the cost of copying a few surviving objects;
  • There are many surviving objects in the old age, and there is no extra space for allocation guarantee, so the mark removal algorithm and mark sorting algorithm must be used for recycling;

3.2 Memory leak

The so-called memory leak refers to the useless objects in the memory that cannot be reclaimed. The performance phenomenon is to cause memory jitter and reduce available memory, which in turn leads to frequent GC, stuttering, and OOM.

The following is a code that simulates a memory leak:

/**
 * 模拟内存泄露的Activity
 */
public class MemoryLeakActivity extends AppCompatActivity implements CallBack{
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memoryleak);
        ImageView imageView = findViewById(R.id.iv_memoryleak);
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.splash);
        imageView.setImageBitmap(bitmap);
        
        // 添加静态类引用
        CallBackManager.addCallBack(this);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
//        CallBackManager.removeCallBack(this);
    }
    @Override
    public void dpOperate() {
        // do sth
    }

When we use the Memory Profiler tool to view the memory curve, we find that the memory is constantly rising, as shown in the figure below.
在这里插入图片描述
If you want to analyze and locate the specific memory leak location, we can use the MAT tool. First, use the MAT tool to generate the hprof file, and click dump to convert the current memory information into the hprof file. The generated file needs to be converted into a MAT readable file. Execute the conversion command to complete the conversion. The generated file is located in the Android/sdk/platorm-tools path.

hprof-conv 刚刚生成的hprof文件 memory-mat.hprof

Use mat to open the hprof file you just converted, and then use Android Studio to open the hprof file, as shown in the figure below.
在这里插入图片描述
Then click [Historygram] on the panel and search for MemoryLeakActivity to view the relevant information of the corresponding leaked file.
在这里插入图片描述
Then, view all reference objects and get the related reference chain, as shown in the figure below.
在这里插入图片描述
在这里插入图片描述
You can see that GC Roots is CallBackManager
在这里插入图片描述
Therefore, we can remove the CallBackManager reference when the Activity is destroyed.

@Override
protected void onDestroy() {
    super.onDestroy();
    CallBackManager.removeCallBack(this);
}

Of course, the above is just an example of using the MAT analysis tool, and other memory leaks can be solved with the help of the MAT analysis tool.

3.3 Memory optimization of big picture

In Android development, we often encounter memory leaks caused by loading large images. For this scenario, there is a general solution, that is, using ARTHook to detect unreasonable images. We know that there are two main ways to obtain the memory occupied by Bitmap:

  • Through the getByteCount method, but need to be obtained at runtime
  • width * height * memory occupied by one pixel * compression ratio of the resource directory where the picture is located

The ARTHook method can be used to elegantly obtain unreasonable pictures with low intrusiveness, but it is generally used offline due to compatibility issues. To use ARTHook, you need to install the following dependencies:

implementation 'me.weishu:epic:0.3.6'

Then customize the implementation of the Hook method, as shown below.

public class CheckBitmapHook extends XC_MethodHook {
    @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        ImageView imageView = (ImageView)param.thisObject;
        checkBitmap(imageView,imageView.getDrawable());
    }
    private static void checkBitmap(Object o,Drawable drawable) {
        if(drawable instanceof BitmapDrawable && o instanceof View) {
            final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
            if(bitmap != null) {
                final View view = (View)o;
                int width = view.getWidth();
                int height = view.getHeight();
                if(width > 0 && height > 0) {
                    if(bitmap.getWidth() > (width <<1) && bitmap.getHeight() > (height << 1)) {
                        warn(bitmap.getWidth(),bitmap.getHeight(),width,height,
                                new RuntimeException("Bitmap size is too large"));
                    }
                } else {
                    final Throwable stacktrace = new RuntimeException();
                    view.getViewTreeObserver().addOnPreDrawListener(
                            new ViewTreeObserver.OnPreDrawListener() {
                                @Override public boolean onPreDraw() {
                                    int w = view.getWidth();
                                    int h = view.getHeight();
                                    if(w > 0 && h > 0) {
                                        if (bitmap.getWidth() >= (w << 1)
                                                && bitmap.getHeight() >= (h << 1)) {
                                            warn(bitmap.getWidth(), bitmap.getHeight(), w, h, stacktrace);
                                        }
                                        view.getViewTreeObserver().removeOnPreDrawListener(this);
                                    }
                                    return true;
                                }
                            });
                }
            }
        }
    }
    private static void warn(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight, Throwable t) {
        String warnInfo = new StringBuilder("Bitmap size too large: ")
                .append("\n real size: (").append(bitmapWidth).append(',').append(bitmapHeight).append(')')
                .append("\n desired size: (").append(viewWidth).append(',').append(viewHeight).append(')')
                .append("\n call stack trace: \n").append(Log.getStackTraceString(t)).append('\n')
                .toString();
        LogUtils.i(warnInfo);

Finally, Hook is injected when the Application is initialized.

DexposedBridge.hookAllConstructors(ImageView.class, new XC_MethodHook() {
    @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        DexposedBridge.findAndHookMethod(ImageView.class,"setImageBitmap", Bitmap.class,
                new CheckBitmapHook());
    }
});

3.4 Online monitoring

3.4.1 Conventional plan

plan one
Obtain the current occupied memory size in a specific scenario. If the current memory size exceeds 80% of the system's maximum memory, perform a dump (Debug.dumpHprofData()) on the current memory, choose a suitable time to upload the hprof file, and then manually analyze it with the MAT tool The document.

Disadvantages:

  • The dump file is relatively large, and is positively related to the user's use time and the object tree.
  • Large files result in a higher upload failure rate and difficult analysis.

plan two
Bring LeakCannary online, add preset suspicion points, monitor the suspicion points for memory leaks, and find that the memory leaks are sent back to the server.

Disadvantages:

  • The versatility is low, it is necessary to preset the doubt point, and the place where there is no preset doubt point cannot be monitored.
  • LeakCanary analysis is time-consuming and memory-consuming, and OOM may occur.
3.4.2 LeakCannary transformation

The transformation mainly involves the following points:

  • Change the need to preset suspect points to automatically find the suspect points, and automatically set the suspect points in the object class that occupies a large memory in the front memory.
  • LeakCanary analysis of leaked links is relatively slow, and it is transformed to only analyze objects with large retain size.
  • The analysis process will be OOM, because LeakCannary will load all the analysis objects into the memory during analysis. We can record the number and occupancy size of the analysis objects, and tailor the analysis objects, not all of them are loaded into the memory.

The completed transformation steps are as follows:

  1. Monitoring conventional indicators: standby memory, memory occupied by key modules, OOM rate
  2. Monitor the number of GCs, GC time, etc. in the life cycle of the APP and the life cycle of the key module interface
  3. Bring the customized LeakCanary online to automatically analyze online memory leaks

4. Network optimization

4.1 The impact of network optimization

The App’s network connection has a lot of influence on the user, and in most cases it is very intuitive, directly affecting the user’s experience of the App. Among them, the more important points are:
traffic : App traffic consumption is more sensitive to users, after all, traffic costs money. Now most people have installed traffic monitoring tools on their phones to monitor App traffic usage. If Our App is not well controlled in this aspect, which will give users a bad experience.
Power : Relative to the user, the power is not so obvious. The average user may not pay too much attention. But as in the power optimization, the network connection (radio) is a factor that has a great impact on the power. So we must Pay attention.
users wait for : that is, user experience, good user experience, is the first step for us to retain users. If the App request waits for a long time, it will give the user a network card, and the application will feel slow. If there is a comparison, there is As an alternative, our App may be ruthlessly abandoned by users.

4.2 Network analysis tools

The tools that can be used for network analysis include Monitor, proxy tools, and so on.

4.2.1 Network Monitor

The Monitor tool built into Android Studio provides a Network Monitor, which can help developers perform network analysis. The following is a schematic diagram of a typical Network Monitor.
在这里插入图片描述

  • Rx --- R(ecive) represents downstream traffic, that is, download and receive.
  • Tx --- T(ransmit) means upstream traffic, that is, upload and send.

Network Monitor tracks the data requests of selected applications in real time. We can connect to the mobile phone, select and debug the application process, and then operate the page request we need to analyze on the App.

4.2.2 Agent tools

The network proxy tool has two functions. One is to intercept network request response packets and analyze network requests; the other is to set proxy network, which is generally used to test different network environments in mobile App development, such as Wifi/4G/3G/weak network, etc. .

Now, there are many proxy tools that can be used, such as Wireshark, Fiddler, Charles, etc.

4.3 Network optimization plan

For network optimization, optimization is mainly carried out from two aspects:

  1. reduces the active time : Reduce the frequency of network data acquisition, thereby reducing the power consumption of the radio and controlling the power usage.
  2. Compressed data packet size : Compressing data packets can reduce traffic consumption, and can also make each request faster.

Based on the above scheme, the following common solutions can be obtained:

4.3.1 Interface design

1, API design
The API design between the App and the server should consider the frequency of network requests, the status of resources, and so on. So that the App can complete the business requirements and interface display with fewer requests.

For example, registration and login. Normally there are two APIs, registration and login, but when designing the API, we should include an implicit login for the registration interface. To avoid App having to request a login interface after registration.

2, use Gzip to compress

Use Gzip to compress request and response, reduce the amount of data transmitted, thereby reducing data consumption. When using a network request framework such as Retrofit to make a network request, Gzip compression is performed by default.

3, use Protocol Buffer
In the past, we used XML to transmit data, and later replaced XML with JSON, largely for readability and reduction of data volume. In game development, in order to ensure the accuracy and timeliness of the data, Google introduced the Protocol Buffer data exchange format.

4. Get pictures of different resolutions according to network conditions
When we use Taobao or Jingdong, we will see that the application will obtain images of different resolutions according to the network situation to avoid waste of traffic and improve user experience.

4.3.2 Reasonable use of network cache

Proper use of caching can not only make our applications look faster, but also avoid unnecessary traffic consumption and bring a better user experience.

1, package network request

When the interface design cannot meet our business needs. For example, an interface may need to request multiple interfaces, or the network is good, we want to get more data when in the Wifi state, etc. At this time, some network requests can be packaged, for example, while requesting a list, the detailed data of item items with a higher header click rate can be obtained.

2, monitor device status
In order to improve the user experience, we can monitor the usage status of the device, and then combine with JobScheduler to execute network requests. For example, Splash splash screen advertisement images, we can download and cache them locally when connected to Wifi; news apps can be cached offline when charging and Wifi.

4.3.3 Weak network testing & optimization

1, weak network test
There are several ways to simulate a weak network for testing:

Android Emulator
Usually, we can set the network speed and delay by creating and starting the Android emulator, as shown in the figure below.
在这里插入图片描述
Then, the emulator command we use at startup is as follows.

$emulator -netdelay gprs -netspeed gsm -avd Nexus_5_API_22

2, network proxy tool
Using network proxy tools can also simulate network conditions. Take Charles as an example, keep the phone and PC in the same local area network, set the proxy mode to manual in the advanced settings of the mobile wifi settings, fill in the PC ip address for the proxy ip, and the port number defaults to 8888.

在这里插入图片描述

5. Power consumption optimization

In fact, if our application needs to play video, need to obtain GPS information, or is a game application, the power consumption is more serious. How to determine which power consumption can be avoided or need to be optimized? We can open the power consumption ranking list that comes with the mobile phone and find that the "Glory of the King" has been used for more than 7 hours. At this time, users have an expectation of the power consumption of the "Glory of the King".
在这里插入图片描述

5.1 Optimization direction

If you find that an application is not used at all at this time, but consumes a lot of power, it will be ruthlessly killed by the system. So the first direction of power consumption optimization is to optimize the background power consumption of the application.

Knowing how the system calculates power consumption, we can also know what the application should not do in the background, such as obtaining WakeLock, WiFi and Bluetooth scanning for a long time, and background services. Why is it said that the first direction of power consumption optimization is to optimize the power consumption of the application background, because most manufacturers' pre-installed projects require the most stringent application background standby power consumption.

在这里插入图片描述

在这里插入图片描述
Of course, we will not completely ignore the power consumption at the front desk, but the standard will relax a lot. Let's take a look at the picture below. If the system pops up this dialog box for your application, it may be acceptable for WeChat users, but for most other applications, many users may directly add you to the background. In the restricted list.

power consumption optimization is to comply with the rules of the system and make the system think that your power consumption is normal.

Android P and above versions monitor the background power consumption through Android Vitals, so we need to comply with the rules of Android Vitals. The current specific rules are as follows.
在这里插入图片描述
It can be seen that the Android system is currently more concerned about background Alarm wakeup, background network, background WiFi scanning, and some long-term WakeLock preventing the system from sleeping in the background, because these may cause power consumption problems.

5.2 Power consumption monitoring

5.2.1 Android Vitals

Several monitoring programs and rules about power consumption of Android Vitals can help us monitor power consumption.

After using it for a while, I found that it is not that easy to use. Take Alarm wakeup as an example, Vitals uses more than 10 times per hour as the rule. Since this rule cannot be modified, in many cases we may wish to make a more detailed distinction for different system versions. Secondly, like Battery Historian, we can only get the components marked with wakeup, we can't get the applied stack, and we can't get information about whether the mobile phone is charging or the remaining battery level at the time. The picture below is the information obtained by wakeup.

在这里插入图片描述
The same is true for networks, WiFi scans, and WakeLock. Although Vitals helped us narrow the scope of the investigation, there was still no way to confirm the specific cause of the problem.

5.3 How to monitor power consumption

As mentioned earlier, Android Vitals is not so easy to use, and it is actually not usable at all for domestic applications. So what should our power consumption monitoring system monitor, and how should it do it? First of all, let's take a look at how to do power consumption monitoring.

  • monitoring information : Simply put, what the system cares about, we monitor what, and should focus on background power consumption monitoring. Similar to Alarm wakeup, WakeLock, WiFi scans, Network are all necessary, others can be based on the actual situation of the application. If it is a map application, background acquisition of GPS is allowed; if it is a pedometer application, background acquisition of the Sensor is not a big problem.
  • site information : The monitoring system hopes to obtain complete stack information, such as which line of code initiates WiFi scans, which line of code applies for WakeLock, etc. Some information such as whether the phone is charging at the time, the battery level of the phone, the application foreground and background time, and the CPU status can also help us troubleshoot certain problems.
  • Refinement rules : Finally, we need to abstract the monitored content into rules. Of course, the monitoring items or parameters of different applications are not the same. Since the specific conditions of each application are different, simple rules that can be used for reference.

在这里插入图片描述
在这里插入图片描述

5.3.2 Hook scheme

After clarifying what we need to monitor and the specific rules, let's take a look at the technical solutions for power monitoring. Let's first look at the Hook program. The advantage of the Hook solution is that the user access is very simple, there is no need to modify the code, and the access cost is relatively low. Let me take a few more commonly used rules as examples to see how to use Java Hook to achieve the purpose of monitoring.

1,WakeLock
WakeLock is used to prevent the CPU, the screen and even the keyboard from sleeping. Similar to Alarm, JobService will also apply for WakeLock to complete background CPU operations. The core control code of WakeLock is in PowerManagerService, and the implementation method is very simple, as shown below.

// 代理 PowerManagerService
ProxyHook().proxyHook(context.getSystemService(Context.POWER_SERVICE), "mService", this);

@Override
public void beforeInvoke(Method method, Object[] args) {
    // 申请 Wakelock
    if (method.getName().equals("acquireWakeLock")) {
        if (isAppBackground()) {
            // 应用后台逻辑,获取应用堆栈等等     
         } else {
            // 应用前台逻辑,获取应用堆栈等等
         }
    // 释放 Wakelock
    } else if (method.getName().equals("releaseWakeLock")) {
       // 释放的逻辑    
    }
}

2,Alarm
Alarm is used to do some timed repetitive tasks. It has four types, among which ELAPSED_REALTIME_WAKEUP and RTC_WAKEUP types will wake up the device. Similarly, the core control logic of Alarm is in AlarmManagerService, which is implemented as follows.

// 代理 AlarmManagerService
new ProxyHook().proxyHook(context.getSystemService
(Context.ALARM_SERVICE), "mService", this);

public void beforeInvoke(Method method, Object[] args) {
    // 设置 Alarm
    if (method.getName().equals("set")) {
        // 不同版本参数类型的适配,获取应用堆栈等等
    // 清除 Alarm
    } else if (method.getName().equals("remove")) {
        // 清除的逻辑
    }
}

In addition to WakeLock and Alarm, for the background CPU, we can use the methods related to lag monitoring; for the background network, we can also monitor the related methods through the network; for GPS monitoring, we can use the hook agent LOCATION_SERVICE; for the Sensor, we can use "MSensorListeners" in Hook SENSOR_SERVICE, you can get some information.

Finally, we save the stack information to which the resource is applied. When we trigger a rule to report a problem, we can upload the collected stack information, whether the battery is charged, CPU information, the time before and after the application and other auxiliary information to the background.

5.3.3 Pile-instrument method

Although the Hook method is simple, some rules may not be easy to find a suitable Hook point, and after Android P, many Hook points are not supported. For compatibility reasons, the first thing I think of is the instrumentation method. Take WakeLock as an example:

public class WakelockMetrics {
    // Wakelock 申请
    public void acquire(PowerManager.WakeLock wakelock) {
        wakeLock.acquire();
        // 在这里增加 Wakelock 申请监控逻辑
    }
    // Wakelock 释放
    public void release(PowerManager.WakeLock wakelock, int flags) {
        wakelock.release();
        // 在这里增加 Wakelock 释放监控逻辑
    }
}

If you study power consumption again, then you must know Facebook’s open source battery-metrics library for power consumption monitoring, Battery-Metrics, which monitors a complete set of data, including Alarm, WakeLock, Camera, CPU, Network, etc., and also collects battery charge status and power Level and other information. However, it is a pity that Battery-Metrics only provides a series of basic classes, and developers still need to modify a lot of source code in actual use.

6. Installation package optimization

The apps on the market now range from tens of megabytes to hundreds of megabytes. The smaller the installation package, the less traffic when downloading, the better the user experience, the faster the download, and the faster the installation. So for the installation package, what aspects can we start to optimize?

6.1 Common optimization strategies

1, clean up useless resources
In the Android packaging process, if the code has references to resources and codes, it will be packaged into the App. In order to prevent these obsolete codes and resources from being packaged into the App, we need to clean up these useless codes and resources in a timely manner. Reduce the size of the App. The cleaning method is to click [Refactor] -> [Remove unused Resource] of android Studio in turn, as shown in the figure below.
在这里插入图片描述
2, use Lint tool

The Lint tool is still very useful, it gives us points that need to be optimized:

  • Detect useless layouts and delete
  • Delete unused resources
  • It is recommended to delete some unused characters in String.xml

3. Turn on shrinkResources to remove useless resources
Configure shrinkResources true in build.gradle, and useless resources will be automatically removed when packaging, but after experiments, it is found that the package that is printed does not, but will replace some useless resources with smaller things. Note that "useless" here means that all parent functions that call the picture are ultimately discarded code, and shrinkResources true can only remove the case where there is no parent function call.

    android {
        buildTypes {
            release {
                shrinkResources true
            }
        }
    }

In addition, most applications do not actually need to support internationalization support for dozens of languages, and language support files can also be deleted.

6.2 Resource compression

In android development, there are many built-in pictures, which take up a lot of volume, so in order to reduce the size of the package, we can compress the resources. Commonly used methods are:

  1. Use compressed pictures: Using compressed pictures can effectively reduce the size of the App.
  2. Only use one set of pictures: For most logarithmic apps, only one set of design drawings is enough.
  3. Use jpg images without alpha: For large non-transparent images, jpg will have a significant advantage over png. Although it is not absolute, it will usually be reduced to more than half.
  4. Use tinypng lossy compression: Support uploading PNG images to the official website for compression, then downloading and saving, while maintaining the alpha channel, the compression of PNG can reach within 1/3, and the loss of compression is basically indistinguishable by the naked eye.
  5. Use webp format: webp supports transparency, compression ratio, and occupies a smaller volume than JPG pictures. It is natively supported from Android 4.0+, but does not support transparency, until Android 4.2.1+ does not support displaying webp with transparency. Pay special attention when using it.
  6. Use svg: Vector graphics are composed of dots and lines, which are different from bitmaps. It can maintain clarity even if you zoom in, and using vector graphics can save 30-40% of space compared to bitmap designs.
  7. Compress the packaged picture: Use the 7zip compression method to compress the picture, you can directly use the WeChat open source AndResGuard compression scheme.
    apply plugin: 'AndResGuard'
    buildscript {
        dependencies {
            classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.1.7'
        }
    }
    andResGuard {
        mappingFile = null
        use7zip = true
        useSign = true
        keepRoot = false
        // add <your_application_id>.R.drawable.icon into whitelist.
        // because the launcher will get thgge icon with his name
        def packageName = <your_application_id>
                whiteList = [
        //for your icon
        packageName + ".R.drawable.icon",
                //for fabric
                packageName + ".R.string.com.crashlytics.*",
                //for umeng update
                packageName + ".R.string.umeng*",
                packageName + ".R.string.UM*",
                packageName + ".R.string.tb_*",
                packageName + ".R.layout.umeng*",
                packageName + ".R.layout.tb_*",
                packageName + ".R.drawable.umeng*",
                packageName + ".R.drawable.tb_*",
                packageName + ".R.anim.umeng*",
                packageName + ".R.color.umeng*",
                packageName + ".R.color.tb_*",
                packageName + ".R.style.*UM*",
                packageName + ".R.style.umeng*",
                packageName + ".R.id.umeng*"
        ]
        compressFilePattern = [
        "*.png",
                "*.jpg",
                "*.jpeg",
                "*.gif",
                "resources.arsc"
        ]
        sevenzip {
            artifact = 'com.tencent.mm:SevenZip:1.1.7'
            //path = "/usr/local/bin/7za"
        }
    }

6.3 Dynamic loading of resources

In front-end development, dynamic loading of resources can effectively reduce the size of the apk. In addition, only support for mainstream architectures, such as arm, can be considered not to support mips and x86 architectures, which can greatly reduce the size of the APK.

Of course, in addition to the optimization scenarios of the scenarios mentioned above, the optimization of Android App also includes storage optimization, multi-threading optimization, and crash processing.


xiangzhihong
5.9k 声望15.3k 粉丝

著有《React Native移动开发实战》1,2,3、《Kotlin入门与实战》《Weex跨平台开发实战》、《Flutter跨平台开发与实战》1,2和《Android应用开发实战》