Android method to get the application size

  • 2020-05-19 05:53:30
  • OfStack

Today, I encountered a problem trying to get the size of an installed package. After searching for 1, I found that there is an getPackageSizeInfo method in PackageManager. Unfortunately, it belongs to hide, and when it is executed, it will call back the result to onGetStatsCompleted method of IPackageStatsObserver. Later, I wanted to directly calculate the size of apk in /data/app and /system/app, but sometimes I encountered permission problems and needed root to get the size. Later, I remembered that there was an application management in the system setup, which listed the space occupied by all the programs, the data size and the cache size. Well, that's the breakthrough.
I wrote an Context article to get other packages before, this thing is really useful, this combination of reflection, can do a lot of amazing things, like this one today.

The code:

Java code


package chroya.demo;  

import java.lang.reflect.Constructor;  
import java.lang.reflect.Field;  
import java.lang.reflect.InvocationTargetException;  
import java.util.concurrent.CountDownLatch;  

import android.app.Activity;  
import android.content.Context;  
import android.content.pm.PackageStats;  
import android.content.pm.PackageManager.NameNotFoundException;  
import android.os.Bundle;  
import android.os.Handler;  
import android.os.Message;  
import android.util.Log;  

public class Main extends Activity {  
    private PackageStats ps;  

    public void getPackageStats(String packageName) {  
        try {  
            // To obtain setting The package of Context  
            Context mmsCtx = createPackageContext("com.android.settings",  
                    Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);  
            // use setting the classloader loading com.android.settings.ManageApplications class   
            Class<?> maClass = Class.forName("com.android.settings.ManageApplications", true, mmsCtx.getClassLoader());  
            // That created it 1 An object   
            Object maObject = maClass.newInstance();  

            /* 
             *  Will the private domain mPm The assignment. because mPm in SizeObserver the invokeGetSize It's used,  
             *  Because they didn't do it onCreate It's not initialized, so it's initialized here.  
             */  
            Field f_mPm = maClass.getDeclaredField("mPm");  
            f_mPm.setAccessible(true);              
            f_mPm.set(maObject, mmsCtx.getPackageManager());  

            /* 
             *  to mHandler The assignment is redefined Handler In order to receive SizeObserver the  
             * onGetStatsCompleted In the callback method dispatch Take the message PackageStats Object.  
             * */  
            Field f_mHandler = maClass.getDeclaredField("mHandler");  
            f_mHandler.setAccessible(true);  
            f_mHandler.set(maObject, new Handler() {  
                  public void handleMessage(Message msg) {  
                      if(msg.what == 1) {  
                          // Obtained here PackageStats object   
                          ps = (PackageStats) msg.getData().getParcelable("ApplicationPackageStats");                           
                          Log.d("", ""+ps.codeSize);                            
                      }  
                  }  
            });  

            // Loading inner class SizeObserver  
            Class<?> sizeObserverClass = Class.forName("com.android.settings.ManageApplications$SizeObserver", true, mmsCtx.getClassLoader());  
            Constructor sizeObserverConstructor = sizeObserverClass.getDeclaredConstructors()[0];  
            sizeObserverConstructor.setAccessible(true);  
            /* 
             *  create SizeObserver Object, two parameters, first 1 An object of an external class,  
             *  That is ManageApplications The object, the first 2 One is msgId , that is,  
             *  message-distributing id , with Handler The received msgId1 The sample.  
             * */  
            Object soObject = sizeObserverConstructor.newInstance(maObject, 1);  
            // perform invokeGetSize methods   
            sizeObserverClass.getMethod("invokeGetSize", String.class,  
                    CountDownLatch.class).invoke(soObject, packageName, new CountDownLatch(1));           
        } catch (NameNotFoundException e) {  
            e.printStackTrace();  
        } catch (ClassNotFoundException e) {  
            e.printStackTrace();  
        } catch (IllegalAccessException e) {  
            e.printStackTrace();  
        } catch (IllegalArgumentException e) {  
            e.printStackTrace();  
        } catch (SecurityException e) {  
            e.printStackTrace();  
        } catch (InvocationTargetException e) {  
            e.printStackTrace();  
        } catch (NoSuchMethodException e) {  
            e.printStackTrace();  
        } catch (InstantiationException e) {  
            e.printStackTrace();  
        } catch (NoSuchFieldException e) {  
            e.printStackTrace();  
        }  
    }  

    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);    
        getPackageStats("chroya.demo");         
    }  
}  

The comments are all in the code, so a little bit of 1 should make sense.
Once you get the PackageStats object, you can get the size of the application footprint, the size of the data, and the size of the cache.

Besides, it's only hack code after all, it's not universal. The limitation of this code is that only 1.5 will work, and it won't work if someone removes the setting package. To write common code for each version of SDK, you must look at the setting package for each version to see how the code has changed, and then write one method for each version, ok, according to the ideas given above.

If you want to succeed, you must first believe in yourself and then win the trust of your friends.


Related articles: