For Android developers, understanding basic application development skills is often not enough, because a lot of performance tuning is required, whether it’s for a job or an interview, to improve the app experience. For Android development, performance optimization mainly focuses on the following aspects: startup optimization, rendering optimization, memory optimization, network optimization, lag 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 experience. If the startup speed is slow, users may uninstall and abandon the application.

1.1 Optimization of cold start, hot start and warm start

1.1.1 concept

There are three startup modes for Android applications: cold startup, hot startup, and warm startup.

  • Cold start: When the system does not have any App process (for example, when the App is started for the first time or the App is completely killed), the App is called cold start.
  • Hot launch: The process by which the app is switched to the background after pressing the Home button or otherwise.
  • Warm start: A warm start contains some of the actions of a cold start, but the App process still exists, which means it has more overhead than a hot start.

As you can see, hot boot is the fastest startup, and warm boot is a startup mode between cold and hot. A cold start is the slowest because it involves creating a lot of processes. Here are the tasks associated with a cold start:

1.1.2 Visual optimization

In cold start mode, the system starts three tasks:

  • Load and start the application.
  • A blank startup window that displays the application immediately after startup.
  • Create the application process.

Once the system has created the application process, the application process moves on to the next stage and does the following.

  • Creating an App object
  • Start the main thread
  • Create an Activity object for the application entry
  • Fill load layout View
  • Measure -> Layout -> draw

After the application process completes the first drawing, the system process swaps the currently displayed background window for the main activity. At this point, the user can start using the application. Since the process of creating an App is determined by the hardware and software of the phone, we can only make some visual optimizations during the process.

1.1.3 Enable theme optimization

During cold startup, the theme of the startup window needs to be set when the application process is created. Currently, most applications start with a LaunchActivity page to display Application information. If other third-party services are initiated in the Application, a blank screen will appear.

To make our splash screen more smooth and seamless, we can set the splash screen image in the Theme that starts the Activity so that the splash screen image in the launch window will be a splash screen image instead of a white screen.

    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowBackground">@drawable/lunch</item>  // Flash page image
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowDrawsSystemBarBackgrounds">false</item>
    </style>
Copy the code

1.2 Code optimization

Setting the theme can only be used in scenarios where the requirements are not very high, and this kind of optimization is not a cure, but the key is the optimization of the code. In order to optimize, we need to know some basic data.

1.2.1 Statistics of Cold Startup Time

ADB Run the following command in Android Studio Terminal to view the start time of the page:

adb shell am start -W packagename/[packagename]. The first screen ActivityCopy the code

When the execution is complete, the following information is printed 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
Copy the code

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

  • ThisTime: How long it took for the last Activity to start
  • TotalTime: Total Activity start time
  • WaitTime: The total time it takes for AMS to start the Activity

Log mode Buried point mode is another method to count the online time. This method records the start time and end time, and then takes the difference between them. First, we need to define a utility 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")}}}Copy the code

At startup, we directly dot the Application’s attachBaseContext. So where do you start and finish? The end burying suggestion is to bury the data displayed on the page. You can use the following methods:

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")}}Copy the code

1.2.2 Optimization of detection tools

When doing startup optimization, we can use a tripartite tool to help us understand the various stages of the method or thread, CPU execution time, etc. TraceView and SysTrace are introduced here.

TraceView

TraceView displays the execution time, call stack and other information in a graphical format. The information is comprehensive and includes all threads, as shown in the following figure.Use TraceView test will generate the results generated in the android’s/data/packagename/files directory. Because Traceview collects comprehensive information, it will lead to serious running overhead and slow down the whole APP. Therefore, we cannot distinguish whether Traceview affects our startup time.

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

After API 18, you can use TraceCompat directly to fetch data because it is a compatible API.

Start: TraceCompat.beginSection("tag ") End: TraceCompat.endSection(a)Copy the code

Then, execute the following script.

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

Here we can popularize the meaning of each word end:

  • B: The size of the collected data
  • T: time
  • A: Name of the listening application package
  • O: indicates the name of the generated file

Systrace is a lightweight tool with low overhead, and can directly reflect CPU utilization.

2. UI rendering optimization

The Android system redraws the Activity every 16ms, so our application must complete all the logical operations of the screen refresh within 16ms. Each frame can only stay for 16ms, otherwise it will drop frames. Android app lag is directly related to UI rendering.

2.1 the CPU and GPU

For most mobile phones, the screen refresh frequency 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 lag. Rendering operations usually rely on two core components: CPU and GPU. CPU is responsible for calculation operations including Measure and Layout, while GPU is responsible for Rasterization.

The so-called rasterization is the process of converting vector graphics into bitmaps. The display on mobile phones is displayed one pixel at a time. For example, a Button, TextView and other components are divided into pixels and displayed on the mobile phone screen one by one. The purpose of UI rendering optimization is to reduce the pressure on CPU and GPU, remove unnecessary operations, and ensure that all CPU and GPU computation, drawing, rendering and other operations are processed within 16ms per frame, so that UI can be smoothly and smoothly displayed.

2.2 Overdrawing

The first step in UI rendering optimization is to find Overdraw, which describes a pixel on the screen being drawn multiple times in the same frame. In overlapping UI layouts, if the invisible UI is also drawing or the last control blocks the previous control, some pixel areas will be drawn multiple times, which increases the pressure on the CPU and GPU.

So how do you find places in your layout that Overdraw? It is very simple, just open the developer option in the phone, and then turn on the debug GPU overdrawing switch, and then you can see whether the application layout is overdrawn, as shown in the picture below.Blue, light green, light red and dark red represent four different degrees of Overdraw. 1X, 2X, 3X and 4x respectively represent that the same pixel has been drawn many times in the same frame, 1x represents one (the best case), and 4x represents four times (the worst case). What we need to eliminate are 3X and 4x.

2.3 Solve the OverDraw of custom View

We know that the custom View sometimes rewrite the onDraw method, but the Android system is unable to detect onDraw inside the specific operation, so the system can not do some optimization for us. If the View has a large amount of overlap, CPU and GPU resources will be wasted. In this case, we can use Canvas.cliprect () to help the system identify those visible areas.

This method specifies a rectangular area that will be drawn only within this area. Other areas will be ignored. Let’s further illustrate the use of OverDraw with a small Demo provided by Google.In the code below, the DroidCard class encapsulates the card information as follows.

public class DroidCard {

public int x;// Draw the starting point on the left
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(a);this.height = this.bitmap.getHeight();
 }
}
Copy the code

The code for customizing a View is as follows:

public class DroidCardsView extends View {
// The spacing between images
private int mCardSpacing = 150;
// Record the distance between the image and the left
private int mCardLeft = 10;

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

private Paint paint = new Paint(a);public DroidCardsView(Context context) {
super(context);
initCards(a); }public DroidCardsView(Context context, AttributeSet attrs) {
super(context, attrs);
initCards(a); }/** * Initializes the card collection */
protected void initCards(a){
Resources res = getResources(a); 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(a); }/** * draw DroidCard */
private void drawDroidCard(Canvas canvas, DroidCard c) {
canvas.drawBitmap(c.bitmap,c.x,0f,paint); }}Copy the code

Then, we run the code and turn on the phone’s Overdraw switch, which looks like this:As you can see, the pale red area has obviously been painted three times due to the overlap of the images. So how do you solve this problem? In fact, the analysis can be found that the bottom picture only needs to draw one-third, to ensure that the bottom two pictures only need to return one-third of the top picture completely drawn. The optimized code looks like this:

public class DroidCardsView extends View {

// The spacing between images
private int mCardSpacing = 150;
// Record the distance between the image and the left
private int mCardLeft = 10;

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

private Paint paint = new Paint(a);public DroidCardsView(Context context) {
super(context);
initCards(a); }public DroidCardsView(Context context, AttributeSet attrs) {
super(context, attrs);
initCards(a); }/** * Initializes the card collection */
protected void initCards(a){
Resources res = getResources(a); 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(a)- 1));
invalidate(a); }/** * draw the last DroidCard * @param canvas * @param c */
private void drawLastDroidCard(Canvas canvas,DroidCard c) {
canvas.drawBitmap(c.bitmap,c.x,0f,paint);
}

/** * draw DroidCard * @param canvas * @param mDroidCards * @param I */
private void drawDroidCard(Canvas canvas,List<DroidCard> mDroidCards,int i) {
DroidCard c = mDroidCards.get(i);
canvas.save(a); 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();
 }
}

Copy the code

In the above code, we use the Canvas clipRect method to cut out a region before drawing, so that when drawing, only the region is drawn, and the excess part is not drawn. Rerun the above code to look like the image below.

2.4 Hierarchy Viewer

The Hierarchy Viewer is a built-in tool in the Android Device Monitor that allows developers to measure the layout speed of each view in a layout Hierarchy and help developers find performance bottlenecks caused by view hierarchies. Hierarchy Viewer uses red, yellow, and green colors to distinguish the relative performance of Measure, Layout, and Executive.

Open the

  1. Connect the device to the computer. If a dialog box appears on the device asking you to allow USB debugging? , please click OK.
  2. Open your project in Android Studio, build and run the project on your device.
  3. Start the Android Device Monitor. Android Studio may display the Disable ADB Integration dialog box because only one process can connect to the Device at a time through ADB, and the Android Device Monitor is requesting a connection. Therefore, click Yes.
  4. From the menu bar, choose Window > Open Perspective and click Hierarchy View.
  5. Double-click the software package name of the application in the Windows TAB on the left. This populates 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 and avoid repeating nested layouts. If we write a deep Hierarchy, it will seriously increase the burden of CPU and cause serious performance lag. For the use of the Hierarchy Viewer, please refer to:Analyze the layout using the Hierarchy Viewer.

2.5 Memory Jitter

After we optimize the tree structure of view and Overdraw, we may still feel that our app is stalling and losing frames, or sliding slowly, etc. We need to check whether there is memory jitter. Memory jitter refers to the frequent blocking of UI threads caused by frequent memory creation and GC.

Android has a mechanism for automatically managing memory, but improper use of memory can still cause serious performance problems. Creating too many objects in the same frame is a particular concern. Creating too many objects in the same frame can cause the GC to go non-stop, and any operations of all threads will need to be paused until the GC is complete. A large number of non-stop GC operations can significantly consume frame interval time. If you do too many GC operations in the frame interval, the page will get stuck.In Android development, there are two main reasons for frequent GC operations:

  • Memory jitter means that a large number of objects are generated in a short period of time and released immediately.
  • A large number of objects exceeding the threshold in a short period of time and running out of memory can also trigger GC operations.

Android memory jitter can be detected using the Android Studio Profiler.Then, click record to record memory information and find the location of memory jitter. Of course, you can also directly use Jump to Source to locate the code location.

In order to avoid memory jitter, we need to avoid allocating objects in the for loop. We need to try to move the creation of objects out of the loop. We also need to pay attention to the onDraw method in the custom View. Avoid complex operations in the onDraw method and avoid creating objects. For those cases where the need to create objects is unavoidable, we can consider the object pool model to solve the problem of frequent creation and destruction, but note that objects in the object pool need to be released manually after use.

3. Memory optimization

3.1 Memory Management

In the previous Java Basics section, we also made a basic introduction to the Java memory management model, see: Android interview must ask Java basics

3.1.1 Memory Area

In the Java memory model, the memory area is divided into method area, heap, program counter, local method stack, virtual machine stack five areas, as shown in the following figure. Methods area

  • Thread shared areas for storing class information, static variables, constants, and code data compiled by the just-in-time compiler.
  • OOM occurs when memory allocation requirements cannot be met.

The heap

  • The thread shared area, which is the largest chunk of memory managed by the JAVA VIRTUAL machine, is created when the virtual machine is started.
  • Where object instances are held, almost all of which are allocated on the heap, the main area managed by GC.

The virtual machine stack

  • Thread private area, where each Java method execution creates a stack frame to store information about local variables, operand stacks, dynamic links, method exits, and so on. Method from the beginning of execution to the end of the process is the stack frame in the virtual machine stack process.
  • The local variable table stores basic data types, object references, and returnAddress types known at compile time. The required memory space is allocated at compile time, and the space to locally change tables in a frame when entering a method is fully determined and does not need to be changed at run time.
  • If the stack depth requested by the thread is greater than the maximum depth allowed by the VM, a SatckOverFlowError is raised.
  • An OutOfMemoryError is thrown if sufficient memory cannot be allocated during vm dynamic expansion.

Local method stack

  • It provides services for Native methods in virtual machines. There are no mandatory provisions on the language, data structure and usage mode used in the local method stack. Virtual machines can implement it by themselves.
  • The size of the memory area is not fixed and can be dynamically expanded as needed.

Program counter

  • A small memory space, thread private, that stores a bytecode line number indicator for execution by the current thread.
  • The bytecode interpreter changes the value of this counter to select the next bytecode instruction to execute: branch, loop, jump, and so on.
  • Each thread has a separate program counter
  • The only area in the Java virtual machine that is not OOM

3.1.2 Garbage collection

Mark clearing algorithm Mark clearing algorithm is mainly divided into two stages, first mark the need to recycle the object, and then how to mark the completion of the unified recovery of all marked objects; Disadvantages:

  • Efficiency problem: Both marking and cleaning processes are inefficient.
  • Space issues: There are many discrete memory fragments after the tag is cleared, which can lead to the problem of not finding enough contiguous space when large objects need to be allocated and having to trigger GC.

The replication algorithm divides the available memory into two pieces with the same size according to the space, and only uses one piece each time. When the memory is used up, the surviving objects are copied to another piece of memory, and then the whole object in this memory area is cleared. The entire half region is reclaimed at a time without causing fragmentation problems, which is simple and efficient. Disadvantages: Need to reduce the memory to half of the original, space cost is too high.

The tagging process is the same as the tagging cleaning algorithm, but the cleaning process does not clean the recyclable objects directly, but moves all the living objects like one end, and then concentrates the cleaning into memory outside the end boundary.

Generational collection algorithm Modern VM garbage collection algorithms use generational collection algorithms to collect garbage. Memory is divided into the new generation and the old generation based on the object life cycle, and the most appropriate collection algorithm is adopted based on the characteristics of each generation.

  • There are few living 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 at the cost of copying a small number of living objects.
  • There are many living objects in the old age and there is no extra space for allocation guarantee, so we must use the mark clearing algorithm and the mark sorting algorithm to recycle.

3.2 Memory Leakage

A memory leak is an object in memory that is not useful and cannot be reclaimed. It will cause memory jitter, reduce available memory, and result in frequent GC, lag, and OOM.

Here is a code that simulates a memory leak:

/** * Emulates 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);
        
        // Add a static class reference
        CallBackManager.addCallBack(this);
    }
    @Override
    protected void onDestroy(a) {
        super.onDestroy(a);// CallBackManager.removeCallBack(this);
    }
    @Override
    public void dpOperate(a) {
        // do sth
    }
Copy the code

When we use the Memory Profiler tool to look at the Memory curve, we find that the Memory keeps rising, as shown in the figure below.If you want to analyze and locate specific locations of memory leaks, you can use the MAT tool. First, use MAT tool to generate hprof file and click dump to convert the current memory information into hprof file. The generated file needs to be converted into a file that MAT can read. Run the conversion command to complete the conversion. The generated file is in the Android/ SDK /platorm-tools directory.

Hprof-conv Just generated the hprof file memory-mat.hprofCopy the code

Use MAT to open the hprof file you just converted, and then use Android Studio to open the hprof file, as shown below.Then click on [Historygram] in the panel and search for MemoryLeakActivity to see information about the leaked file.Then, look at all the reference objects and get the associated reference chain, as shown below. You can see that GC Roots are the CallBackManagerTherefore, we simply remove the CallBackManager reference when the Activity is destroyed.

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

Copy the code

Of course, the above is only an example of the use of MAT analysis tool, other memory leaks can be solved with MAT analysis tool.

3.3 Large picture memory optimization

A common solution to the memory leak problem encountered by loading large images in Android development is ARTHook for detecting improper images. As we know, there are two main ways to obtain Bitmap memory:

  • Through the getByteCount method, but you need to get it at run time
  • width * height *Memory per pixel*Compression ratio of the resource directory where the image resides

ARTHook method is an elegant way to get inappropriate images, less intrusive, but generally used offline due to compatibility issues. Using ARTHook requires the following dependencies to be installed:

Implementation 'me. Weishu: epic: 0.3.6'Copy the code

Then customize 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);

Copy the code

Finally, inject the Hook at Application initialization.

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()); }});Copy the code

3.4 Online Monitoring

3.4.1 General scheme

If the current memory size exceeds 80% of the maximum memory size, Dump (debug. dumpHprofData()) the current memory, upload the hprof file at an appropriate time, and manually analyze the file using the MAT tool.

Disadvantages:

  • A large Dump file is positively correlated with the usage time and object tree.
  • Large files lead to high upload failure rate and difficult analysis.

Solution 2 Brings LeakCannary online, adds a preset suspect point, monitors memory leaks at the suspected point, and sends the memory leaks back to the server.

Disadvantages:

  • With low generality, it is necessary to preset the doubt point, and the place without the preset doubt point cannot be monitored.
  • LeakCanary Analysis is time-consuming and memory consuming, and an OOM may occur.

3.4.2 LeakCannary transformation

The transformation mainly involves the following points:

  • Change the need to preset the doubt point to automatically find the doubt point, automatically set the doubt point in the object class which occupies a large memory in the previous memory.
  • LeakCanary Analyzes the leak link slowly. Therefore, it is modified to analyze only objects with a large Retain size.
  • The analysis process is OOM because LeakCannary will load all analysis objects into the memory during analysis. We can record the number and usage of analysis objects and cut the analysis objects so that they are not all loaded into the memory.

The transformation steps are as follows:

  1. General monitoring indicators: Standby memory, memory occupied by key modules, and OOM rate
  2. Monitor the NUMBER and time of GC in APP life cycle and key module interface life cycle
  3. Bring the custom LeakCanary online to automate the analysis of memory leaks online

4. Network optimization

4.1 Impact of network optimization

The network connection of App has a lot of influence on users, and in most cases it is very intuitive, directly affecting users’ experience of using the App. Traffic: The traffic consumption of an App is sensitive to users. After all, traffic costs money. Now most people have installed a traffic monitoring tool App on their mobile phones, which is used to monitor the traffic usage of the App. If this aspect of our App is not well controlled, it will give users a bad use experience. Quantity of electricity: The quantity of electricity is not so obvious to the user. The average user might not notice much. However, as in power optimization, network connection (RADIO) is a factor that has a great impact on power. So we have to pay attention. User waiting: that is user experience, good user experience, is the first step to retain users. If the App request waits for a long time, it will give users a feeling of network congestion and slow application response. If there is a comparison and a substitute, our App is likely to be abandoned by users mercilessly.

4.2 Network Analysis Tools

The tools for network analysis include Monitor and proxy tools.

2 Network Monitor

Android Studio’s built-in Monitor tool provides a Network Monitor to help developers with Network analysis. The following is a typical Network Monitor diagram.

  • Rx — R(ECIVE) indicates downlink traffic, that is, download reception.
  • Tx – -t (ransmit) Indicates upstream traffic, that is, upload and send.

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

4.2.2 Proxy Tools

Network agent tool has two functions. One is to intercept network request response packet and analyze network request. Another setting agent network is generally used for testing different network environments in mobile App development, such as Wifi/4G/3G/ weak network, etc.

Today, there are many proxy tools available, such as Wireshark, Fiddler, Charles, and more.

4.3 Network optimization scheme

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

  1. Reduce active time: reduce the frequency of network data acquisition, so as to reduce radio power consumption and control power consumption.
  2. Compressed packet size: Compressed packets can reduce traffic consumption and make each request faster.

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

4.3.1 Interface design

1. API design API design between App and server should consider the frequency of network requests, the state of resources, etc. So that the App can fulfill the business requirements and display the interface with fewer requests.

For example, register for login. There would normally be two apis, registration and login, but when designing the API we should include an implicit login for the registration interface. To avoid having to request a login interface once the App is registered.

2. Use Gzip compression

Gzip is used to compress request and Response, reducing the amount of data to be transmitted and thus reducing traffic consumption. When network requests are made using a network request framework such as Retrofit, Gzip is compressed by default.

3. Before using the Protocol Buffer, we used XML to transmit data, but later we used JSON instead of XML, largely for readability and reducing the amount of data. In game development, In order to ensure the accuracy and timeliness of data, Google introduced the Protocol Buffer data exchange format.

4. Obtain pictures with different resolutions according to the network situation. When we use Taobao or JINGdong, we will see that the application will obtain pictures with different resolutions according to the network situation, so as to avoid waste of traffic and improve user experience.

4.3.2 Proper Use of network cache

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

1. Package network requests

When the interface design does not meet our business needs. For example, maybe an interface needs to request multiple interfaces, or maybe the network is good and we want to get more data when we are on Wifi, etc. At this point, you can package some network requests, such as a list request, while getting the details of the item that has the most hits for the Header.

2. Monitoring device status In order to improve user experience, we can monitor the usage status of the device, and then execute network requests in combination with JobScheduler. For example, Splash Splash advertising images can be downloaded and cached locally when connected to Wifi. News App can do offline cache when charging and Wifi.

4.3.3 Weak network test & optimization

1. Weak network test There are several ways to simulate weak network test:

Android EmulatorIn general, we create and start the Android emulator to set the network speed and latency, as shown below.Then, we use the emulator command at startup as follows.

$emulator -netdelay gprs -netspeed gsm -avd Nexus_5_API_22
Copy the code

2. Network proxy tool Network proxy tool can also simulate the network situation. Take Charles as an example. Keep the mobile phone and PC on the same LAN. Set the proxy mode to manual in the advanced Settings of wifi Settings on the mobile phone.

5. Optimization of power consumption

In fact, if our app needs to play video, needs to get GPS information, or is a game app, the power consumption is serious. How do you determine which power consumption can be avoided, or which needs to be optimized? We can open up the power consumption chart of the phone and find that Honor of Kings has been used for more than 7 hours. At this point, the user has expected the power consumption of Honor of Kings.

5.1 Optimization Direction

If he finds an application that he doesn’t use at all, but consumes a lot of power, he will be ruthlessly killed by the system. So the first direction of power consumption optimization is to optimize the background power consumption of applications.

Knowing how the system calculates power consumption allows us to know what apps should not do in the background, such as long WakeLock fetching, WiFi and Bluetooth scanning, and background services. Why the first direction of power consumption optimization is to optimize the application background power consumption, because most manufacturers of pre-installation project requirements are the most stringent application background standby power consumption.

Of course, we won’t completely ignore the power consumption of the front desk, but standards will be relaxed a lot. Take a look at the picture below. If the system pops up this dialog box for your application, users may be able to tolerate it for wechat, but for most other applications, many users may directly add you to the list of background restrictions.

The second direction of power consumption optimization is to conform to the rules of the system, so that the system thinks your power consumption is normal.

Android P and above monitor the power consumption of the background through Android Vitals, so we need to comply with the rules of Android Vitals, the specific rules of which are as follows.It can be seen that the Android system is currently more concerned about background Alarm wake, background network, background WiFi scan and part of the long WakeLock to prevent system background sleep, because these may lead to power consumption problems.

5.2 Power consumption monitoring

5.2.1 Android Vitals

Android Vitals has several power monitoring schemes and rules to help us monitor power consumption.

  • Alarm Manager wakeUp Too many wakes
  • Frequent use of local wakeup locks
  • The background network usage is too high
  • Background WiFi Scans are too many

After using it for a while, I found it didn’t work that well. In the case of Alarm wakeup, Vitals uses more than 10 beats per hour as a rule. Since this rule cannot be modified, many times we may want to make a more detailed distinction between different system versions. Second, as Battery Historian, we can only get the components marked by Wakeup, not the application stack, whether the phone is being charged at the time, the remaining Battery, etc. Here’s what Wakeup got.

This is also true for networks, WiFi scans and WakeLock. Although Vitals helped us narrow the scope of investigation, we still could not confirm the specific cause of the problem.

5.3 How Do I Monitor Power Consumption

As mentioned earlier, Android Vitals isn’t that easy to use and doesn’t work at all for domestic apps. So what should our power-consumption monitoring systems monitor, and how should they do it? First of all, let’s take a look at how electricity consumption monitoring should be done.

  • Monitoring information: in simple terms, what the system cares about, we monitor what, and should be the main power consumption monitoring. Alarm wakeup, WakeLock, WiFi Scans, and Network are all necessary. Others can be identified based on the application requirements. For maps, background GPS is allowed; If it is a pedometer application, the background Sensor is not too big a problem.
  • Site information: Monitoring systems want to obtain complete stack information such as which line of code initiated WiFi SCANS and which line requested WakeLock. Information such as whether the phone was charging at the time, the battery level of the phone, application foreground and background time, and CPU status can also help us troubleshoot certain problems.
  • Refining rules: Finally, we need to abstract the monitoring content into rules. Of course, the monitoring items or parameters vary from application to application. Since the specifics of each application are different, simple rules can be used as a reference.

5.3.2 Hook plan

After defining what we need to monitor and the specific rules, let’s look at the technical solution of power monitoring. Let’s take a look at Hook scheme first. The advantage of Hook scheme is that the user access is very simple without modifying the code, and the cost of access is relatively low. Let me use a few common rules as examples to see how Java Hooks can be used for monitoring purposes.

WakeLock WakeLock is used to prevent CPU, screen and even keyboard sleep. Alarm and JobService also apply for WakeLock to perform background CPU operations. The core control code for WakeLock is in PowerManagerService. The implementation method is very simple, as shown below.

// Proxy PowerManagerService ProxyHook().proxyhook (context.getSystemService(context.power_service), "mService", this); @Override public void beforeInvoke(Method method, Object[] args) {// Apply Wakelock if (method.getName().equals("acquireWakeLock")) {if (isAppBackground()) { } else {// Apply foreground logic, Else if (method.getName().equals("releaseWakeLock")) {// Free logic}}Copy the code

2. Alarm Alarm is used to perform timed repetitive tasks. There are four types of Alarm Alarm, including ELAPSED_REALTIME_WAKEUP and RTC_WAKEUP. Similarly, the core control logic of Alarm is in AlarmManagerService, which is implemented as follows.

// Proxy AlarmManagerService new ProxyHook().proxyhook (context.getSystemService (context.alarm_service), "mService", This); public void beforeInvoke(Method method, Object[] args) {// Set Alarm if (method.getName().equals("set")) { Else if (method.getName().equals("remove")) {// Clear logic}}Copy the code

In addition to WakeLock and Alarm, for background cpus, we can use a caton-related monitoring method; For the background network, we can also monitor the relevant methods through the network; For GPS monitoring, we can use Hook agent LOCATION_SERVICE; For sensors, part of the information can be retrieved through “mSensorListeners” in the Hook SENSOR_SERVICE.

Finally, we save the stack information to which the resource was requested. When a rule is triggered to report a problem, you can upload the collected stack information, whether the battery is charged, CPU information, and time before and after application to the background.

5.3.3 pile method

Using the Hook method is simple, but some rules may not be easy to find the right Hook point, and after Android P, many Hook points are not supported. For compatibility, the first thing that came to my mind was piling. Take WakeLock as an example:

Public class WakelockMetrics {// Wakelock request public void acquire(PowerManager.wakelock Wakelock) {wakelock.acquire (); Public void release(powerManager.wakelock Wakelock, int flags) { wakelock.release(); // Add Wakelock release monitoring logic here}}Copy the code

If you’ve been studying Battery consumption, you probably know that Facebook’s open source library, Battery-Metrics, monitors Alarm, WakeLock, Camera, CPU, Network, and more. It also collects information on charging status and battery level. Unfortunately, battery-Metrics only provides a set of basic classes, and developers still need to modify a lot of the source code to actually use them.

6. Optimization of installation package

At present, there are dozens of apps in the market, and hundreds of apps in the market. The smaller the installation package, save traffic when downloading, good user experience, faster download, faster installation. So what can we do to optimize the installation package?

6,1 common optimization strategies

1. Clean up useless resourcesIn the process of Android packaging, if the code involves references to resources and codes, it will be packaged into the App. In order to prevent such discarded codes and resources from being packaged into the App, we need to timely clean up these useless codes and resources to reduce the size of the App. To clean it up, click android Studio’s “Refactor” -> “Remove unused Resource”, as shown below. 2. Use Lint

The Lint tool is still useful in that it gives us points to optimize:

  • Detect unused layouts and delete them
  • Delete unused resources
  • It is recommended that some unused characters in string.xml be deleted as well

Set shrinkResources to true in Build. gradle to remove shrinkResources automatically. If you set shrinkResources to true in build.gradle, you can remove shrinkResources automatically. Note that “useless” here means that calling all parent functions of the image ends up as obsolete code, and shrinkResources True can only remove cases without any parent function calls.

    android {
        buildTypes {
            release {
                shrinkResources true
            }
        }
    }
Copy the code

In addition, most applications don’t need to support internationalization for dozens of languages, and you can remove language support files.

6.2 Resource Compression

In Android development, there are a lot of built-in images, and these images take up a lot of volume, so to reduce the size of the package, we can compress resources. Common methods are:

  1. Use compressed images: Use compressed images to reduce the size of your App.
  2. Just one set of images: For most logarithmic apps, just one set of blueprints is enough.
  3. Use JPG images without alpha: For large opaque images, JPG will have a significant, though not absolute, advantage over PNG size, often reduced by more than half.
  4. Using Tinypng lossy compression: supports uploading PNG images to the official website for compression, and then downloading and saving. Under the condition of maintaining the alpha channel, the compression of PNG can reach 1/3, and the loss of compression is basically indistinguisable with the naked eye.
  5. Use webP format: WebP supports transparency, compression ratio, and takes up less volume than JPG images. Since Android 4.0+ native support, but does not support including transparency, until Android 4.2.1+ does not support the display of transparency webP, when using special care.
  6. Using SVG: Vector images are made up of dots and lines, and unlike bitmaps, they remain sharp when magnified, and use vector images to save 30-40% of space compared to bitmap designs.
  7. Compress the packaged image: Use the 7zip compression method to compress the image, and you can directly use the open source AndResGuard compression scheme of wechat.
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 {an artifact = 'com. Tencent. Mm: sevenzip: 1.1.7' / / path = "/ usr/local/bin / 7 za"}}Copy the code

6.3 Dynamic Resource Loading

In front-end development, dynamic loading of resources can effectively reduce the size of APK. In addition, only support for mainstream architecture, such as ARM, MIPS and x86 architecture can be considered not support, which can greatly reduce the size of APK.

Of course, in addition to the optimization scenarios mentioned above, the optimization of Android App also includes storage optimization, multithreading optimization and crash handling.