Repository: cundong/MemoryMonitor Branch: master Commit: d180d00a91dc Files: 64 Total size: 85.8 KB Directory structure: gitextract_vdzligds/ ├── .gitignore ├── README.md ├── app/ │ ├── build.gradle │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── cundong/ │ │ └── memory/ │ │ ├── App.java │ │ ├── Constants.java │ │ ├── MainActivity.java │ │ ├── demo/ │ │ │ ├── right/ │ │ │ │ ├── AsyncTaskOutOfMemoryActivity.java │ │ │ │ ├── HandlerOutOfMemoryActivity.java │ │ │ │ ├── OneStaticOutOfMemoryActivity.java │ │ │ │ ├── OneThreadOutOfMemoryActivity.java │ │ │ │ ├── TwoStaticOutOfMemoryActivity.java │ │ │ │ └── TwoThreadOutOfMemoryActivity.java │ │ │ └── wrong/ │ │ │ ├── AsyncTaskOutOfMemoryActivity.java │ │ │ ├── HandlerOutOfMemoryActivity.java │ │ │ ├── MemoryChurnActivity.java │ │ │ ├── StaticOutOfMemoryActivity.java │ │ │ └── ThreadOutOfMemoryActivity.java │ │ ├── service/ │ │ │ ├── CoreService.java │ │ │ └── EmptyService.java │ │ └── util/ │ │ ├── BitmapUtils.java │ │ ├── Logger.java │ │ ├── MemoryUtil.java │ │ └── ViewUtils.java │ └── res/ │ ├── anim/ │ │ ├── slide_down.xml │ │ └── slide_up.xml │ ├── layout/ │ │ ├── activity_demo.xml │ │ ├── activity_main.xml │ │ ├── activity_outofmemory_list.xml │ │ ├── activity_test.xml │ │ └── float_view.xml │ ├── menu/ │ │ └── main.xml │ ├── values/ │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── values-v11/ │ │ └── styles.xml │ ├── values-v14/ │ │ └── styles.xml │ └── values-w820dp/ │ └── dimens.xml ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── magnet/ │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── premnirmal/ │ │ └── Magnet/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── premnirmal/ │ │ └── Magnet/ │ │ ├── FlingListener.java │ │ ├── IconCallback.java │ │ ├── Magnet.java │ │ ├── RemoveView.java │ │ └── SimpleAnimator.java │ └── res/ │ ├── anim/ │ │ ├── slide_down.xml │ │ └── slide_up.xml │ ├── drawable/ │ │ └── bottom_shadow.xml │ ├── layout/ │ │ └── x_button_holder.xml │ ├── menu/ │ │ └── paranormal.xml │ └── values/ │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # built application files *.ap_ # files for the dex VM *.dex # Java class files *.class # generated files bin/ gen/ # Local configuration file (sdk path, etc) local.properties # Proguard folder generated by Eclipse proguard/ # Ignore gradle files .gradle/ build/ # Eclipse project files .classpath .project .settings/ # Intellij project files *.iml *.ipr *.iws .idea/ # Mac system files .DS_Store ================================================ FILE: README.md ================================================ # MemoryMonitor 一个给开发者使用的Android App内存清理、监控工具。 主要包括三部分内容: * 内存清理 通过内存清理可以模拟系统内存不足时对进程的回收。 * Pss监控 通过内存监控可以监控指定应用程序使用的total Pss以及当前手机的内存使用情况,从而检测该应用是否存在内存泄漏。 * 内存优化 整理了一些关于内存优化的tips,以及一些可能导致内存溢出的场景示例,包含错误的写法和正确的写法。 ## 1.内存清理 类似各种手机管家的 **加速球**,获取系统已用内存比率、可用内存大小,一键清理。 可以用于测试自己开发的Activity、Fragment健壮性,模拟Activity、Fragment被回收的场景,测试自己的程序是否完好的保存、恢复当前场景。 比如:打开你开发的某个Activity、Fragment,切到后台,清理一次内存,在将其切回前台后,看会不会出现空指针异常,以及程序状态是否被恢复。 ## 2.Pss监控 Android 系统中的内存和Linux系统一样,存在着大量的共享内存。每个APP占内存会有私有和公共的两部分,我们可以通过App的Pss值,可以获取到这两部分内存。 Pss(Proportional Set Size):实际使用的物理内存,即:自身应用占有的内存+共享内存中比例分配给这个应用的内存。 通过该程序,每隔1秒,获取一次被监控App的Total Pss值。 使用某个功能(可能会导致OOM的那些都要试试),查看Pss是否飙升,或者使用过许久都没有降低。 如果使用后飙升并且长时间都降不下来,那就说明肯定会导致OOM(对象使用过之后还被引用着未释放),如果使用之后Total Pss飙升,但是使用过之后能降下来,也可能会导致OOM,我们还是需要去一点一点排查是什么原因导致的。 如果使用后飙升并且长时间都降不下来,我们就需要 [使用MAT来进一步分析问题所在](http://blog.csdn.net/xiaanming/article/details/42396507)。 此处提到的Pss,也可以使用adb命令 > adb shell dumpsys meminfo *your packageName* 查看: ![total Pss](https://github.com/cundong/MemoryMonitor/blob/master/screenshot/total%20Pss.png?raw=true) ## 3.内存优化 Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般比较小(最低端的设备16M,后来出的设备变成了24M,48M等等),因此我们所能利用的内存空间是有限的。如果我们使用内存占用超过了一定的限额后就会出现OutOfMemory的错误。 可能会导致内存溢出的情况有以下几种: ### 对静态变量的错误使用 如果一个变量为static变量,它就属于整个类,而不是类的具体实例,所以static变量的生命周期是特别的长,如果static变量引用了一些资源耗费过多的实例,例如Context,就有内存溢出的危险。 [Google开发者博客,给出了一个例子](http://android-developers.blogspot.jp/2009/01/avoiding-memory-leaks.html),专门介绍长时间引用Context导致内存溢出的情况。 示例代码: ```java private static Drawable sBackground; @Override protected void onCreate(Bundle state) { super.onCreate(state); TextView textView = new TextView(this); textView.setText("Leaks are bad"); if (sBackground == null) { sBackground = getResources().getDrawable(R.drawable.large_bitmap); } textView.setBackgroundDrawable(sBackground); setContentView(textView); } ``` 这种情况下,静态的sBackground变量,虽然没有显式的持有Context的引用,但当我们执行`view.setBackgroundDrawable(Drawable drawable);`的时候,drawable 对象会将当前view设置为一个回调,通过 `View.setCallback(this)` 方法。 具体可见View类的源码: ``` public void setBackgroundDrawable(Drawable background) { //... if (mBackground == null || mBackground.getMinimumHeight() != background.getMinimumHeight() || mBackground.getMinimumWidth() != background.getMinimumWidth()) { requestLayout = true; } background.setCallback(this); if (background.isStateful()) { background.setState(getDrawableState()); } background.setVisible(getVisibility() == VISIBLE, false); mBackground = background; //... } ``` `background.setCallback(this);` 代码块就是我们说的设置回调。 所以,这种情况就会存在这么一个隐式的引用链:Drawable持有View,而View持有Context,sBackground 是静态的,生命周期特别的长,于是就会导致了Context的溢出。 解决办法: 1.不用activity的context 而是用Application的Context; ```java private static Drawable sBackground; @Override protected void onCreate(Bundle state) { super.onCreate(state); TextView textView = new TextView(this.getApplication()); textView.setText("Leaks are bad"); if (sBackground == null) { sBackground = getResources().getDrawable(R.drawable.large_bitmap); } textView.setBackgroundDrawable(sBackground); setContentView(textView); } ``` 2.在onDestroy()方法中,解除Activity与Drawable的绑定关系,从而去除Drawable对Activity的引用,使Context能够被回收; ```java @Override protected void onDestroy() { super.onDestroy(); ViewUtils.unbindDrawables(findViewById(android.R.id.content)); System.gc(); } public static void unbindDrawables(View view) { if (view.getBackground() != null) { view.getBackground().setCallback(null); } if (view instanceof ViewGroup) { for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { unbindDrawables(((ViewGroup) view).getChildAt(i)); } ((ViewGroup) view).removeAllViews(); } } ``` ### 长周期内部类、匿名内部类长时间持有外部类引用导致相关资源无法释放 长周期内部类、匿名内部类,如Handler,Thread,AsyncTask等。 HandlerOutOfMemoryActivity所示的是Handler引发的内存溢出。 ThreadOutOfMemoryActivity所示的是Thread引发的内存溢出。 AsyncTaskOutOfMemoryActivity所示的时AsyncTask引发的内存溢出。 ### Bitmap导致的内存溢出 一般是因为尝试加载过大的图片到内存,或者是内存中已经存在的过多的图片,从而导致内存溢出。 ### 数据库Cursor未关闭 正常情况下,如果查询得到的数据量较小时不会有内存问题,而且虚拟机能够保证Cusor最终会被释放掉,如果Cursor的数据量特表大,特别是如果里面有Blob信息时,应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理。 ### 单例模式引用Context导致的内存泄露 如果在某个Activity中使用 `Singleton instance = Singleton.getInstance(this);` 就会造成该Activity一直被 `Singleton` 引用着,不能释放。这时候,正确的做法是使用 `getApplicationContext()` 来替代 `Activity的Context` ,这样就能避免内存泄露。 ### 代码中一些细节 >* 尽量使用9path >* Adapter要使用convertView >* 各种监听,广播等,注册的同时要记得取消注册 >* 使用完对象要及时销毁,能使用局部变量的不要使用全局变量,功能用完成后要去掉对他的引用 >* 切勿在循环调用的地方去产生对象,比如在getview()里new OnClicklistener(),这样的话,拖动的时候会new大量的对象出来。 >* 使用Android推荐的数据结构,比如HashMap替换为SparseArray,避免使用枚举类型(在Android平台,枚举类型的内存消耗是Static常量的的2倍) >* 使用lint工具优化工程 >* 字符串拼接使用StringBuilder或者StringBuffer >* 尽量使用静态匿名内部类,如果需要对外部类的引用,使用弱引用 >* for循环的使用 用 `final int size = array.length; for(int i = 0; i< size;i++)` 来替代: `for(int i =0;i < array.length;i++) ` 最后,整理了一些开发中可能会导致内存溢出的场景,放在com.cundong.memory.demo.wrong中,并且给出了优化方法,放在com.cundong.memory.demo.right中。 ## 4.截图 ![截屏][1] [1]: https://raw.githubusercontent.com/cundong/MemoryMonitor/master/screenshot/app.png ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 23 buildToolsVersion "23.0.2" defaultConfig { applicationId "com.cundong.memory" minSdkVersion 14 targetSdkVersion 23 versionCode 2 versionName "1.2" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile project(':magnet') testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.android.support:design:23.1.1' compile 'com.jaredrummler:android-processes:1.0.3' } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/cundong/memory/App.java ================================================ package com.cundong.memory; import android.app.Application; import android.content.Context; public class App extends Application { private static Context mContext; public static Context getAppContext() { return mContext; } @Override public void onCreate() { super.onCreate(); mContext = this; } } ================================================ FILE: app/src/main/java/com/cundong/memory/Constants.java ================================================ package com.cundong.memory; import java.util.ArrayList; public class Constants { public static final boolean SHOW_MEMORY_CLEAR = false; // TODO /** * 此处,改为被监控 total Pss 的 processName 列表 *

* 微信: * com.tencent.mm * com.tencent.mm:TMAssistantDownloadSDKService * com.tencent.mm:push * com.tencent.mm:cuploader * com.tencent.mm:nospace * com.tencent.mm:tools * com.tencent.mm:sandbox * com.tencent.mm:exdevice */ public static final ArrayList PROCESS_NAME_LIST = new ArrayList(); static { PROCESS_NAME_LIST.add("com.tencent.mm"); PROCESS_NAME_LIST.add("com.tencent.mm:TMAssistantDownloadSDKService"); PROCESS_NAME_LIST.add("com.tencent.mm:push"); PROCESS_NAME_LIST.add("com.tencent.mm:cuploader"); PROCESS_NAME_LIST.add("com.tencent.mm:nospace"); PROCESS_NAME_LIST.add("com.tencent.mm:tools"); PROCESS_NAME_LIST.add("com.tencent.mm:sandbox"); PROCESS_NAME_LIST.add("com.tencent.mm:exdevice"); } } ================================================ FILE: app/src/main/java/com/cundong/memory/MainActivity.java ================================================ package com.cundong.memory; import android.annotation.TargetApi; import android.app.ActivityManager; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.Settings; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.Toast; import com.cundong.memory.service.CoreService; import com.cundong.memory.service.EmptyService; import com.cundong.memory.util.MemoryUtil; import java.util.List; public class MainActivity extends AppCompatActivity { private static final int OVERLAY_PERMISSION_REQ_CODE = 1; private Button mButton1, mButton2, mButton3, mButton4; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton1 = (Button) findViewById(R.id.button1); mButton2 = (Button) findViewById(R.id.button2); mButton3 = (Button) findViewById(R.id.button3); mButton4 = (Button) findViewById(R.id.button4); mButton3.setVisibility(Constants.SHOW_MEMORY_CLEAR ? View.VISIBLE : View.GONE); mButton4.setVisibility(Constants.SHOW_MEMORY_CLEAR ? View.VISIBLE : View.GONE); mButton1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try2StartMonitor(); } }); mButton2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, CoreService.class); intent.putExtra("action", 2); startService(intent); finish(); } }); mButton3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getApplicationContext(), "clearMemory", Toast.LENGTH_LONG).show(); MemoryUtil.clearMemory(getApplicationContext()); } }); mButton4.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, EmptyService.class); startService(intent); } }); } @TargetApi(Build.VERSION_CODES.M) private void try2StartMonitor() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) { Toast.makeText(this, R.string.permission_err, Toast.LENGTH_LONG).show(); Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())); startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE); } else { Intent intent = new Intent(MainActivity.this, CoreService.class); intent.putExtra("action", 1); startService(intent); finish(); } } @Override protected void onResume() { super.onResume(); boolean isServiceRunning = isServiceRunning(getPackageName() + ".service.CoreService"); mButton1.setVisibility(isServiceRunning ? View.GONE : View.VISIBLE); mButton2.setVisibility(isServiceRunning ? View.VISIBLE : View.GONE); } @TargetApi(Build.VERSION_CODES.M) @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == OVERLAY_PERMISSION_REQ_CODE) { if (!Settings.canDrawOverlays(this)) { Toast.makeText(this, R.string.permission_err, Toast.LENGTH_SHORT).show(); } else { Intent intent = new Intent(MainActivity.this, CoreService.class); intent.putExtra("action", 1); startService(intent); finish(); } } } /** * 使用Application context来调用getSystemService,避免内存泄漏 * * @param className * @return */ private boolean isServiceRunning(String className) { ActivityManager activityManager = (ActivityManager) App.getAppContext().getSystemService(Context.ACTIVITY_SERVICE); List serviceList = activityManager.getRunningServices(100); if (serviceList == null || serviceList.size() <= 0) { return false; } for (ActivityManager.RunningServiceInfo serviceInfo : serviceList) { String serviceName = serviceInfo.service.getClassName(); if (serviceName.equals(className)) { return true; } } return false; } } ================================================ FILE: app/src/main/java/com/cundong/memory/demo/right/AsyncTaskOutOfMemoryActivity.java ================================================ package com.cundong.memory.demo.right; import java.lang.ref.WeakReference; import android.app.Activity; import android.graphics.Bitmap; import android.os.AsyncTask; import android.os.Bundle; import android.widget.ImageView; import com.cundong.memory.util.BitmapUtils; import com.cundong.memory.R; /** * AsyncTask引发的内存溢出解决办法 * * BitmapWorkerTask内部采用弱引用保存Context引用 * */ public class AsyncTaskOutOfMemoryActivity extends Activity { private ImageView mImageView; @Override protected void onCreate(Bundle state) { super.onCreate(state); this.setContentView(R.layout.activity_demo); mImageView = (ImageView) findViewById(R.id.image); BitmapWorkerTask task = new BitmapWorkerTask(mImageView); task.execute(R.drawable.large_bitmap); } class BitmapWorkerTask extends AsyncTask { private final WeakReference imageViewReference; private int data = 0; public BitmapWorkerTask(ImageView imageView) { // Use a WeakReference to ensure the ImageView can be garbage collected imageViewReference = new WeakReference<>(imageView); } // Decode image in background. @Override protected Bitmap doInBackground(Integer... params) { data = params[0]; return BitmapUtils.decodeSampledBitmapFromResource(getResources(), data, 100, 100); } // Once complete, see if ImageView is still around and set bitmap. @Override protected void onPostExecute(Bitmap bitmap) { if (imageViewReference != null && bitmap != null) { final ImageView imageView = imageViewReference.get(); if (imageView != null) { imageView.setImageBitmap(bitmap); } } } } } ================================================ FILE: app/src/main/java/com/cundong/memory/demo/right/HandlerOutOfMemoryActivity.java ================================================ package com.cundong.memory.demo.right; import java.lang.ref.WeakReference; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import com.cundong.memory.R; /** * 解决Handler引发的内存溢出 * * 不使用非静态的内部类,改为使用静态内部类,当静态内部类中需要调用外部的Activity时,改用弱引用。 * */ public class HandlerOutOfMemoryActivity extends Activity { private final MyHandler mHandler = new MyHandler(this); private static class MyHandler extends Handler { private final WeakReference mActivity; public MyHandler(HandlerOutOfMemoryActivity activity) { mActivity = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { HandlerOutOfMemoryActivity activity = mActivity.get(); if (activity != null) { /* ... */ } } } private static final Runnable sRunnable = new Runnable() { @Override public void run() { /* ... */ } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.activity_outofmemory_list); mHandler.postDelayed(sRunnable, 1000 * 60 * 10); finish(); } } ================================================ FILE: app/src/main/java/com/cundong/memory/demo/right/OneStaticOutOfMemoryActivity.java ================================================ package com.cundong.memory.demo.right; import android.app.Activity; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.widget.TextView; import com.cundong.memory.R; /** * 解决static变量引发的内存溢出 * * 1.不用activity的context 而是用application的context * */ public class OneStaticOutOfMemoryActivity extends Activity { private static Drawable sBackground; @Override protected void onCreate(Bundle state) { super.onCreate(state); TextView label = new TextView(this.getApplication()); label.setText("Leaks are bad"); if (sBackground == null) { sBackground = getResources().getDrawable(R.drawable.large_bitmap); } label.setBackgroundDrawable(sBackground); setContentView(label); } } ================================================ FILE: app/src/main/java/com/cundong/memory/demo/right/OneThreadOutOfMemoryActivity.java ================================================ package com.cundong.memory.demo.right; import java.lang.ref.WeakReference; import android.app.Activity; import android.content.Context; import android.os.Bundle; import com.cundong.memory.R; /** * 解决Thread引发的内存溢出 * * 解决办法: * * 在线程内部采用弱引用保存Context引用 * */ public class OneThreadOutOfMemoryActivity extends Activity { private Context mContext; @Override protected void onCreate(Bundle state) { super.onCreate(state); this.setContentView(R.layout.activity_demo); mContext = this.getApplicationContext(); new MyThread().start(); } private void releaseZip(WeakReference context){ /* do something */ } private class MyThread extends Thread { @Override public void run() { super.run(); try { Thread.sleep(1000 * 60 * 2); } catch (InterruptedException e) { e.printStackTrace(); } /* do something */ releaseZip(new WeakReference(mContext)); } } } ================================================ FILE: app/src/main/java/com/cundong/memory/demo/right/TwoStaticOutOfMemoryActivity.java ================================================ package com.cundong.memory.demo.right; import android.app.Activity; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.widget.TextView; import com.cundong.memory.util.ViewUtils; import com.cundong.memory.R; /** * 解决static变量引发的内存溢出 * * 2.在 onDestroy() 方法中,解除 Activity 和 biamap(drawble)的绑定关系,从而去除bitmap对activity 引用,让系统适时的去回收 * */ public class TwoStaticOutOfMemoryActivity extends Activity { private static Drawable sBackground; @Override protected void onCreate(Bundle state) { super.onCreate(state); TextView label = new TextView(this); label.setText("Leaks are bad"); if (sBackground == null) { sBackground = getResources().getDrawable(R.drawable.large_bitmap); } label.setBackgroundDrawable(sBackground); setContentView(label); } @Override protected void onDestroy() { super.onDestroy(); ViewUtils.unbindDrawables(findViewById(android.R.id.content)); System.gc(); } } ================================================ FILE: app/src/main/java/com/cundong/memory/demo/right/TwoThreadOutOfMemoryActivity.java ================================================ package com.cundong.memory.demo.right; import java.lang.ref.WeakReference; import android.app.Activity; import android.content.Context; import android.os.Bundle; import com.cundong.memory.R; /** * 解决Thread引发的内存溢出 * * 1.MyThread改为静态内部类 */ public class TwoThreadOutOfMemoryActivity extends Activity { @Override protected void onCreate(Bundle state) { super.onCreate(state); this.setContentView(R.layout.activity_demo); new MyThread(this).start(); } private static void releaseZip(Context context){ /* do something */ } private static class MyThread extends Thread { private final WeakReference mActivity; public MyThread(TwoThreadOutOfMemoryActivity activity) { mActivity = new WeakReference<>(activity); } @Override public void run() { super.run(); try { Thread.sleep(1000 * 60 * 2); } catch (InterruptedException e) { e.printStackTrace(); } /* do something */ releaseZip(mActivity.get()); } } } ================================================ FILE: app/src/main/java/com/cundong/memory/demo/wrong/AsyncTaskOutOfMemoryActivity.java ================================================ package com.cundong.memory.demo.wrong; import android.app.Activity; import android.graphics.Bitmap; import android.os.AsyncTask; import android.os.Bundle; import android.widget.ImageView; import com.cundong.memory.util.BitmapUtils; import com.cundong.memory.R; /** * AsyncTask引发的内存溢出 * * 当前情况: * * 1. * 内部类BitmapWorkerTask,持有对外部AsyncTaskOutOfMemoryActivity的隐式引用 * * 2. * 如果我们切换横竖屏,默认就会销毁当前Activity,而这个Activity却被BitmapWorkerTask所持有 * * 于是就出现了溢出。 * * * 解决办法: * * BitmapWorkerTask内部采用弱引用保存Context引用 * */ public class AsyncTaskOutOfMemoryActivity extends Activity { private ImageView mImageView; @Override protected void onCreate(Bundle state) { super.onCreate(state); this.setContentView(R.layout.activity_demo); mImageView = (ImageView) findViewById(R.id.image); BitmapWorkerTask task = new BitmapWorkerTask(); task.execute(R.drawable.large_bitmap); } class BitmapWorkerTask extends AsyncTask { private int data = 0; // Decode image in background. @Override protected Bitmap doInBackground(Integer... params) { data = params[0]; return BitmapUtils.decodeSampledBitmapFromResource(getResources(), data, 100, 100); } // Once complete, see if ImageView is still around and set bitmap. @Override protected void onPostExecute(Bitmap bitmap) { if (bitmap != null) { mImageView.setImageBitmap(bitmap); } } } } ================================================ FILE: app/src/main/java/com/cundong/memory/demo/wrong/HandlerOutOfMemoryActivity.java ================================================ package com.cundong.memory.demo.wrong; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import com.cundong.memory.R; /** * Handler引发的内存溢出 * * 1. * 当一个Android应用启动的时候,会自动创建一个供主线程使用的Looper实例,这个Looper实例负责一个一个的处理消息队列中的消息对象,它的生命周期和当前应用 * 的生命周期是一样的。 * * 2. * 当Handler在主线程中初始化之后,发送一个消息(target为当前Handler)至消息队列,这个消息对象就已经包含了Handler实例的引用,只有这样,Looper在处理这条消息的时候,才可以 * 调用Handler的handlerMessage(Message)来完成消息的处理。 * * 3.非静态的内部类和匿名内部类,都会隐式的持有一个外部类的引用。 * * 由于这3个原因。 * * 当Activity finish掉,被延迟的消息会存在消息队列中10分钟,这个消息中又包含了Handler的引用,Handler是一个匿名内部类,又隐式的持有外部Activity的引用,导致 * 其无法回收,进一步导致Activity持有的很多资源都无法回收,也就是内存泄露了。 * * 正确示例:com.cundong.memory.right.HandlerOutOfMemoryActivity * */ public class HandlerOutOfMemoryActivity extends Activity { private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { /* ... */ } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.activity_outofmemory_list); // Post a message and delay its execution for 10 minutes. mHandler.postDelayed(new Runnable() { @Override public void run() { /* ... */ } }, 1000 * 60 * 10); finish(); } } ================================================ FILE: app/src/main/java/com/cundong/memory/demo/wrong/MemoryChurnActivity.java ================================================ package com.cundong.memory.demo.wrong; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import com.cundong.memory.R; import com.cundong.memory.util.MemoryUtil; /** * 内存抖动 * * 在短时间内大量的对象被创建又马上被释放,瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候, * 会触发GC从而导致刚产生的对象又很快被回收。即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加Heap的压力,从而触发更多其他类型的GC。 * * 这个操作有可能会影响到帧率,并使得用户感知到性能问题。 * * Created by cundong on 2015/12/28. */ public class MemoryChurnActivity extends Activity { private Button mButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.activity_test); mButton = (Button) findViewById(R.id.button); mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread() { @Override public void run() { super.run(); for( int i=0; i<100; i++) { Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.large_img); int rowBytes = bmp.getRowBytes(); int height = bmp.getHeight(); long memSize = rowBytes * height; Log.d("@Cundong", "memSize =" + memSize + "B =" + memSize * 1.0 / 1024 / 1024 +" M"); Log.d("@Cundong", "getUsedPercentValue:" + MemoryUtil.getUsedPercentValue()); } /** java.lang.OutOfMemoryError: Failed to allocate a 47 byte allocation with 0 free bytes and 3GB until OOM at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method) at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:609) at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:444) at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:467) at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:497) at com.cundong.memory.demo.wrong.MemoryChurnActivity$1$1.run(MemoryChurnActivity.java:42) */ } } .start(); } }); } } ================================================ FILE: app/src/main/java/com/cundong/memory/demo/wrong/StaticOutOfMemoryActivity.java ================================================ package com.cundong.memory.demo.wrong; import android.app.Activity; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.widget.TextView; import com.cundong.memory.R; /** * static变量引发的内存溢出 * * 如果一个变量为static变量,它就属于整个类,而不是类的具体实例,所以static变量的生命周期是特别的长,如果static变量引用了一些资源耗费过多 * 的实例,例如Context,就有内存溢出的危险。 * * * Google开发者博客,给出了一个例子:http://android-developers.blogspot.jp/2009/01/avoiding-memory-leaks.html * 专门介绍长时间的引用Context导致内存溢出的情况。 * * 这种情况: * * 静态的sBackground变量,虽然没有显式的持有Context的引用,但是: * * 当我们执行view.setBackgroundDrawable(Drawable drawable);之后。 * * Drawable会将View设置为一个回调(通过setCallback()方法),所以就会存在这么一个隐式的引用链:Drawable持有View,View持有Context * * sBackground是静态的,生命周期特别的长,就会导致了Context的溢出。 * * 解决办法: * * 1.不用activity的context 而是用Application的Context * * 2.在onDestroy()方法中,解除Activity与Drawable的绑定关系,从而去除Drawable对Activity的引用,使Context能够被回收 * */ public class StaticOutOfMemoryActivity extends Activity { private static Drawable sBackground; @Override protected void onCreate(Bundle state) { super.onCreate(state); TextView textView = new TextView(this); textView.setText("Leaks are bad"); if (sBackground == null) { sBackground = getResources().getDrawable(R.drawable.large_bitmap); } textView.setBackgroundDrawable(sBackground); setContentView(textView); } } ================================================ FILE: app/src/main/java/com/cundong/memory/demo/wrong/ThreadOutOfMemoryActivity.java ================================================ package com.cundong.memory.demo.wrong; import java.lang.ref.WeakReference; import android.app.Activity; import android.content.Context; import android.os.Bundle; import com.cundong.memory.R; /** * Thread引发的内存溢出 * * 当前情况: * * 1. * 内部类MyThread,持有对外部ThreadOutOfMemoryActivity的隐式引用 * * 2. * 如果我们切换横竖屏,默认就会销毁当前Activity,而这个Activity却被MyThread所持有 * * 于是就出现了溢出。 * * * 解决办法: * * 1.MyThread改为静态内部类 * * 2.在线程内部采用弱引用保存Context引用 * */ public class ThreadOutOfMemoryActivity extends Activity { private Context mContext; @Override protected void onCreate(Bundle state) { super.onCreate(state); this.setContentView(R.layout.activity_demo); mContext = this.getApplicationContext(); new MyThread().start(); } private void releaseZip(WeakReference context){ /* do something */ } private class MyThread extends Thread { @Override public void run() { super.run(); try { Thread.sleep(1000 * 60 * 2); } catch (InterruptedException e) { e.printStackTrace(); } /* do something */ releaseZip(new WeakReference<>(mContext)); } } } ================================================ FILE: app/src/main/java/com/cundong/memory/service/CoreService.java ================================================ package com.cundong.memory.service; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageButton; import android.widget.TextView; import android.widget.Toast; import com.cundong.memory.Constants; import com.cundong.memory.MainActivity; import com.cundong.memory.R; import com.cundong.memory.util.MemoryUtil; import com.premnirmal.Magnet.IconCallback; import com.premnirmal.Magnet.Magnet; import java.lang.ref.WeakReference; import java.text.DecimalFormat; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Timer; import java.util.TimerTask; /** * 类说明: 后台轮询service 每1秒钟更新一次通知栏 更新内存 * * @date 2015-4-18 * @version 1.0 */ public class CoreService extends Service implements IconCallback { private static final String TAG = "CoreService"; private Timer mTimer; private Magnet mMagnet; private View mIconView = null; private View mMemoryClearView; private TextView mDescView; private ImageButton mClearBtn, mSettingBtn; private InnerHandler mHandler; @Override public void onCreate() { super.onCreate(); mIconView = getIconView(); mDescView = (TextView) mIconView.findViewById(R.id.content); mMemoryClearView = mIconView.findViewById(R.id.memory_clear_view); mMemoryClearView.setVisibility(Constants.SHOW_MEMORY_CLEAR ? View.VISIBLE : View.GONE); mClearBtn = (ImageButton) mIconView.findViewById(R.id.clear_btn); mClearBtn.setOnClickListener( new OnClickListener(){ @Override public void onClick(View v) { Toast.makeText(getApplicationContext(), "clearMemory", Toast.LENGTH_SHORT).show(); MemoryUtil.clearMemory(getApplicationContext()); } }); mSettingBtn = (ImageButton) mIconView.findViewById(R.id.setting_btn); mSettingBtn.setOnClickListener( new OnClickListener(){ @Override public void onClick(View v) { Toast.makeText(getApplicationContext(), "test Setting", Toast.LENGTH_SHORT).show(); } }); mMagnet = new Magnet.Builder(this) .setIconView(mIconView) .setIconCallback(this) .setRemoveIconResId(R.drawable.trash) .setRemoveIconShadow(R.drawable.bottom_shadow) .setShouldFlingAway(true) .setShouldStickToWall(true) .setRemoveIconShouldBeResponsive(true) .setInitialPosition(-100, -200) .build(); mMagnet.show(); mHandler = new InnerHandler(this); } private View getIconView() { LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); return inflater.inflate(R.layout.float_view, null); } @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (mTimer == null) { mTimer = new Timer(); mTimer.scheduleAtFixedRate(new RefreshTask(), 0, 1000); } int action = intent != null ? intent.getIntExtra("action", 0) : 0; if (action == 2) { mMagnet.destroy(); this.stopSelf(); } return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); mTimer.cancel(); mTimer = null; } class RefreshTask extends TimerTask { @Override public void run() { String usedPercentValue = MemoryUtil.getUsedPercentValue(); long availableMemory = MemoryUtil.getAvailableMemory(); HashMap totalPssMap = MemoryUtil.getTotalPss(Constants.PROCESS_NAME_LIST); float memory = availableMemory / (float) 1024 / (float) 1024; DecimalFormat decimalFormat = new DecimalFormat("##0.00"); final String[] content = new String[] { getString(R.string.used_percent_value, usedPercentValue), getString(R.string.available_memory, decimalFormat.format(memory)), getString(R.string.total_pss)}; StringBuffer sb = new StringBuffer(); sb.append(content[0]).append(",").append(content[1]).append("\r\n").append(content[2]); Iterator iterator = totalPssMap.entrySet().iterator(); while(iterator.hasNext()) { Map.Entry entry = (Map.Entry) iterator.next(); entry.getKey(); sb.append(entry.getKey()).append("=").append("\r\n").append(entry.getValue()).append("\r\n"); } Bundle data = new Bundle(); data.putString("content", sb.toString()); Message message = mHandler.obtainMessage(1); message.what = 1; message.setData(data); mHandler.sendMessage(message); } } @Override public void onFlingAway() { Log.i(TAG, "onFlingAway"); } @Override public void onMove(float x, float y) { Log.i(TAG, "onMove(" + x + "," + y + ")"); } @Override public void onIconClick(View icon, float iconXPose, float iconYPose) { Log.i(TAG, "onIconClick(..)"); Intent intent = new Intent(this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } @Override public void onIconDestroyed() { Log.i(TAG, "onIconDestroyed()"); stopSelf(); } private static class InnerHandler extends Handler { private WeakReference ref; public InnerHandler(CoreService service) { ref = new WeakReference<>(service); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); CoreService service = ref.get(); if (service == null) { return; } switch (msg.what) { case 1: Bundle data = msg.getData(); String content = data.getString("content"); service.mDescView.setText(content); break; } } } } ================================================ FILE: app/src/main/java/com/cundong/memory/service/EmptyService.java ================================================ package com.cundong.memory.service; import android.app.Service; import android.content.Intent; import android.os.IBinder; /** * 一个空Service,仅仅用于测试一个空进程在dalvik、art上占用多少Total Pss */ public class EmptyService extends Service { public EmptyService() { } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); } } ================================================ FILE: app/src/main/java/com/cundong/memory/util/BitmapUtils.java ================================================ package com.cundong.memory.util; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; public class BitmapUtils { public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } return inSampleSize; } } ================================================ FILE: app/src/main/java/com/cundong/memory/util/Logger.java ================================================ package com.cundong.memory.util; import android.util.Log; public class Logger { /** * log tag */ private String tag = "Logger";// application name // TODO 配置Log打开或关闭 /** * debug or not */ private static boolean debug = true; private static Logger instance = new Logger(); private Logger() { } public static Logger getLogger() { return instance; } private String getFunctionName() { StackTraceElement[] sts = Thread.currentThread().getStackTrace(); if (sts == null) { return null; } for (StackTraceElement st : sts) { if (st.isNativeMethod()) { continue; } if (st.getClassName().equals(Thread.class.getName())) { continue; } if (st.getClassName().equals(this.getClass().getName())) { continue; } return "[" + Thread.currentThread().getName() + "(" + Thread.currentThread().getId() + "): " + st.getFileName() + ":" + st.getLineNumber() + "]"; } return null; } private String createMessage(String msg) { String functionName = getFunctionName(); String message = (functionName == null ? msg : (functionName + " - " + msg)); return message; } /** * log.i */ public void i(String msg) { if (debug) { String message = createMessage(msg); Log.i(tag, message); } } /** * log.v */ public void v(String msg) { if (debug) { String message = createMessage(msg); Log.v(tag, message); } } /** * log.d */ public void d(String msg) { if (debug) { String message = createMessage(msg); Log.d(tag, message); } } /** * log.e */ public void e(String msg) { // if (debug) { String message = createMessage(msg); Log.e(tag, message); // } } /** * log.error */ public void error(Exception e) { if (debug) { StringBuffer sb = new StringBuffer(); String name = getFunctionName(); StackTraceElement[] sts = e.getStackTrace(); if (name != null) { sb.append(name + " - " + e + "\r\n"); } else { sb.append(e + "\r\n"); } if (sts != null && sts.length > 0) { for (StackTraceElement st : sts) { if (st != null) { sb.append("[ " + st.getFileName() + ":" + st.getLineNumber() + " ]\r\n"); } } } Log.e(tag, sb.toString()); } } /** * log.d */ public void w(String msg) { if (debug) { String message = createMessage(msg); Log.w(tag, message); } } public void setTag(String tag) { this.tag = tag; } /** * set debug */ public static void setDebug(boolean d) { debug = d; } public static boolean isDebug() { return debug; } } ================================================ FILE: app/src/main/java/com/cundong/memory/util/MemoryUtil.java ================================================ package com.cundong.memory.util; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.content.Context; import android.os.Build; import android.os.Debug.MemoryInfo; import android.text.TextUtils; import android.util.Log; import com.cundong.memory.App; import com.cundong.memory.Constants; import com.jaredrummler.android.processes.ProcessManager; import com.jaredrummler.android.processes.models.AndroidAppProcess; /** * 类说明: 内存相关数据获取工具类 * * @date 2015-4-18 * @version 1.0 */ public class MemoryUtil { /** * getTotalPss,5.0+使用开源的android-processes解决方案,5.0以下使用系统api * * @param processNameList * @return */ public static HashMap getTotalPss(ArrayList processNameList) { HashMap resultMap = new HashMap<>(); ActivityManager activityMgr = (ActivityManager) App.getAppContext().getSystemService(Context.ACTIVITY_SERVICE); if (Build.VERSION.SDK_INT >= 21) { List list = ProcessManager.getRunningAppProcesses(); if (list != null) { for (AndroidAppProcess processInfo : list) { if (processNameList.contains(processInfo.name)) { int pid = processInfo.pid; MemoryInfo[] memoryInfos = activityMgr.getProcessMemoryInfo(new int[]{pid}); MemoryInfo memoryInfo = memoryInfos[0]; int totalPss = memoryInfo.getTotalPss(); resultMap.put(processInfo.name, new Long(totalPss)); } } } } else { List list = activityMgr.getRunningAppProcesses(); if (list != null) { for (RunningAppProcessInfo processInfo : list) { if (Constants.PROCESS_NAME_LIST.contains(processInfo.processName)) { int pid = processInfo.pid; MemoryInfo[] memoryInfos = activityMgr.getProcessMemoryInfo(new int[] { pid }); MemoryInfo memoryInfo = memoryInfos[0]; int totalPss = memoryInfo.getTotalPss(); resultMap.put(processInfo.processName, new Long(totalPss)); } } } } return resultMap; } /** * getTotalPss,5.0+使用开源的android-processes解决方案,5.0以下使用系统api * * @param processName * @return */ public static long getTotalPss(String processName) { ActivityManager activityMgr = (ActivityManager) App.getAppContext().getSystemService(Context.ACTIVITY_SERVICE); if (Build.VERSION.SDK_INT >= 21) { List list = ProcessManager.getRunningAppProcesses(); if (list != null) { for (AndroidAppProcess processInfo : list) { if (processInfo.name.equals(processName)) { int pid = processInfo.pid; MemoryInfo[] memoryInfos = activityMgr.getProcessMemoryInfo(new int[]{pid}); MemoryInfo memoryInfo = memoryInfos[0]; int totalPss = memoryInfo.getTotalPss(); return totalPss; } } } } else { List list = activityMgr.getRunningAppProcesses(); if (list != null) { for (RunningAppProcessInfo processInfo : list) { if (processInfo.processName.equals(processName)) { int pid = processInfo.pid; MemoryInfo[] memoryInfos = activityMgr.getProcessMemoryInfo(new int[] { pid }); MemoryInfo memoryInfo = memoryInfos[0]; int totalPss = memoryInfo.getTotalPss(); return totalPss; } } } } return -1; } /** * 计算已使用内存的百分比 * */ public static String getUsedPercentValue() { String dir = "/proc/meminfo"; try { FileReader fr = new FileReader(dir); BufferedReader br = new BufferedReader(fr, 2048); String memoryLine = br.readLine(); String subMemoryLine = memoryLine.substring(memoryLine .indexOf("MemTotal:")); br.close(); long totalMemorySize = Integer.parseInt(subMemoryLine.replaceAll( "\\D+", "")); long availableSize = getAvailableMemory() / 1024; int percent = (int) ((totalMemorySize - availableSize) / (float) totalMemorySize * 100); return percent + "%"; } catch (IOException e) { e.printStackTrace(); } return ""; } /** * 获取可用内存 * */ public static long getAvailableMemory() { ActivityManager activityManager = (ActivityManager) App.getAppContext() .getSystemService(Context.ACTIVITY_SERVICE); ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); activityManager.getMemoryInfo(mi); return mi.availMem; } public static void clearMemory(Context context) { ActivityManager activityManger = (ActivityManager) App.getAppContext().getSystemService(Context.ACTIVITY_SERVICE); if (Build.VERSION.SDK_INT >= 21) { List list = ProcessManager.getRunningAppProcesses(); if (list != null) { for (AndroidAppProcess processInfo : list) { int myPid = android.os.Process.myPid(); if(myPid == processInfo.pid) { continue; } if (!processInfo.foreground) { activityManger.killBackgroundProcesses(processInfo.getPackageName()); } } } } else { List list = activityManger.getRunningAppProcesses(); if (list != null) { for (int i = 0; i < list.size(); i++) { ActivityManager.RunningAppProcessInfo appProcessInfo = list.get(i); String[] pkgList = appProcessInfo.pkgList; if (appProcessInfo.importance >= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { for (int j = 0; j < pkgList.length; j++) { if (pkgList[j].equals(context.getPackageName())) { continue; } activityManger.killBackgroundProcesses(pkgList[j]); } } } } } } } ================================================ FILE: app/src/main/java/com/cundong/memory/util/ViewUtils.java ================================================ package com.cundong.memory.util; import android.view.View; import android.view.ViewGroup; public class ViewUtils { public static void unbindDrawables(View view) { if (view.getBackground() != null) { view.getBackground().setCallback(null); } if (view instanceof ViewGroup) { for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { unbindDrawables(((ViewGroup) view).getChildAt(i)); } ((ViewGroup) view).removeAllViews(); } } } ================================================ FILE: app/src/main/res/anim/slide_down.xml ================================================ ================================================ FILE: app/src/main/res/anim/slide_up.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_demo.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_main.xml ================================================