[
  {
    "path": ".gitignore",
    "content": "# built application files\n*.ap_\n\n# files for the dex VM\n*.dex\n\n# Java class files\n*.class\n\n# generated files\nbin/\ngen/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Proguard folder generated by Eclipse\nproguard/\n\n# Ignore gradle files\n.gradle/\nbuild/\n\n# Eclipse project files\n.classpath\n.project\n.settings/\n\n# Intellij project files\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# Mac system files\n.DS_Store"
  },
  {
    "path": "README.md",
    "content": "# MemoryMonitor \n\n一个给开发者使用的Android App内存清理、监控工具。  \n\n主要包括三部分内容：\n\n* 内存清理  \n\n通过内存清理可以模拟系统内存不足时对进程的回收。\n\n* Pss监控  \n\n通过内存监控可以监控指定应用程序使用的total Pss以及当前手机的内存使用情况，从而检测该应用是否存在内存泄漏。\n\n* 内存优化\n\n整理了一些关于内存优化的tips，以及一些可能导致内存溢出的场景示例，包含错误的写法和正确的写法。\n\n## 1.内存清理\n\n类似各种手机管家的 **加速球**，获取系统已用内存比率、可用内存大小，一键清理。\n\n可以用于测试自己开发的Activity、Fragment健壮性，模拟Activity、Fragment被回收的场景，测试自己的程序是否完好的保存、恢复当前场景。\n\n比如：打开你开发的某个Activity、Fragment，切到后台，清理一次内存，在将其切回前台后，看会不会出现空指针异常，以及程序状态是否被恢复。\n\n## 2.Pss监控\n\nAndroid 系统中的内存和Linux系统一样，存在着大量的共享内存。每个APP占内存会有私有和公共的两部分，我们可以通过App的Pss值，可以获取到这两部分内存。\n\nPss（Proportional Set Size）：实际使用的物理内存，即：自身应用占有的内存+共享内存中比例分配给这个应用的内存。\n\n通过该程序，每隔1秒，获取一次被监控App的Total Pss值。\n\n使用某个功能（可能会导致OOM的那些都要试试），查看Pss是否飙升，或者使用过许久都没有降低。\n\n如果使用后飙升并且长时间都降不下来，那就说明肯定会导致OOM（对象使用过之后还被引用着未释放），如果使用之后Total Pss飙升，但是使用过之后能降下来，也可能会导致OOM，我们还是需要去一点一点排查是什么原因导致的。\n\n如果使用后飙升并且长时间都降不下来，我们就需要 [使用MAT来进一步分析问题所在](http://blog.csdn.net/xiaanming/article/details/42396507)。\n\n此处提到的Pss，也可以使用adb命令 \n\n> adb shell dumpsys meminfo *your packageName* \n\n查看：  \n\n![total Pss](https://github.com/cundong/MemoryMonitor/blob/master/screenshot/total%20Pss.png?raw=true)  \n\n## 3.内存优化\n\nAndroid的虚拟机是基于寄存器的Dalvik，它的最大堆大小一般比较小（最低端的设备16M，后来出的设备变成了24M，48M等等），因此我们所能利用的内存空间是有限的。如果我们使用内存占用超过了一定的限额后就会出现OutOfMemory的错误。\n\n可能会导致内存溢出的情况有以下几种：\n\n### 对静态变量的错误使用 \n\n如果一个变量为static变量，它就属于整个类，而不是类的具体实例，所以static变量的生命周期是特别的长，如果static变量引用了一些资源耗费过多的实例，例如Context，就有内存溢出的危险。\n\n[Google开发者博客，给出了一个例子](http://android-developers.blogspot.jp/2009/01/avoiding-memory-leaks.html)，专门介绍长时间引用Context导致内存溢出的情况。\n\n示例代码：\n\n```java\nprivate static Drawable sBackground;\n\n@Override\nprotected void onCreate(Bundle state) {\n\tsuper.onCreate(state);\n\n\tTextView textView = new TextView(this);\n\ttextView.setText(\"Leaks are bad\");\n\t\t\n\tif (sBackground == null) {\n\t\tsBackground = getResources().getDrawable(R.drawable.large_bitmap);\n\t}\n\t\t\n\ttextView.setBackgroundDrawable(sBackground);\n\t\n\tsetContentView(textView);\n}\n```\n\n这种情况下，静态的sBackground变量，虽然没有显式的持有Context的引用，但当我们执行`view.setBackgroundDrawable(Drawable drawable);`的时候，drawable 对象会将当前view设置为一个回调，通过 `View.setCallback(this)` 方法。  \n\n具体可见View类的源码：  \n```\npublic void setBackgroundDrawable(Drawable background) {\n\t//...\n\t\n\tif (mBackground == null || mBackground.getMinimumHeight() != background.getMinimumHeight() ||\n                mBackground.getMinimumWidth() != background.getMinimumWidth()) {\n                requestLayout = true;\n        }\n\n        background.setCallback(this);\n            if (background.isStateful()) {\n                background.setState(getDrawableState());\n        }\n        \n        background.setVisible(getVisibility() == VISIBLE, false);\n        mBackground = background;\n            \n\t//...\n}\n```\n`background.setCallback(this);` 代码块就是我们说的设置回调。\n\n所以，这种情况就会存在这么一个隐式的引用链：Drawable持有View，而View持有Context，sBackground 是静态的，生命周期特别的长，于是就会导致了Context的溢出。\n\n解决办法：  \n\n1.不用activity的context 而是用Application的Context；  \n\n```java\nprivate static Drawable sBackground;\n\n@Override\nprotected void onCreate(Bundle state) {\n\tsuper.onCreate(state);\n\n\tTextView textView = new TextView(this.getApplication());\n\ttextView.setText(\"Leaks are bad\");\n\t\t\n\tif (sBackground == null) {\n\t\tsBackground = getResources().getDrawable(R.drawable.large_bitmap);\n\t}\n\t\t\n\ttextView.setBackgroundDrawable(sBackground);\n\t\n\tsetContentView(textView);\n}\n```\n2.在onDestroy()方法中，解除Activity与Drawable的绑定关系,从而去除Drawable对Activity的引用，使Context能够被回收； \n\n```java\n@Override\nprotected void onDestroy() {\n\tsuper.onDestroy();\n\t\t\t\n\tViewUtils.unbindDrawables(findViewById(android.R.id.content));\n\t\t\n\tSystem.gc();\n}\n\npublic static void unbindDrawables(View view) {\n\tif (view.getBackground() != null) {\n\t\tview.getBackground().setCallback(null);\n\t}\n\t\n\tif (view instanceof ViewGroup) {\n\t\tfor (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {\n\t\t\tunbindDrawables(((ViewGroup) view).getChildAt(i));\n\t\t}\n\t\t((ViewGroup) view).removeAllViews();\n\t}\n}\n```\n\n### 长周期内部类、匿名内部类长时间持有外部类引用导致相关资源无法释放\n\n长周期内部类、匿名内部类，如Handler，Thread，AsyncTask等。\n\nHandlerOutOfMemoryActivity所示的是Handler引发的内存溢出。\n\nThreadOutOfMemoryActivity所示的是Thread引发的内存溢出。\n\nAsyncTaskOutOfMemoryActivity所示的时AsyncTask引发的内存溢出。\n\n### Bitmap导致的内存溢出\n\n一般是因为尝试加载过大的图片到内存，或者是内存中已经存在的过多的图片，从而导致内存溢出。\n\n### 数据库Cursor未关闭\n\n正常情况下，如果查询得到的数据量较小时不会有内存问题，而且虚拟机能够保证Cusor最终会被释放掉，如果Cursor的数据量特表大，特别是如果里面有Blob信息时，应该保证Cursor占用的内存被及时的释放掉，而不是等待GC来处理。\n\n### 单例模式引用Context导致的内存泄露\n\n如果在某个Activity中使用 `Singleton instance = Singleton.getInstance(this);` 就会造成该Activity一直被 `Singleton` 引用着，不能释放。这时候，正确的做法是使用 `getApplicationContext()` 来替代 `Activity的Context` ，这样就能避免内存泄露。\n\n### 代码中一些细节\n\n>* 尽量使用9path\n>* Adapter要使用convertView\n>* 各种监听，广播等，注册的同时要记得取消注册\n>* 使用完对象要及时销毁，能使用局部变量的不要使用全局变量，功能用完成后要去掉对他的引用\n>* 切勿在循环调用的地方去产生对象，比如在getview()里new OnClicklistener(),这样的话，拖动的时候会new大量的对象出来。\n>* 使用Android推荐的数据结构，比如HashMap替换为SparseArray，避免使用枚举类型（在Android平台，枚举类型的内存消耗是Static常量的的2倍）\n>* 使用lint工具优化工程\n>* 字符串拼接使用StringBuilder或者StringBuffer\n>* 尽量使用静态匿名内部类，如果需要对外部类的引用，使用弱引用\n>* for循环的使用\n用\n`final int size = array.length; for(int i = 0; i< size;i++)`\n来替代：\n`for(int i =0;i < array.length;i++) `\n\n最后，整理了一些开发中可能会导致内存溢出的场景，放在com.cundong.memory.demo.wrong中，并且给出了优化方法，放在com.cundong.memory.demo.right中。\n\n## 4.截图\n\n![截屏][1]\n\n[1]: https://raw.githubusercontent.com/cundong/MemoryMonitor/master/screenshot/app.png\n"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 23\n    buildToolsVersion \"23.0.2\"\n\n    defaultConfig {\n        applicationId \"com.cundong.memory\"\n        minSdkVersion 14\n        targetSdkVersion 23\n        versionCode 2\n        versionName \"1.2\"\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    compile project(':magnet')\n    testCompile 'junit:junit:4.12'\n    compile 'com.android.support:appcompat-v7:23.1.1'\n    compile 'com.android.support:design:23.1.1'\n    compile 'com.jaredrummler:android-processes:1.0.3'\n}"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.cundong.memory\">\n\n    <uses-sdk\n        android:minSdkVersion=\"14\"\n        android:targetSdkVersion=\"23\" />\n\n    <uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\" />\n    <uses-permission android:name=\"android.permission.GET_TASKS\" />\n    <uses-permission android:name=\"android.permission.KILL_BACKGROUND_PROCESSES\" />\n\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n\n    <application\n        android:name=\".App\"\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/AppTheme\">\n        <activity\n            android:name=\".MainActivity\"\n            android:configChanges=\"orientation|keyboardHidden|navigation|screenSize\"\n            android:label=\"@string/app_name\"\n            android:launchMode=\"singleTask\"\n            android:screenOrientation=\"portrait\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <activity\n            android:name=\".demo.wrong.HandlerOutOfMemoryActivity\"\n            android:configChanges=\"orientation|keyboardHidden|navigation|screenSize\"\n            android:label=\"@string/app_name\"\n            android:screenOrientation=\"portrait\" />\n        <activity\n            android:name=\".demo.wrong.StaticOutOfMemoryActivity\"\n            android:configChanges=\"orientation|keyboardHidden|navigation|screenSize\"\n            android:label=\"@string/app_name\"\n            android:screenOrientation=\"portrait\" />\n        <activity\n            android:name=\".demo.right.HandlerOutOfMemoryActivity\"\n            android:configChanges=\"orientation|keyboardHidden|navigation|screenSize\"\n            android:label=\"@string/app_name\"\n            android:screenOrientation=\"portrait\" />\n        <activity\n            android:name=\".demo.right.OneStaticOutOfMemoryActivity\"\n            android:configChanges=\"orientation|keyboardHidden|navigation|screenSize\"\n            android:label=\"@string/app_name\"\n            android:screenOrientation=\"portrait\" />\n        <activity\n            android:name=\".demo.right.TwoStaticOutOfMemoryActivity\"\n            android:configChanges=\"orientation|keyboardHidden|navigation|screenSize\"\n            android:label=\"@string/app_name\"\n            android:screenOrientation=\"portrait\" />\n        <activity\n            android:name=\".demo.wrong.MemoryChurnActivity\"\n            android:configChanges=\"orientation|keyboardHidden|navigation|screenSize\"\n            android:label=\"@string/app_name\"\n            android:screenOrientation=\"portrait\" />\n\n        <service android:name=\".service.CoreService\" android:exported=\"false\"/>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/App.java",
    "content": "package com.cundong.memory;\n\nimport android.app.Application;\nimport android.content.Context;\n\npublic class App extends Application {\n\n    private static Context mContext;\n\n    public static Context getAppContext() {\n        return mContext;\n    }\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        mContext = this;\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/Constants.java",
    "content": "package com.cundong.memory;\n\nimport java.util.ArrayList;\n\npublic class Constants {\n\n    public static final boolean SHOW_MEMORY_CLEAR = false;\n\n    // TODO\n    /**\n     * 此处，改为被监控 total Pss 的 processName 列表\n     * <p/>\n     * 微信：\n     * com.tencent.mm\n     * com.tencent.mm:TMAssistantDownloadSDKService\n     * com.tencent.mm:push\n     * com.tencent.mm:cuploader\n     * com.tencent.mm:nospace\n     * com.tencent.mm:tools\n     * com.tencent.mm:sandbox\n     * com.tencent.mm:exdevice\n     */\n    public static final ArrayList<String> PROCESS_NAME_LIST = new ArrayList();\n\n    static {\n        PROCESS_NAME_LIST.add(\"com.tencent.mm\");\n        PROCESS_NAME_LIST.add(\"com.tencent.mm:TMAssistantDownloadSDKService\");\n        PROCESS_NAME_LIST.add(\"com.tencent.mm:push\");\n        PROCESS_NAME_LIST.add(\"com.tencent.mm:cuploader\");\n        PROCESS_NAME_LIST.add(\"com.tencent.mm:nospace\");\n        PROCESS_NAME_LIST.add(\"com.tencent.mm:tools\");\n        PROCESS_NAME_LIST.add(\"com.tencent.mm:sandbox\");\n        PROCESS_NAME_LIST.add(\"com.tencent.mm:exdevice\");\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/MainActivity.java",
    "content": "package com.cundong.memory;\n\nimport android.annotation.TargetApi;\nimport android.app.ActivityManager;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.provider.Settings;\nimport android.support.v7.app.AppCompatActivity;\nimport android.view.View;\nimport android.widget.Button;\nimport android.widget.Toast;\n\nimport com.cundong.memory.service.CoreService;\nimport com.cundong.memory.service.EmptyService;\nimport com.cundong.memory.util.MemoryUtil;\n\nimport java.util.List;\n\npublic class MainActivity extends AppCompatActivity {\n\n    private static final int OVERLAY_PERMISSION_REQ_CODE = 1;\n\n    private Button mButton1, mButton2, mButton3, mButton4;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        mButton1 = (Button) findViewById(R.id.button1);\n        mButton2 = (Button) findViewById(R.id.button2);\n        mButton3 = (Button) findViewById(R.id.button3);\n        mButton4 = (Button) findViewById(R.id.button4);\n\n        mButton3.setVisibility(Constants.SHOW_MEMORY_CLEAR ? View.VISIBLE : View.GONE);\n        mButton4.setVisibility(Constants.SHOW_MEMORY_CLEAR ? View.VISIBLE : View.GONE);\n\n        mButton1.setOnClickListener(new View.OnClickListener() {\n\n            @Override\n            public void onClick(View v) {\n\n                try2StartMonitor();\n            }\n        });\n\n        mButton2.setOnClickListener(new View.OnClickListener() {\n\n            @Override\n            public void onClick(View v) {\n                Intent intent = new Intent(MainActivity.this, CoreService.class);\n                intent.putExtra(\"action\", 2);\n                startService(intent);\n\n                finish();\n            }\n        });\n\n        mButton3.setOnClickListener(new View.OnClickListener() {\n\n            @Override\n            public void onClick(View v) {\n                Toast.makeText(getApplicationContext(), \"clearMemory\", Toast.LENGTH_LONG).show();\n                MemoryUtil.clearMemory(getApplicationContext());\n            }\n        });\n\n        mButton4.setOnClickListener(new View.OnClickListener() {\n\n            @Override\n            public void onClick(View v) {\n                Intent intent = new Intent(MainActivity.this, EmptyService.class);\n                startService(intent);\n            }\n        });\n    }\n\n    @TargetApi(Build.VERSION_CODES.M)\n    private void try2StartMonitor() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {\n\n            Toast.makeText(this, R.string.permission_err, Toast.LENGTH_LONG).show();\n\n            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse(\"package:\" + getPackageName()));\n            startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);\n        } else {\n            Intent intent = new Intent(MainActivity.this, CoreService.class);\n            intent.putExtra(\"action\", 1);\n            startService(intent);\n\n            finish();\n        }\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n\n        boolean isServiceRunning = isServiceRunning(getPackageName() + \".service.CoreService\");\n\n        mButton1.setVisibility(isServiceRunning ? View.GONE : View.VISIBLE);\n        mButton2.setVisibility(isServiceRunning ? View.VISIBLE : View.GONE);\n    }\n\n    @TargetApi(Build.VERSION_CODES.M)\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {\n            if (!Settings.canDrawOverlays(this)) {\n                Toast.makeText(this, R.string.permission_err, Toast.LENGTH_SHORT).show();\n            } else {\n                Intent intent = new Intent(MainActivity.this, CoreService.class);\n                intent.putExtra(\"action\", 1);\n                startService(intent);\n\n                finish();\n            }\n        }\n    }\n\n    /**\n     * 使用Application context来调用getSystemService，避免内存泄漏\n     *\n     * @param className\n     * @return\n     */\n    private boolean isServiceRunning(String className) {\n\n        ActivityManager activityManager = (ActivityManager) App.getAppContext().getSystemService(Context.ACTIVITY_SERVICE);\n        List<ActivityManager.RunningServiceInfo> serviceList = activityManager.getRunningServices(100);\n\n        if (serviceList == null || serviceList.size() <= 0) {\n            return false;\n        }\n\n        for (ActivityManager.RunningServiceInfo serviceInfo : serviceList) {\n\n            String serviceName = serviceInfo.service.getClassName();\n\n            if (serviceName.equals(className)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/demo/right/AsyncTaskOutOfMemoryActivity.java",
    "content": "package com.cundong.memory.demo.right;\n\nimport java.lang.ref.WeakReference;\n\nimport android.app.Activity;\nimport android.graphics.Bitmap;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.widget.ImageView;\n\nimport com.cundong.memory.util.BitmapUtils;\nimport com.cundong.memory.R;\n\n/**\n * AsyncTask引发的内存溢出解决办法\n * \n * BitmapWorkerTask内部采用弱引用保存Context引用\n * \n */\npublic class AsyncTaskOutOfMemoryActivity extends Activity {\n\t\n\tprivate ImageView mImageView;\n\t\n\t@Override\n\tprotected void onCreate(Bundle state) {\n\t\tsuper.onCreate(state);\n\t\tthis.setContentView(R.layout.activity_demo);\n\t\t\n\t\tmImageView = (ImageView) findViewById(R.id.image);\n\t\t\n\t\tBitmapWorkerTask task = new BitmapWorkerTask(mImageView);\n\t    task.execute(R.drawable.large_bitmap);\n\t}\n\t\n\tclass BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {\n\t    private final WeakReference<ImageView> imageViewReference;\n\t    private int data = 0;\n\n\t    public BitmapWorkerTask(ImageView imageView) {\n\t        // Use a WeakReference to ensure the ImageView can be garbage collected\n\t        imageViewReference = new WeakReference<>(imageView);\n\t    }\n\n\t    // Decode image in background.\n\t    @Override\n\t    protected Bitmap doInBackground(Integer... params) {\n\t        data = params[0];\n\t        return BitmapUtils.decodeSampledBitmapFromResource(getResources(), data, 100, 100);\n\t    }\n\n\t    // Once complete, see if ImageView is still around and set bitmap.\n\t    @Override\n\t    protected void onPostExecute(Bitmap bitmap) {\n\t        if (imageViewReference != null && bitmap != null) {\n\t            final ImageView imageView = imageViewReference.get();\n\t            if (imageView != null) {\n\t                imageView.setImageBitmap(bitmap);\n\t            }\n\t        }\n\t    }\n\t}\n}"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/demo/right/HandlerOutOfMemoryActivity.java",
    "content": "package com.cundong.memory.demo.right;\n\nimport java.lang.ref.WeakReference;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Message;\n\nimport com.cundong.memory.R;\n\n/**\n * 解决Handler引发的内存溢出\n * \n * 不使用非静态的内部类，改为使用静态内部类，当静态内部类中需要调用外部的Activity时，改用弱引用。\n * \n */\npublic class HandlerOutOfMemoryActivity extends Activity {\n\t\n\tprivate final MyHandler mHandler = new MyHandler(this);\n\n\tprivate static class MyHandler extends Handler {\n\t\tprivate final WeakReference<HandlerOutOfMemoryActivity> mActivity;\n\n\t\tpublic MyHandler(HandlerOutOfMemoryActivity activity) {\n\t\t\tmActivity = new WeakReference<>(activity);\n\t\t}\n\n\t\t@Override\n\t\tpublic void handleMessage(Message msg) {\n\t\t\tHandlerOutOfMemoryActivity activity = mActivity.get();\n\t\t\tif (activity != null) {\n\t\t\t\t/* ... */\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate static final Runnable sRunnable = new Runnable() {\n\t\t@Override\n\t\tpublic void run() { \n\t\t\t/* ... */\n\t\t}\n\t};\n\n\t@Override\n\tprotected void onCreate(Bundle savedInstanceState) {\n\t\tsuper.onCreate(savedInstanceState);\n\t\tthis.setContentView(R.layout.activity_outofmemory_list);\n\t\t\n\t\tmHandler.postDelayed(sRunnable, 1000 * 60 * 10);\n\n\t\tfinish();\n\t}\n}"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/demo/right/OneStaticOutOfMemoryActivity.java",
    "content": "package com.cundong.memory.demo.right;\n\nimport android.app.Activity;\nimport android.graphics.drawable.Drawable;\nimport android.os.Bundle;\nimport android.widget.TextView;\n\nimport com.cundong.memory.R;\n\n/**\n * 解决static变量引发的内存溢出\n * \n * 1.不用activity的context 而是用application的context\n * \n */\npublic class OneStaticOutOfMemoryActivity extends Activity {\n\t\n\tprivate static Drawable sBackground;\n\n\t@Override\n\tprotected void onCreate(Bundle state) {\n\t\tsuper.onCreate(state);\n\n\t\tTextView label = new TextView(this.getApplication());\n\t\tlabel.setText(\"Leaks are bad\");\n\t\t\n\t\tif (sBackground == null) {\n\t\t\tsBackground = getResources().getDrawable(R.drawable.large_bitmap);\n\t\t}\n\t\t\n\t\tlabel.setBackgroundDrawable(sBackground);\n\t\t\n\t\tsetContentView(label);\n\t}\n}"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/demo/right/OneThreadOutOfMemoryActivity.java",
    "content": "package com.cundong.memory.demo.right;\n\nimport java.lang.ref.WeakReference;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.Bundle;\n\nimport com.cundong.memory.R;\n\n/**\n * 解决Thread引发的内存溢出\n * \n * 解决办法：\n * \n * 在线程内部采用弱引用保存Context引用\n * \n */\npublic class OneThreadOutOfMemoryActivity extends Activity {\n\t\n\tprivate Context mContext;\n\t\n\t@Override\n\tprotected void onCreate(Bundle state) {\n\t\tsuper.onCreate(state);\n\t\tthis.setContentView(R.layout.activity_demo);\n\t\t\n\t\tmContext = this.getApplicationContext();\n\t\t\n\t\tnew MyThread().start();\n\t}\n\t\n\tprivate void releaseZip(WeakReference<Context> context){\n\t\t/* do something */\n\t}\n\t\n\tprivate class MyThread extends Thread {\n\t\t@Override\n\t\tpublic void run() {\n\t\t\tsuper.run();\n\n\t\t\ttry {\n\t\t\t\tThread.sleep(1000 * 60 * 2);\n\t\t\t} catch (InterruptedException e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\n\t\t\t/* do something */\n\t\t\treleaseZip(new WeakReference<Context>(mContext));\n\t\t}\n\t}\n}"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/demo/right/TwoStaticOutOfMemoryActivity.java",
    "content": "package com.cundong.memory.demo.right;\n\nimport android.app.Activity;\nimport android.graphics.drawable.Drawable;\nimport android.os.Bundle;\nimport android.widget.TextView;\n\nimport com.cundong.memory.util.ViewUtils;\nimport com.cundong.memory.R;\n\n/**\n * 解决static变量引发的内存溢出\n * \n * 2.在 onDestroy() 方法中，解除 Activity 和 biamap（drawble）的绑定关系,从而去除bitmap对activity 引用，让系统适时的去回收\n * \n */\npublic class TwoStaticOutOfMemoryActivity extends Activity {\n\t\n\tprivate static Drawable sBackground;\n\n\t@Override\n\tprotected void onCreate(Bundle state) {\n\t\tsuper.onCreate(state);\n\n\t\tTextView label = new TextView(this);\n\t\tlabel.setText(\"Leaks are bad\");\n\t\t\n\t\tif (sBackground == null) {\n\t\t\tsBackground = getResources().getDrawable(R.drawable.large_bitmap);\n\t\t}\n\t\t\n\t\tlabel.setBackgroundDrawable(sBackground);\n\t\t\n\t\tsetContentView(label);\n\t}\n\n\t@Override\n\tprotected void onDestroy() {\n\t\tsuper.onDestroy();\n\t\t\t\n\t\tViewUtils.unbindDrawables(findViewById(android.R.id.content));\n\t\t\n\t\tSystem.gc();\n\t}\n}"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/demo/right/TwoThreadOutOfMemoryActivity.java",
    "content": "package com.cundong.memory.demo.right;\n\nimport java.lang.ref.WeakReference;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.Bundle;\n\nimport com.cundong.memory.R;\n\n/**\n * 解决Thread引发的内存溢出\n * \n * 1.MyThread改为静态内部类\n */\npublic class TwoThreadOutOfMemoryActivity extends Activity {\n\t\n\t@Override\n\tprotected void onCreate(Bundle state) {\n\t\tsuper.onCreate(state);\n\t\tthis.setContentView(R.layout.activity_demo);\n\t\t\n\t\tnew MyThread(this).start();\n\t}\n\t\n\tprivate static void releaseZip(Context context){\n\t\t/* do something */\n\t}\n\t\n\tprivate static class MyThread extends Thread {\n\t\t\n\t\tprivate final WeakReference<TwoThreadOutOfMemoryActivity> mActivity;\n\n\t\tpublic MyThread(TwoThreadOutOfMemoryActivity activity) {\n\t\t\tmActivity = new WeakReference<>(activity);\n\t\t}\n\t\t\n\t\t@Override\n\t\tpublic void run() {\n\t\t\tsuper.run();\n\n\t\t\ttry {\n\t\t\t\tThread.sleep(1000 * 60 * 2);\n\t\t\t} catch (InterruptedException e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\n\t\t\t/* do something */\n\t\t\treleaseZip(mActivity.get());\n\t\t}\n\t}\n}"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/demo/wrong/AsyncTaskOutOfMemoryActivity.java",
    "content": "package com.cundong.memory.demo.wrong;\n\nimport android.app.Activity;\nimport android.graphics.Bitmap;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.widget.ImageView;\n\nimport com.cundong.memory.util.BitmapUtils;\nimport com.cundong.memory.R;\n\n/**\n * AsyncTask引发的内存溢出\n * \n * 当前情况：\n * \n * 1.\n * 内部类BitmapWorkerTask，持有对外部AsyncTaskOutOfMemoryActivity的隐式引用\n * \n * 2.\n * 如果我们切换横竖屏，默认就会销毁当前Activity，而这个Activity却被BitmapWorkerTask所持有\n * \n * 于是就出现了溢出。\n * \n * \n * 解决办法：\n * \n * BitmapWorkerTask内部采用弱引用保存Context引用\n * \n */\npublic class AsyncTaskOutOfMemoryActivity extends Activity {\n\t\n\tprivate ImageView mImageView;\n\t\n\t@Override\n\tprotected void onCreate(Bundle state) {\n\t\tsuper.onCreate(state);\n\t\tthis.setContentView(R.layout.activity_demo);\n\t\t\n\t\tmImageView = (ImageView) findViewById(R.id.image);\n\t\t\n\t\tBitmapWorkerTask task = new BitmapWorkerTask();\n\t    task.execute(R.drawable.large_bitmap);\n\t}\n\t\n\tclass BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {\n\t    \n\t    private int data = 0;\n\n\t    // Decode image in background.\n\t    @Override\n\t    protected Bitmap doInBackground(Integer... params) {\n\t        data = params[0];\n\t        return BitmapUtils.decodeSampledBitmapFromResource(getResources(), data, 100, 100);\n\t    }\n\n\t    // Once complete, see if ImageView is still around and set bitmap.\n\t    @Override\n\t    protected void onPostExecute(Bitmap bitmap) {\n\t        if (bitmap != null) {\n\t        \tmImageView.setImageBitmap(bitmap);\n\t        }\n\t    }\n\t}\n}"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/demo/wrong/HandlerOutOfMemoryActivity.java",
    "content": "package com.cundong.memory.demo.wrong;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Message;\n\nimport com.cundong.memory.R;\n\n/**\n * Handler引发的内存溢出\n * \n * 1.\n * 当一个Android应用启动的时候，会自动创建一个供主线程使用的Looper实例，这个Looper实例负责一个一个的处理消息队列中的消息对象，它的生命周期和当前应用\n * 的生命周期是一样的。\n * \n * 2.\n * 当Handler在主线程中初始化之后，发送一个消息(target为当前Handler)至消息队列，这个消息对象就已经包含了Handler实例的引用，只有这样，Looper在处理这条消息的时候，才可以\n * 调用Handler的handlerMessage(Message)来完成消息的处理。\n * \n * 3.非静态的内部类和匿名内部类，都会隐式的持有一个外部类的引用。\n * \n * 由于这3个原因。\n * \n * 当Activity finish掉，被延迟的消息会存在消息队列中10分钟，这个消息中又包含了Handler的引用，Handler是一个匿名内部类，又隐式的持有外部Activity的引用，导致\n * 其无法回收，进一步导致Activity持有的很多资源都无法回收，也就是内存泄露了。\n * \n * 正确示例：com.cundong.memory.right.HandlerOutOfMemoryActivity\n * \n */\npublic class HandlerOutOfMemoryActivity extends Activity {\n\t\n\tprivate final Handler mHandler = new Handler() {\n\n\t\t@Override\n\t\tpublic void handleMessage(Message msg) {\n\t\t\t/* ... */\n\t\t}\n\t};\n\n\t@Override\n\tprotected void onCreate(Bundle savedInstanceState) {\n\t\tsuper.onCreate(savedInstanceState);\n\t\tthis.setContentView(R.layout.activity_outofmemory_list);\n\n\t\t// Post a message and delay its execution for 10 minutes.\n\t\tmHandler.postDelayed(new Runnable() {\n\t\t\t@Override\n\t\t\tpublic void run() { \n\t\t\t\t\n\t\t\t\t/* ... */\n\t\t\t}\n\t\t}, 1000 * 60 * 10);\n\t\t\n\t\tfinish();\n\t}\n}"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/demo/wrong/MemoryChurnActivity.java",
    "content": "package com.cundong.memory.demo.wrong;\n\nimport android.app.Activity;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.Button;\n\nimport com.cundong.memory.R;\nimport com.cundong.memory.util.MemoryUtil;\n\n/**\n * 内存抖动\n *\n * 在短时间内大量的对象被创建又马上被释放，瞬间产生大量的对象会严重占用Young Generation的内存区域，当达到阀值，剩余空间不够的时候，\n * 会触发GC从而导致刚产生的对象又很快被回收。即使每次分配的对象占用了很少的内存，但是他们叠加在一起会增加Heap的压力，从而触发更多其他类型的GC。\n *\n * 这个操作有可能会影响到帧率，并使得用户感知到性能问题。\n *\n * Created by cundong on 2015/12/28.\n */\npublic class MemoryChurnActivity extends Activity {\n\n    private Button mButton;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        this.setContentView(R.layout.activity_test);\n\n        mButton = (Button) findViewById(R.id.button);\n        mButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                new Thread() {\n                    @Override\n                    public void run() {\n                        super.run();\n\n                        for( int i=0; i<100; i++) {\n                            Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.large_img);\n\n                            int rowBytes = bmp.getRowBytes();\n                            int height = bmp.getHeight();\n\n                            long memSize = rowBytes * height;\n                            Log.d(\"@Cundong\", \"memSize =\" + memSize + \"B =\" + memSize * 1.0 / 1024 / 1024 +\" M\");\n                            Log.d(\"@Cundong\", \"getUsedPercentValue:\" + MemoryUtil.getUsedPercentValue());\n                        }\n\n                        /**\n                        java.lang.OutOfMemoryError: Failed to allocate a 47 byte allocation with 0 free bytes and 3GB until OOM\n                        at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)\n                        at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:609)\n                        at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:444)\n                        at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:467)\n                        at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:497)\n                        at com.cundong.memory.demo.wrong.MemoryChurnActivity$1$1.run(MemoryChurnActivity.java:42)\n                         */\n                    }\n                } .start();\n            }\n        });\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/demo/wrong/StaticOutOfMemoryActivity.java",
    "content": "package com.cundong.memory.demo.wrong;\n\nimport android.app.Activity;\nimport android.graphics.drawable.Drawable;\nimport android.os.Bundle;\nimport android.widget.TextView;\n\nimport com.cundong.memory.R;\n\n/**\n * static变量引发的内存溢出\n * \n * 如果一个变量为static变量，它就属于整个类，而不是类的具体实例，所以static变量的生命周期是特别的长，如果static变量引用了一些资源耗费过多\n * 的实例，例如Context，就有内存溢出的危险。\n * \n * \n * Google开发者博客，给出了一个例子：http://android-developers.blogspot.jp/2009/01/avoiding-memory-leaks.html\n * 专门介绍长时间的引用Context导致内存溢出的情况。\n * \n * 这种情况：\n * \n * 静态的sBackground变量，虽然没有显式的持有Context的引用，但是：\n * \n * 当我们执行view.setBackgroundDrawable(Drawable drawable);之后。\n * \n * Drawable会将View设置为一个回调（通过setCallback()方法），所以就会存在这么一个隐式的引用链：Drawable持有View，View持有Context\n * \n * sBackground是静态的，生命周期特别的长，就会导致了Context的溢出。\n * \n * 解决办法：\n * \n * 1.不用activity的context 而是用Application的Context\n * \n * 2.在onDestroy()方法中，解除Activity与Drawable的绑定关系,从而去除Drawable对Activity的引用，使Context能够被回收\n * \n */\npublic class StaticOutOfMemoryActivity extends Activity {\n\t\n\tprivate static Drawable sBackground;\n\n\t@Override\n\tprotected void onCreate(Bundle state) {\n\t\tsuper.onCreate(state);\n\n\t\tTextView textView = new TextView(this);\n\t\ttextView.setText(\"Leaks are bad\");\n\t\t\n\t\tif (sBackground == null) {\n\t\t\tsBackground = getResources().getDrawable(R.drawable.large_bitmap);\n\t\t}\n\t\t\n\t\ttextView.setBackgroundDrawable(sBackground);\n\t\t\n\t\tsetContentView(textView);\n\t}\n}"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/demo/wrong/ThreadOutOfMemoryActivity.java",
    "content": "package com.cundong.memory.demo.wrong;\n\nimport java.lang.ref.WeakReference;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.Bundle;\n\nimport com.cundong.memory.R;\n\n/**\n * Thread引发的内存溢出\n * \n * 当前情况：\n * \n * 1.\n * 内部类MyThread，持有对外部ThreadOutOfMemoryActivity的隐式引用\n * \n * 2.\n * 如果我们切换横竖屏，默认就会销毁当前Activity，而这个Activity却被MyThread所持有\n * \n * 于是就出现了溢出。\n * \n * \n * 解决办法：\n * \n * 1.MyThread改为静态内部类\n * \n * 2.在线程内部采用弱引用保存Context引用\n * \n */\npublic class ThreadOutOfMemoryActivity extends Activity {\n\t\n\tprivate Context mContext;\n\t\n\t@Override\n\tprotected void onCreate(Bundle state) {\n\t\tsuper.onCreate(state);\n\t\tthis.setContentView(R.layout.activity_demo);\n\t\t\n\t\tmContext = this.getApplicationContext();\n\t\t\n\t\tnew MyThread().start();\n\t}\n\t\n\tprivate void releaseZip(WeakReference<Context> context){\n\t\t/* do something */\n\t}\n\t\n\tprivate class MyThread extends Thread {\n\t\t@Override\n\t\tpublic void run() {\n\t\t\tsuper.run();\n\n\t\t\ttry {\n\t\t\t\tThread.sleep(1000 * 60 * 2);\n\t\t\t} catch (InterruptedException e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\n\t\t\t/* do something */\n\t\t\treleaseZip(new WeakReference<>(mContext));\n\t\t}\n\t}\n}"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/service/CoreService.java",
    "content": "package com.cundong.memory.service;\n\nimport android.app.Service;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.os.Message;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.View.OnClickListener;\nimport android.widget.ImageButton;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.cundong.memory.Constants;\nimport com.cundong.memory.MainActivity;\nimport com.cundong.memory.R;\nimport com.cundong.memory.util.MemoryUtil;\nimport com.premnirmal.Magnet.IconCallback;\nimport com.premnirmal.Magnet.Magnet;\n\nimport java.lang.ref.WeakReference;\nimport java.text.DecimalFormat;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.Timer;\nimport java.util.TimerTask;\n\n/**\n * 类说明： \t后台轮询service 每1秒钟更新一次通知栏 更新内存\n * \n * @date \t2015-4-18\n * @version 1.0\n */\npublic class CoreService extends Service implements IconCallback {\n\n\tprivate static final String TAG = \"CoreService\";\n\n\tprivate Timer mTimer;\n\t\n\tprivate Magnet mMagnet;\n\n\tprivate View mIconView = null;\n\tprivate View mMemoryClearView;\n\n\tprivate TextView mDescView;\n\tprivate ImageButton mClearBtn, mSettingBtn;\n\n\tprivate InnerHandler mHandler;\n\n\t@Override\n\tpublic void onCreate() {\n\t\tsuper.onCreate();\n\n\t\tmIconView = getIconView();\n\t\tmDescView = (TextView) mIconView.findViewById(R.id.content);\n\t\tmMemoryClearView = mIconView.findViewById(R.id.memory_clear_view);\n\t\tmMemoryClearView.setVisibility(Constants.SHOW_MEMORY_CLEAR ? View.VISIBLE : View.GONE);\n\n\t\tmClearBtn = (ImageButton) mIconView.findViewById(R.id.clear_btn);\n\t\tmClearBtn.setOnClickListener( new OnClickListener(){\n\n\t\t\t@Override\n\t\t\tpublic void onClick(View v) {\n\t\t\t\t\n\t\t\t\tToast.makeText(getApplicationContext(), \"clearMemory\", Toast.LENGTH_SHORT).show();\n\t\t\t\tMemoryUtil.clearMemory(getApplicationContext());\n\t\t\t}\n\t\t});\n\t\t\n\t\tmSettingBtn = (ImageButton) mIconView.findViewById(R.id.setting_btn);\n\t\tmSettingBtn.setOnClickListener( new OnClickListener(){\n\n\t\t\t@Override\n\t\t\tpublic void onClick(View v) {\n\t\t\t\tToast.makeText(getApplicationContext(), \"test Setting\", Toast.LENGTH_SHORT).show();\n\t\t\t}\n\t\t});\n\n\t\tmMagnet = new Magnet.Builder(this)\n\t\t\t.setIconView(mIconView)\n\t\t\t.setIconCallback(this)\n\t\t\t.setRemoveIconResId(R.drawable.trash)\n\t\t\t.setRemoveIconShadow(R.drawable.bottom_shadow)\n\t\t\t.setShouldFlingAway(true)\n\t\t\t.setShouldStickToWall(true)\n\t\t\t.setRemoveIconShouldBeResponsive(true)\n\t\t\t.setInitialPosition(-100, -200)\n\t\t\t.build();\n\t\t\n\t\tmMagnet.show();\n\n\t\tmHandler = new InnerHandler(this);\n\t}\n\n\tprivate View getIconView() {\n\t\tLayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);\n\t\treturn inflater.inflate(R.layout.float_view, null);\n\t}\n\t\n\t@Override\n\tpublic IBinder onBind(Intent intent) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic int onStartCommand(Intent intent, int flags, int startId) {\n\n\t\tif (mTimer == null) {\n\t\t\tmTimer = new Timer();\n\t\t\tmTimer.scheduleAtFixedRate(new RefreshTask(), 0, 1000);\n\t\t}\n\n\t\tint action = intent != null ? intent.getIntExtra(\"action\", 0) : 0;\n\t\tif (action == 2) {\n\t\t\tmMagnet.destroy();\n\t\t\tthis.stopSelf();\n\t\t}\n\t\t\n\t\treturn super.onStartCommand(intent, flags, startId);\n\t}\n\n\t@Override\n\tpublic void onDestroy() {\n\n\t\tsuper.onDestroy();\n\n\t\tmTimer.cancel();\n\t\tmTimer = null;\n\t}\n\n\tclass RefreshTask extends TimerTask {\n\n\t\t@Override\n\t\tpublic void run() {\n\n\t\t\tString usedPercentValue = MemoryUtil.getUsedPercentValue();\n\t\t\tlong availableMemory = MemoryUtil.getAvailableMemory();\n\t\t\tHashMap<String, Long> totalPssMap = MemoryUtil.getTotalPss(Constants.PROCESS_NAME_LIST);\n\n\t\t\tfloat memory = availableMemory / (float) 1024 / (float) 1024;\n\t\t\tDecimalFormat decimalFormat = new DecimalFormat(\"##0.00\");\n\t\t\t\n\t\t\tfinal String[] content = new String[] {\n\t\t\t\t\tgetString(R.string.used_percent_value, usedPercentValue),\n\t\t\t\t\tgetString(R.string.available_memory, decimalFormat.format(memory)), getString(R.string.total_pss)};\n\n\t\t\tStringBuffer sb = new StringBuffer();\n\t\t\tsb.append(content[0]).append(\",\").append(content[1]).append(\"\\r\\n\").append(content[2]);\n\n\t\t\tIterator iterator = totalPssMap.entrySet().iterator();\n\t\t\twhile(iterator.hasNext()) {\n\t\t\t\tMap.Entry entry = (Map.Entry) iterator.next();\n\t\t\t\tentry.getKey();\n\t\t\t\tsb.append(entry.getKey()).append(\"=\").append(\"\\r\\n\").append(entry.getValue()).append(\"\\r\\n\");\n\t\t\t}\n\n\t\t\tBundle data = new Bundle();\n\t\t\tdata.putString(\"content\", sb.toString());\n\t\t\t\n\t\t\tMessage message = mHandler.obtainMessage(1);\n            message.what = 1;   \n            message.setData(data);\n\n\t\t\tmHandler.sendMessage(message);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void onFlingAway() {\n\t\tLog.i(TAG, \"onFlingAway\");\n\t}\n\n\t@Override\n\tpublic void onMove(float x, float y) {\n\t\tLog.i(TAG, \"onMove(\" + x + \",\" + y + \")\");\n\t}\n\n\t@Override\n\tpublic void onIconClick(View icon, float iconXPose, float iconYPose) {\n\t\tLog.i(TAG, \"onIconClick(..)\");\n\n\t\tIntent intent = new Intent(this, MainActivity.class);\n\t\tintent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\t\tstartActivity(intent);\n\t}\n\n\t@Override\n\tpublic void onIconDestroyed() {\n\t\tLog.i(TAG, \"onIconDestroyed()\");\n\t\tstopSelf();\n\t}\n\n\tprivate static class InnerHandler extends Handler {\n\n\t\tprivate WeakReference<CoreService> ref;\n\n\t\tpublic InnerHandler(CoreService service) {\n\t\t\tref = new WeakReference<>(service);\n\t\t}\n\n\t\t@Override\n\t\tpublic void handleMessage(Message msg) {\n\t\t\tsuper.handleMessage(msg);\n\n\t\t\tCoreService service = ref.get();\n\t\t\tif (service == null) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tswitch (msg.what) {\n\t\t\t\tcase 1:\n\t\t\t\t\tBundle data = msg.getData();\n\t\t\t\t\tString content = data.getString(\"content\");\n\t\t\t\t\tservice.mDescView.setText(content);\n\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/service/EmptyService.java",
    "content": "package com.cundong.memory.service;\n\nimport android.app.Service;\nimport android.content.Intent;\nimport android.os.IBinder;\n\n/**\n * 一个空Service，仅仅用于测试一个空进程在dalvik、art上占用多少Total Pss\n */\npublic class EmptyService extends Service {\n\n    public EmptyService() {\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        return null;\n    }\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/util/BitmapUtils.java",
    "content": "package com.cundong.memory.util;\n\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\n\npublic class BitmapUtils {\n\n    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,\n                                                         int reqWidth, int reqHeight) {\n\n        // First decode with inJustDecodeBounds=true to check dimensions\n        final BitmapFactory.Options options = new BitmapFactory.Options();\n        options.inJustDecodeBounds = true;\n        BitmapFactory.decodeResource(res, resId, options);\n\n        // Calculate inSampleSize\n        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);\n\n        // Decode bitmap with inSampleSize set\n        options.inJustDecodeBounds = false;\n        return BitmapFactory.decodeResource(res, resId, options);\n    }\n\n    public static int calculateInSampleSize(\n            BitmapFactory.Options options, int reqWidth, int reqHeight) {\n        // Raw height and width of image\n        final int height = options.outHeight;\n        final int width = options.outWidth;\n        int inSampleSize = 1;\n\n        if (height > reqHeight || width > reqWidth) {\n\n            final int halfHeight = height / 2;\n            final int halfWidth = width / 2;\n\n            // Calculate the largest inSampleSize value that is a power of 2 and keeps both\n            // height and width larger than the requested height and width.\n            while ((halfHeight / inSampleSize) > reqHeight\n                    && (halfWidth / inSampleSize) > reqWidth) {\n                inSampleSize *= 2;\n            }\n        }\n\n        return inSampleSize;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/util/Logger.java",
    "content": "package com.cundong.memory.util;\n\nimport android.util.Log;\n\npublic class Logger {\n\n\t/**\n\t * log tag\n\t */\n\tprivate String tag = \"Logger\";// application name\n\n\t// TODO 配置Log打开或关闭\n\t/**\n\t * debug or not\n\t */\n\tprivate static boolean debug = true;\n\n\tprivate static Logger instance = new Logger();\n\n\tprivate Logger() {\n\n\t}\n\n\tpublic static Logger getLogger() {\n\t\treturn instance;\n\t}\n\n\tprivate String getFunctionName() {\n\t\tStackTraceElement[] sts = Thread.currentThread().getStackTrace();\n\n\t\tif (sts == null) {\n\t\t\treturn null;\n\t\t}\n\n\t\tfor (StackTraceElement st : sts) {\n\t\t\tif (st.isNativeMethod()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (st.getClassName().equals(Thread.class.getName())) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (st.getClassName().equals(this.getClass().getName())) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\treturn \"[\" + Thread.currentThread().getName() + \"(\" + Thread.currentThread().getId() + \"): \" + st.getFileName() + \":\" + st.getLineNumber() + \"]\";\n\t\t}\n\n\t\treturn null;\n\t}\n\n\tprivate String createMessage(String msg) {\n\t\tString functionName = getFunctionName();\n\t\tString message = (functionName == null ? msg : (functionName + \" - \" + msg));\n\t\treturn message;\n\t}\n\n\t/**\n\t * log.i\n\t */\n\tpublic void i(String msg) {\n\t\tif (debug) {\n\t\t\tString message = createMessage(msg);\n\t\t\tLog.i(tag, message);\n\t\t}\n\t}\n\n\t/**\n\t * log.v\n\t */\n\tpublic void v(String msg) {\n\t\tif (debug) {\n\t\t\tString message = createMessage(msg);\n\t\t\tLog.v(tag, message);\n\t\t}\n\t}\n\n\t/**\n\t * log.d\n\t */\n\tpublic void d(String msg) {\n\t\tif (debug) {\n\t\t\tString message = createMessage(msg);\n\t\t\tLog.d(tag, message);\n\t\t}\n\t}\n\n\t/**\n\t * log.e\n\t */\n\tpublic void e(String msg) {\n\t\t// if (debug) {\n\t\tString message = createMessage(msg);\n\t\tLog.e(tag, message);\n\t\t// }\n\t}\n\n\t/**\n\t * log.error\n\t */\n\tpublic void error(Exception e) {\n\t\tif (debug) {\n\t\t\tStringBuffer sb = new StringBuffer();\n\t\t\tString name = getFunctionName();\n\t\t\tStackTraceElement[] sts = e.getStackTrace();\n\n\t\t\tif (name != null) {\n\t\t\t\tsb.append(name + \" - \" + e + \"\\r\\n\");\n\t\t\t} else {\n\t\t\t\tsb.append(e + \"\\r\\n\");\n\t\t\t}\n\t\t\tif (sts != null && sts.length > 0) {\n\t\t\t\tfor (StackTraceElement st : sts) {\n\t\t\t\t\tif (st != null) {\n\t\t\t\t\t\tsb.append(\"[ \" + st.getFileName() + \":\" + st.getLineNumber() + \" ]\\r\\n\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tLog.e(tag, sb.toString());\n\t\t}\n\t}\n\n\t/**\n\t * log.d\n\t */\n\tpublic void w(String msg) {\n\t\tif (debug) {\n\t\t\tString message = createMessage(msg);\n\t\t\tLog.w(tag, message);\n\t\t}\n\t}\n\n\tpublic void setTag(String tag) {\n\t\tthis.tag = tag;\n\t}\n\n\t/**\n\t * set debug\n\t */\n\tpublic static void setDebug(boolean d) {\n\t\tdebug = d;\n\t}\n\n\tpublic static boolean isDebug() {\n\t\treturn debug;\n\t}\n}"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/util/MemoryUtil.java",
    "content": "package com.cundong.memory.util;\r\n\r\nimport java.io.BufferedReader;\r\nimport java.io.FileReader;\r\nimport java.io.IOException;\r\nimport java.util.ArrayList;\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\n\r\nimport android.app.ActivityManager;\r\nimport android.app.ActivityManager.RunningAppProcessInfo;\r\nimport android.content.Context;\r\nimport android.os.Build;\r\nimport android.os.Debug.MemoryInfo;\r\nimport android.text.TextUtils;\r\nimport android.util.Log;\r\n\r\nimport com.cundong.memory.App;\r\nimport com.cundong.memory.Constants;\r\nimport com.jaredrummler.android.processes.ProcessManager;\r\nimport com.jaredrummler.android.processes.models.AndroidAppProcess;\r\n\r\n/**\r\n * 类说明： \t内存相关数据获取工具类\r\n *\r\n * @date \t2015-4-18\r\n * @version 1.0\r\n */\r\npublic class MemoryUtil {\r\n\r\n\t/**\r\n\t * getTotalPss，5.0+使用开源的android-processes解决方案，5.0以下使用系统api\r\n\t *\r\n\t * @param processNameList\r\n\t * @return\r\n\t */\r\n\tpublic static HashMap<String, Long> getTotalPss(ArrayList<String> processNameList) {\r\n\r\n\t\tHashMap<String, Long> resultMap = new HashMap<>();\r\n\t\tActivityManager activityMgr = (ActivityManager) App.getAppContext().getSystemService(Context.ACTIVITY_SERVICE);\r\n\r\n\t\tif (Build.VERSION.SDK_INT >= 21) {\r\n\t\t\tList<AndroidAppProcess> list = ProcessManager.getRunningAppProcesses();\r\n\r\n\t\t\tif (list != null) {\r\n\t\t\t\tfor (AndroidAppProcess processInfo : list) {\r\n\r\n\t\t\t\t\tif (processNameList.contains(processInfo.name)) {\r\n\t\t\t\t\t\tint pid = processInfo.pid;\r\n\t\t\t\t\t\tMemoryInfo[] memoryInfos = activityMgr.getProcessMemoryInfo(new int[]{pid});\r\n\r\n\t\t\t\t\t\tMemoryInfo memoryInfo = memoryInfos[0];\r\n\t\t\t\t\t\tint totalPss = memoryInfo.getTotalPss();\r\n\r\n\t\t\t\t\t\tresultMap.put(processInfo.name, new Long(totalPss));\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tList<RunningAppProcessInfo> list = activityMgr.getRunningAppProcesses();\r\n\t\t\tif (list != null) {\r\n\t\t\t\tfor (RunningAppProcessInfo processInfo : list) {\r\n\r\n\t\t\t\t\tif (Constants.PROCESS_NAME_LIST.contains(processInfo.processName)) {\r\n\t\t\t\t\t\tint pid = processInfo.pid;\r\n\t\t\t\t\t\tMemoryInfo[] memoryInfos = activityMgr.getProcessMemoryInfo(new int[] { pid });\r\n\r\n\t\t\t\t\t\tMemoryInfo memoryInfo = memoryInfos[0];\r\n\t\t\t\t\t\tint totalPss = memoryInfo.getTotalPss();\r\n\r\n\t\t\t\t\t\tresultMap.put(processInfo.processName, new Long(totalPss));\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn resultMap;\r\n\t}\r\n\r\n\t/**\r\n\t * getTotalPss，5.0+使用开源的android-processes解决方案，5.0以下使用系统api\r\n\t *\r\n\t * @param processName\r\n\t * @return\r\n\t */\r\n\tpublic static long getTotalPss(String processName) {\r\n\r\n\t\tActivityManager activityMgr = (ActivityManager) App.getAppContext().getSystemService(Context.ACTIVITY_SERVICE);\r\n\r\n\t\tif (Build.VERSION.SDK_INT >= 21) {\r\n\t\t\tList<AndroidAppProcess> list = ProcessManager.getRunningAppProcesses();\r\n\r\n\t\t\tif (list != null) {\r\n\t\t\t\tfor (AndroidAppProcess processInfo : list) {\r\n\t\t\t\t\tif (processInfo.name.equals(processName)) {\r\n\t\t\t\t\t\tint pid = processInfo.pid;\r\n\t\t\t\t\t\tMemoryInfo[] memoryInfos = activityMgr.getProcessMemoryInfo(new int[]{pid});\r\n\r\n\t\t\t\t\t\tMemoryInfo memoryInfo = memoryInfos[0];\r\n\t\t\t\t\t\tint totalPss = memoryInfo.getTotalPss();\r\n\r\n\t\t\t\t\t\treturn totalPss;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tList<RunningAppProcessInfo> list = activityMgr.getRunningAppProcesses();\r\n\t\t\tif (list != null) {\r\n\t\t\t\tfor (RunningAppProcessInfo processInfo : list) {\r\n\r\n\t\t\t\t\tif (processInfo.processName.equals(processName)) {\r\n\t\t\t\t\t\tint pid = processInfo.pid;\r\n\t\t\t\t\t\tMemoryInfo[] memoryInfos = activityMgr.getProcessMemoryInfo(new int[] { pid });\r\n\r\n\t\t\t\t\t\tMemoryInfo memoryInfo = memoryInfos[0];\r\n\t\t\t\t\t\tint totalPss = memoryInfo.getTotalPss();\r\n\r\n\t\t\t\t\t\treturn totalPss;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn -1;\r\n\t}\r\n\r\n\t/**\r\n\t * 计算已使用内存的百分比\r\n\t * \r\n\t */\r\n\tpublic static String getUsedPercentValue() {\r\n\t\tString dir = \"/proc/meminfo\";\r\n\t\ttry {\r\n\t\t\tFileReader fr = new FileReader(dir);\r\n\t\t\tBufferedReader br = new BufferedReader(fr, 2048);\r\n\t\t\tString memoryLine = br.readLine();\r\n\t\t\tString subMemoryLine = memoryLine.substring(memoryLine\r\n\t\t\t\t\t.indexOf(\"MemTotal:\"));\r\n\t\t\tbr.close();\r\n\t\t\tlong totalMemorySize = Integer.parseInt(subMemoryLine.replaceAll(\r\n\t\t\t\t\t\"\\\\D+\", \"\"));\r\n\t\t\tlong availableSize = getAvailableMemory() / 1024;\r\n\t\t\tint percent = (int) ((totalMemorySize - availableSize)\r\n\t\t\t\t\t/ (float) totalMemorySize * 100);\r\n\t\t\treturn percent + \"%\";\r\n\t\t} catch (IOException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t}\r\n\t\treturn \"\";\r\n\t}\r\n\t\r\n\t/**\r\n\t * 获取可用内存\r\n\t * \r\n\t */\r\n\tpublic static long getAvailableMemory() {\r\n\r\n\t\tActivityManager activityManager = (ActivityManager) App.getAppContext()\r\n\t\t\t\t.getSystemService(Context.ACTIVITY_SERVICE);\r\n\r\n\t\tActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();\r\n\t\tactivityManager.getMemoryInfo(mi);\r\n\r\n\t\treturn mi.availMem;\r\n\t}\r\n\r\n\tpublic static void clearMemory(Context context) {\r\n\t\tActivityManager activityManger = (ActivityManager) App.getAppContext().getSystemService(Context.ACTIVITY_SERVICE);\r\n\r\n\r\n\t\tif (Build.VERSION.SDK_INT >= 21) {\r\n\t\t\tList<AndroidAppProcess> list = ProcessManager.getRunningAppProcesses();\r\n\r\n\t\t\tif (list != null) {\r\n\t\t\t\tfor (AndroidAppProcess processInfo : list) {\r\n\r\n\t\t\t\t\tint myPid = android.os.Process.myPid();\r\n\r\n\t\t\t\t\tif(myPid == processInfo.pid) {\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (!processInfo.foreground) {\r\n\t\t\t\t\t\tactivityManger.killBackgroundProcesses(processInfo.getPackageName());\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tList<ActivityManager.RunningAppProcessInfo> list = activityManger.getRunningAppProcesses();\r\n\r\n\t\t\tif (list != null) {\r\n\t\t\t\tfor (int i = 0; i < list.size(); i++) {\r\n\t\t\t\t\tActivityManager.RunningAppProcessInfo appProcessInfo = list.get(i);\r\n\r\n\t\t\t\t\tString[] pkgList = appProcessInfo.pkgList;\r\n\r\n\t\t\t\t\tif (appProcessInfo.importance >= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {\r\n\r\n\t\t\t\t\t\tfor (int j = 0; j < pkgList.length; j++) {\r\n\r\n\t\t\t\t\t\t\tif (pkgList[j].equals(context.getPackageName())) {\r\n\t\t\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\tactivityManger.killBackgroundProcesses(pkgList[j]);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "app/src/main/java/com/cundong/memory/util/ViewUtils.java",
    "content": "package com.cundong.memory.util;\n\nimport android.view.View;\nimport android.view.ViewGroup;\n\npublic class ViewUtils {\n\n    public static void unbindDrawables(View view) {\n        if (view.getBackground() != null) {\n            view.getBackground().setCallback(null);\n        }\n\n        if (view instanceof ViewGroup) {\n            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {\n                unbindDrawables(((ViewGroup) view).getChildAt(i));\n            }\n            ((ViewGroup) view).removeAllViews();\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/res/anim/slide_down.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <translate\n        android:fromYDelta=\"0%\"\n        android:toYDelta=\"100%\"\n        android:duration=\"@android:integer/config_shortAnimTime\"/>\n</set>"
  },
  {
    "path": "app/src/main/res/anim/slide_up.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <translate\n        android:fromYDelta=\"100%\"\n        android:toYDelta=\"0%\"\n        android:duration=\"@android:integer/config_shortAnimTime\"/>\n</set>"
  },
  {
    "path": "app/src/main/res/layout/activity_demo.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\" >\n\n    <TextView\n        android:id=\"@+id/desc\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"10dip\"\n        android:text=\"demo\"\n        android:textSize=\"18sp\" />\n\n    <ImageView\n        android:id=\"@+id/image\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:src=\"@drawable/large_bitmap\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    tools:context=\"com.cundong.memory.MainActivity\">\n\n    <Button\n        android:id=\"@+id/button1\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/startmonitor\" />\n\n    <Button\n        android:id=\"@+id/button2\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/button1\"\n        android:text=\"@string/stopmonitor\" />\n\n    <Button\n        android:id=\"@+id/button3\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/button2\"\n        android:text=\"@string/clearmemory\" />\n\n    <Button\n        android:id=\"@+id/button4\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/button3\"\n        android:text=\"Test EmptyProcess\" />\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_outofmemory_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    tools:context=\"com.cundong.memory.MainActivity\" >\n\n    <TextView\n        android:id=\"@+id/desc\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textSize=\"18sp\" />\n\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\" >\n\n    <Button\n        android:id=\"@+id/button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Start\"\n         />\n\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/layout/float_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/floating_layer_black\"\n    android:gravity=\"center_vertical|left\"\n    android:orientation=\"vertical\"\n    android:padding=\"4dip\">\n\n    <TextView\n        android:id=\"@+id/content\"\n        android:layout_width=\"@dimen/float_width\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"10dip\"\n        android:lineSpacingMultiplier=\"1.2\"\n        android:textColor=\"#000000\"\n        android:textSize=\"12sp\" />\n\n    <RelativeLayout\n        android:id=\"@+id/memory_clear_view\"\n        android:layout_width=\"@dimen/float_width\"\n        android:layout_height=\"wrap_content\">\n\n        <ImageButton\n            android:id=\"@+id/clear_btn\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentLeft=\"true\"\n            android:layout_centerInParent=\"true\"\n            android:background=\"@drawable/sillding_menu_mobile_clear\" />\n\n        <ImageButton\n            android:id=\"@+id/setting_btn\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_centerInParent=\"true\"\n            android:background=\"@drawable/sliding_explorer_icon\" />\n    </RelativeLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/menu/main.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:context=\"com.example.testmemo.MainActivity\" >\n\n    <item\n        android:id=\"@+id/action_settings\"\n        android:orderInCategory=\"100\"\n        android:showAsAction=\"never\"\n        android:title=\"@string/action_settings\"/>\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"colorPrimaryDark\">#303F9F</color>\n    <color name=\"colorAccent\">#FF4081</color>\n\n    <color name=\"colorTransparent\">#00000000</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<resources>\n\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n\n    <dimen name=\"float_width\">160dip</dimen>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"app_name\">MemoryMonitor</string>\n    <string name=\"hello_world\">Hello world!</string>\n    <string name=\"action_settings\">Settings</string>\n    \n    <string name=\"startmonitor\">开始监控</string>\n    <string name=\"stopmonitor\">停止监控</string>\n    <string name=\"clearmemory\">清理内存</string>\n    <string name=\"used_percent_value\">内存使用:%1$s</string>\n    <string name=\"available_memory\">可用:%1$sM</string>\n    <string name=\"total_pss\">Pss Total:\\r\\n</string>\n\n    <string name=\"permission_err\">当前targets API level 23+, 必须手动设置“允许在其他应用上层显示”开启才行</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n    <style name=\"AppTheme.NoActionBar\">\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n    </style>\n\n    <style name=\"AppTheme.AppBarOverlay\" parent=\"ThemeOverlay.AppCompat.Dark.ActionBar\" />\n\n    <style name=\"AppTheme.PopupOverlay\" parent=\"ThemeOverlay.AppCompat.Light\" />\n\n    <style name=\"Transparent\" parent=\"AppTheme.NoActionBar\">\n        <item name=\"android:windowBackground\">@color/colorTransparent</item>\n        <item name=\"android:windowIsTranslucent\">true</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-v11/styles.xml",
    "content": "<resources>\n\n    <!--\n        Base application theme for API 11+. This theme completely replaces\n        AppBaseTheme from res/values/styles.xml on API 11+ devices.\n    -->\n    <style name=\"AppBaseTheme\" parent=\"android:Theme.Holo.Light\">\n        <!-- API 11 theme customizations can go here. -->\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-v14/styles.xml",
    "content": "<resources>\n\n    <!--\n        Base application theme for API 14+. This theme completely replaces\n        AppBaseTheme from BOTH res/values/styles.xml and\n        res/values-v11/styles.xml on API 14+ devices.\n    -->\n    <style name=\"AppBaseTheme\" parent=\"android:Theme.Holo.Light.DarkActionBar\">\n        <!-- API 14 theme customizations can go here. -->\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n\n    <!--\n         Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively).\n    -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n\n</resources>\n"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    repositories {\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:1.5.0'\n\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        jcenter()\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Wed Oct 21 11:34:03 PDT 2015\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-2.8-all.zip\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched.\nif $cygwin ; then\n    [ -n \"$JAVA_HOME\" ] && JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\nfi\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >&-\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >&-\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "magnet/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "magnet/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion 23\n    buildToolsVersion \"22.0.1\"\n    \n    lintOptions {\n          abortOnError false\n    }\n\n    defaultConfig {\n        minSdkVersion 3\n        targetSdkVersion 22\n        versionCode 1\n        versionName \"1.0\"\n    }\n}\n"
  },
  {
    "path": "magnet/gradle.properties",
    "content": "POM_NAME=Magnet\nPOM_ARTIFACT_ID=library\nPOM_PACKAGING=aar"
  },
  {
    "path": "magnet/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/prem/Documents/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "magnet/src/androidTest/java/com/premnirmal/Magnet/ApplicationTest.java",
    "content": "package com.premnirmal.Magnet;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http://d.android.com/tools/testing/testing_android.html\">Testing Fundamentals</a>\n */\npublic class ApplicationTest extends ApplicationTestCase<Application> {\n    public ApplicationTest() {\n        super(Application.class);\n    }\n}"
  },
  {
    "path": "magnet/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest\n    package=\"com.premnirmal.Magnet\" >\n\n    <application/>\n\n</manifest>\n"
  },
  {
    "path": "magnet/src/main/java/com/premnirmal/Magnet/FlingListener.java",
    "content": "package com.premnirmal.Magnet;\n\nimport android.view.GestureDetector;\nimport android.view.MotionEvent;\n\n/**\n * Created by prem on 7/20/14.\n */\nclass FlingListener extends GestureDetector.SimpleOnGestureListener {\n\n    private static final float FLING_THRESHOLD_VELOCITY = 50f;\n\n    @Override\n    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {\n        return (Math.abs(e2.getX() - e1.getX()) < FLING_THRESHOLD_VELOCITY && e2.getY() - e1.getY() > FLING_THRESHOLD_VELOCITY);\n    }\n\n}"
  },
  {
    "path": "magnet/src/main/java/com/premnirmal/Magnet/IconCallback.java",
    "content": "package com.premnirmal.Magnet;\n\nimport android.view.View;\n\n/**\n * Created by prem on 7/20/14.\n * Desc: Interface that gives the user callbacks for when the MagnetIcon has been interacted with.\n */\npublic interface IconCallback {\n\n    /**\n     * Insert code for what to do when the icon has been flung away\n     */\n    public void onFlingAway();\n\n    /**\n     * Callback for when the icon has been dragged by the user\n     * @param x x coordiante on the screen in pixels\n     * @param y y coordinate on the screen in pixels\n     */\n    public void onMove(float x, float y);\n\n    /**\n     * Callback for when the icon has been clicked. Perform any action such as launch your app,\n     * or show a menu, etc.\n     * @param icon the view holding the icon. Get context from this view.\n     * @param iconXPose the x coordinate of the icon in pixels\n     * @param iconYPose the y coordiante of the icon in pixels\n     */\n    public void onIconClick(View icon, float iconXPose, float iconYPose);\n\n\n    /**\n     * Callback for when the icon has been destroyed. Usually you should stop your service in this.\n     */\n    public void onIconDestroyed();\n}\n"
  },
  {
    "path": "magnet/src/main/java/com/premnirmal/Magnet/Magnet.java",
    "content": "package com.premnirmal.Magnet;\n\nimport android.content.Context;\nimport android.graphics.PixelFormat;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.util.DisplayMetrics;\nimport android.view.GestureDetector;\nimport android.view.Gravity;\nimport android.view.LayoutInflater;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.WindowManager;\nimport android.widget.RelativeLayout;\n\n/**\n * Created by prem on 7/20/14.\n * Desc: Class holding the Magnet Icon, and performing touchEvents on the view.\n */\npublic class Magnet implements View.OnTouchListener {\n\n    protected static final int TOUCH_TIME_THRESHOLD = 200;\n\n    protected View mIconView;\n    protected RemoveView mRemoveView;\n    protected WindowManager mWindowManager;\n    protected WindowManager.LayoutParams mLayoutParams;\n    protected Context mContext;\n    protected GestureDetector mGestureDetector;\n    protected boolean shouldStickToWall = true;\n    protected boolean shouldFlingAway = true;\n    protected IconCallback mListener;\n    protected MoveAnimator mAnimator;\n\n    protected long lastTouchDown;\n    protected float lastXPose, lastYPose;\n    protected boolean isBeingDragged = false;\n    protected int mWidth, mHeight;\n\n    protected int mInitialX = -1, mInitialY = -1;\n    /**\n     * Builder class to create your {@link Magnet}\n     */\n    public static class Builder {\n\n        Magnet magnet;\n\n        public Builder(Context context) {\n            magnet = new Magnet(context);\n        }\n\n        /**\n         * The Icon must have a view, provide a view or a layout using {@link #setIconView(int)}\n         * @param iconView the view representing the icon\n         * @return\n         */\n        public Builder setIconView(View iconView) {\n            magnet.mIconView = iconView;\n            magnet.mIconView.setOnTouchListener(magnet);\n            return this;\n        }\n\n        /**\n         * Use an xml layout to provide the button view\n         * @param iconViewRes the layout id of the icon\n         * @return\n         */\n        public Builder setIconView(int iconViewRes) {\n            magnet.mIconView = LayoutInflater.from(magnet.mContext).inflate(iconViewRes, null);\n            magnet.mIconView.setOnTouchListener(magnet);\n            return this;\n        }\n\n        /**\n         * whether your magnet sticks to the edge of your screen when you release it\n         * @param shouldStick\n         * @return\n         */\n        public Builder setShouldStickToWall(boolean shouldStick) {\n            magnet.shouldStickToWall = shouldStick;\n            return this;\n        }\n\n        /**\n         * whether you can fling away your Magnet towards the bottom of the screen\n         * @param shoudlFling\n         * @return\n         */\n        public Builder setShouldFlingAway(boolean shoudlFling) {\n            magnet.shouldFlingAway = shoudlFling;\n            return this;\n        }\n\n        /**\n         * Callback for when the icon moves, or when it isis flung away and destroyed\n         * @param callback\n         * @return\n         */\n        public Builder setIconCallback(IconCallback callback) {\n            magnet.mListener = callback;\n            return this;\n        }\n\n        /**\n         *\n         * @param shouldBeResponsive\n         * @return\n         */\n        public Builder setRemoveIconShouldBeResponsive(boolean shouldBeResponsive) {\n            magnet.mRemoveView.shouldBeResponsive = shouldBeResponsive;\n            return this;\n        }\n\n        /**\n         * you can set a custom remove icon or use the default one\n         * @param removeIconResId\n         * @return\n         */\n        public Builder setRemoveIconResId(int removeIconResId) {\n            magnet.mRemoveView.setIconResId(removeIconResId);\n            return this;\n        }\n\n        /**\n         * you can set a custom remove icon shadow or use the default one\n         * @param shadow\n         * @return\n         */\n        public Builder setRemoveIconShadow(int shadow) {\n            magnet.mRemoveView.setShadowBG(shadow);\n            return this;\n        }\n\n        /**\n         * Set the initial coordinates of the magnet\n         * @param x\n         * @param y\n         * @return\n         */\n        public Builder setInitialPosition(int x, int y) {\n            magnet.mInitialX = x;\n            magnet.mInitialY = y;\n            return this;\n        }\n\n        public Magnet build() {\n            if(magnet.mIconView == null) {\n                throw new NullPointerException(\"Magnet view is null! Must set a view for the magnet!\");\n            }\n            return magnet;\n        }\n    }\n\n\n    protected Magnet(Context context) {\n        mContext = context;\n        mGestureDetector = new GestureDetector(context, new FlingListener());\n        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);\n        mAnimator = new MoveAnimator();\n        mRemoveView = new RemoveView(context);\n    }\n\n    /**\n     * Show the Magnet i.e. add it to the Window\n     */\n    public void show() {\n        addToWindow(mIconView);\n        updateSize();\n        if(mInitialX != -1 || mInitialY != -1) {\n            setPosition(mInitialX, mInitialY, true);\n        } else {\n            goToWall();\n        }\n    }\n\n    protected void addToWindow(View icon) {\n        WindowManager.LayoutParams params = new WindowManager.LayoutParams(\n                WindowManager.LayoutParams.WRAP_CONTENT,\n                WindowManager.LayoutParams.WRAP_CONTENT,\n                WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,\n                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |\n                        WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,\n                PixelFormat.TRANSLUCENT\n        );\n\n        try {\n            mWindowManager.addView(icon, mLayoutParams = params);\n        } catch(Exception e) {\n            // ignore\n            // if targetSdkVersion >=23 addView maybe crash here.\n            // http://developer.android.com/intl/zh-cn/reference/android/Manifest.permission.html\n        }\n    }\n\n    protected void updateSize() {\n        final DisplayMetrics metrics = new DisplayMetrics();\n        mWindowManager.getDefaultDisplay().getMetrics(metrics);\n        mWidth = (metrics.widthPixels - mIconView.getWidth()) / 2;\n        mHeight = (metrics.heightPixels - mIconView.getHeight()) / 2;\n    }\n\n    @Override\n    public boolean onTouch(View view, MotionEvent event) {\n        boolean eaten = false;\n        if (shouldFlingAway) {\n            eaten = mGestureDetector.onTouchEvent(event);\n        }\n        if (eaten) {\n            flingAway();\n        } else {\n            float x = event.getRawX();\n            float y = event.getRawY();\n            int action = event.getAction();\n            if (action == MotionEvent.ACTION_DOWN) {\n                showRemoveView();\n                lastTouchDown = System.currentTimeMillis();\n                mAnimator.stop();\n                updateSize();\n                isBeingDragged = true;\n            } else if (action == MotionEvent.ACTION_UP) {\n                if (System.currentTimeMillis() - lastTouchDown < TOUCH_TIME_THRESHOLD) {\n                    if (mListener != null) {\n                        mListener.onIconClick(mIconView, x, y);\n                    }\n                }\n                hideRemoveView();\n                isBeingDragged = false;\n                eaten = false;\n                goToWall();\n            } else if (action == MotionEvent.ACTION_MOVE) {\n                if (isBeingDragged) {\n                    move(x - lastXPose, y - lastYPose);\n                }\n            }\n\n            lastXPose = x;\n            lastYPose = y;\n        }\n        return eaten;\n    }\n\n    protected void flingAway() {\n        if (shouldFlingAway) {\n            int y = mContext.getResources().getDisplayMetrics().heightPixels / 2;\n            int x = 0;\n            mAnimator.start(x, y);\n            if (mListener != null) {\n                mListener.onFlingAway();\n            }\n            destroy();\n        }\n    }\n\n    protected void showRemoveView() {\n        if (mRemoveView != null && shouldFlingAway) {\n            mRemoveView.show();\n        }\n    }\n\n    protected void hideRemoveView() {\n        if (mRemoveView != null && shouldFlingAway) {\n            mRemoveView.hide();\n        }\n    }\n\n    protected void goToWall() {\n        if (shouldStickToWall) {\n            float nearestXWall = mLayoutParams.x >= 0 ? mWidth : -mWidth;\n            float nearestYWall = mLayoutParams.y > 0 ? mHeight : -mHeight;\n            if (Math.abs(mLayoutParams.x - nearestXWall) < Math.abs(mLayoutParams.y - nearestYWall)) {\n                mAnimator.start(nearestXWall, mLayoutParams.y);\n            } else {\n                mAnimator.start(mLayoutParams.x, nearestYWall);\n            }\n        }\n    }\n\n    protected void move(float deltaX, float deltaY) {\n        mLayoutParams.x += deltaX;\n        mLayoutParams.y += deltaY;\n        if (mRemoveView != null && shouldFlingAway) {\n            mRemoveView.onMove(mLayoutParams.x, mLayoutParams.y);\n        }\n        mWindowManager.updateViewLayout(mIconView, mLayoutParams);\n        if (mListener != null) {\n            mListener.onMove(mLayoutParams.x, mLayoutParams.y);\n        }\n        if (shouldFlingAway && !isBeingDragged && Math.abs(mLayoutParams.x) < 50\n                && Math.abs(mLayoutParams.y - (mContext.getResources().getDisplayMetrics().heightPixels / 2)) < 250) {\n            flingAway();\n        }\n    }\n\n    /**\n     * Destroys the magnet - removes the view from the WindowManager and calls\n     * {@link IconCallback#onIconDestroyed()}\n     */\n    public void destroy() {\n        mWindowManager.removeView(mIconView);\n        if (mRemoveView != null) {\n            mRemoveView.destroy();\n        }\n        if (mListener != null) {\n            mListener.onIconDestroyed();\n        }\n        mContext = null;\n    }\n\n    /**\n     * Set the position of the Magnet.\n     * Note: must be called **after** {@link #show()} is called.\n     * This will call {@link IconCallback#onMove(float, float)} on your listener\n     * @param x the x position\n     * @param y the y position\n     * @param animate whether the Magnet should animate to that position. If false the Magnet\n     *                will simply just set its coordinates to the given position\n     */\n    public void setPosition(int x, int y, boolean animate) {\n        if(animate) {\n            mAnimator.start(x, y);\n        } else {\n            mLayoutParams.x = x;\n            mLayoutParams.y = y;\n            mWindowManager.updateViewLayout(mIconView, mLayoutParams);\n            if (mListener != null) {\n                mListener.onMove(mLayoutParams.x, mLayoutParams.y);\n            }\n        }\n    }\n\n    protected class MoveAnimator implements Runnable {\n\n        protected Handler handler = new Handler(Looper.getMainLooper());\n        protected float destinationX;\n        protected float destinationY;\n        protected long startingTime;\n\n        protected void start(float x, float y) {\n            this.destinationX = x;\n            this.destinationY = y;\n            startingTime = System.currentTimeMillis();\n            handler.post(this);\n        }\n\n        @Override\n        public void run() {\n            if (mIconView != null && mIconView.getParent() != null) {\n                float progress = Math.min(1, (System.currentTimeMillis() - startingTime) / 400f);\n                float deltaX = (destinationX - mLayoutParams.x) * progress;\n                float deltaY = (destinationY - mLayoutParams.y) * progress;\n                move(deltaX, deltaY);\n                if (progress < 1) {\n                    handler.post(this);\n                }\n            }\n        }\n\n        protected void stop() {\n            handler.removeCallbacks(this);\n        }\n\n    }\n}\n"
  },
  {
    "path": "magnet/src/main/java/com/premnirmal/Magnet/RemoveView.java",
    "content": "package com.premnirmal.Magnet;\n\nimport android.content.Context;\nimport android.graphics.PixelFormat;\nimport android.util.Log;\nimport android.view.Gravity;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.WindowManager;\nimport android.view.animation.Animation;\nimport android.widget.ImageView;\nimport android.widget.RelativeLayout;\n\n/**\n * Created by prem on 7/20/14.\n * ViewHolder for the remove Icon.\n */\nclass RemoveView {\n\n    View mLayout;\n    View mButton;\n    View mShadow;\n    ImageView mButtonImage;\n    private WindowManager mWindowManager;\n    private SimpleAnimator mShowAnim;\n    private SimpleAnimator mHideAnim;\n\n    private SimpleAnimator mShadowFadeOut;\n    private SimpleAnimator mShadowFadeIn;\n\n    private final int buttonBottomPadding;\n\n    boolean shouldBeResponsive = true;\n\n    RemoveView(Context context) {\n        mLayout = LayoutInflater.from(context).inflate(R.layout.x_button_holder, null);\n        mButton = mLayout.findViewById(R.id.xButton);\n        mButtonImage = (ImageView) mLayout.findViewById(R.id.xButtonImg);\n        mButtonImage.setImageResource(R.drawable.trash);\n        buttonBottomPadding = mButton.getPaddingBottom();\n        mShadow = mLayout.findViewById(R.id.shadow);\n        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);\n        addToWindow(mLayout);\n        mShowAnim = new SimpleAnimator(mButton, R.anim.slide_up);\n        mHideAnim = new SimpleAnimator(mButton, R.anim.slide_down);\n        mShadowFadeIn = new SimpleAnimator(mShadow, android.R.anim.fade_in);\n        mShadowFadeOut = new SimpleAnimator(mShadow, android.R.anim.fade_out);\n        hide();\n    }\n\n    void setIconResId(int id) {\n        mButtonImage.setImageResource(id);\n    }\n\n    void setShadowBG(int shadowBG) {\n        mShadow.setBackgroundResource(shadowBG);\n    }\n\n    void show() {\n        if (mLayout != null && mLayout.getParent() == null) {\n            addToWindow(mLayout);\n        }\n        mShadowFadeIn.startAnimation();\n        mShowAnim.startAnimation();\n    }\n\n    void hide() {\n        mShadowFadeOut.startAnimation();\n        mHideAnim.startAnimation(new Animation.AnimationListener() {\n            @Override\n            public void onAnimationStart(Animation animation) {\n\n            }\n\n            @Override\n            public void onAnimationEnd(Animation animation) {\n                if (mLayout != null && mLayout.getParent() != null) {\n                    mWindowManager.removeView(mLayout);\n                }\n            }\n\n            @Override\n            public void onAnimationRepeat(Animation animation) {\n\n            }\n        });\n    }\n\n    void onMove(final float x, final float y) {\n        if (shouldBeResponsive) {\n            final int xTransformed = (int) Math.abs(x * 100 / (mButton.getContext().getResources().getDisplayMetrics().widthPixels / 2));\n            final int bottomPadding = buttonBottomPadding - (xTransformed / 5);\n            if (x < 0) {\n                mButton.setPadding(0, 0, xTransformed, bottomPadding);\n            } else {\n                mButton.setPadding(xTransformed, 0, 0, bottomPadding);\n            }\n        }\n    }\n\n    void destroy() {\n        if (mLayout != null && mLayout.getParent() != null) {\n            mWindowManager.removeView(mLayout);\n        }\n        mLayout = null;\n        mWindowManager = null;\n    }\n\n    private void addToWindow(View layout) {\n//        WindowManager.LayoutParams params = new WindowManager.LayoutParams(\n//                WindowManager.LayoutParams.MATCH_PARENT,\n//                WindowManager.LayoutParams.MATCH_PARENT,\n//                WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,\n//                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,\n//                PixelFormat.TRANSLUCENT\n//        );\n\n        WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();\n        mParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;\n        mParams.width = RelativeLayout.LayoutParams.MATCH_PARENT;\n        mParams.height = RelativeLayout.LayoutParams.MATCH_PARENT;\n        mParams.gravity = Gravity.CENTER;\n\n        mParams.format = PixelFormat.TRANSLUCENT;\n        mParams.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN\n                | WindowManager.LayoutParams.FLAG_DIM_BEHIND\n                | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;\n        mParams.dimAmount = 0.4f;\n\n        try {\n            mWindowManager.addView(layout, mParams);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "magnet/src/main/java/com/premnirmal/Magnet/SimpleAnimator.java",
    "content": "package com.premnirmal.Magnet;\n\nimport android.view.View;\nimport android.view.animation.Animation;\nimport android.view.animation.AnimationUtils;\n\nimport java.lang.ref.WeakReference;\n\n/**\n * Created by prem on 7/20/14.\n * A class that takes care of animating a view in a simple way.\n */\nclass SimpleAnimator {\n\n    private WeakReference<View> mViewRef;\n    private int animation;\n\n    public SimpleAnimator(View view, int anim) {\n        this.animation = anim;\n        this.mViewRef = new WeakReference<View>(view);\n    }\n\n    public void startAnimation() {\n        startAnimation(null);\n    }\n\n    public void startAnimation(Animation.AnimationListener listener) {\n        mViewRef.get().clearAnimation();\n        Animation anim = AnimationUtils.loadAnimation(mViewRef.get().getContext(), animation);\n        if(listener != null) {\n            anim.setAnimationListener(listener);\n        }\n        anim.setFillAfter(true);\n        mViewRef.get().startAnimation(anim);\n    }\n\n}\n"
  },
  {
    "path": "magnet/src/main/res/anim/slide_down.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <translate\n        android:fromYDelta=\"0%\"\n        android:toYDelta=\"100%\"\n        android:duration=\"@android:integer/config_shortAnimTime\"/>\n</set>"
  },
  {
    "path": "magnet/src/main/res/anim/slide_up.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <translate\n        android:fromYDelta=\"100%\"\n        android:toYDelta=\"0%\"\n        android:duration=\"@android:integer/config_shortAnimTime\"/>\n</set>"
  },
  {
    "path": "magnet/src/main/res/drawable/bottom_shadow.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item>\n        <shape android:shape=\"rectangle\">\n            <gradient android:startColor=\"#05000000\"\n                android:endColor=\"#0096ff\"\n                android:type=\"linear\"\n                android:angle=\"270\"\n                />\n            <corners android:radius=\"2dp\" />\n        </shape>\n    </item>\n</selector>"
  },
  {
    "path": "magnet/src/main/res/layout/x_button_holder.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                xmlns:tools=\"http://schemas.android.com/tools\"\n                tools:background=\"@color/white\"\n                android:orientation=\"vertical\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\">\n\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"80dp\"\n        android:id=\"@+id/shadow\"\n        android:background=\"@drawable/bottom_shadow\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_centerHorizontal=\"true\"/>\n\n\n    <FrameLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:paddingBottom=\"@dimen/default_padding_bottom\"\n        android:id=\"@+id/xButton\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_centerHorizontal=\"true\">\n\n        <ImageView\n            android:layout_width=\"60dp\"\n            android:layout_height=\"60dp\"\n            android:id=\"@+id/xButtonImg\"/>\n    </FrameLayout>\n</RelativeLayout>"
  },
  {
    "path": "magnet/src/main/res/menu/paranormal.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:context=\".ParanormalActivity\" >\n    <item android:id=\"@+id/action_settings\"\n        android:title=\"@string/action_settings\"\n        android:orderInCategory=\"100\"\n        android:showAsAction=\"never\" />\n</menu>\n"
  },
  {
    "path": "magnet/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <dimen name=\"default_padding_bottom\">55dp</dimen>\n</resources>\n"
  },
  {
    "path": "magnet/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"app_name\">Magnet</string>\n    <string name=\"hello_world\">Hello world!</string>\n    <string name=\"action_settings\">Settings</string>\n\n</resources>\n"
  },
  {
    "path": "magnet/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"android:Theme.Holo.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n    </style>\n\n</resources>\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':app', ':magnet'\n"
  }
]