【Android工具(LeakCanary—内存泄露检测神器)】丈夫志四海,万里犹比邻。这篇文章主要讲述Android工具:LeakCanary—内存泄露检测神器相关的知识,希望能为你提供帮助。
一、LeakCanary简介
LeakCanary是Square公司开源的一个检测内存的泄露的函数库,
可以方便地和你的项目进行集成,
在Debug版本中监控Activity、Fragment等的内存泄露;
LeakCanary集成到项目中之后,
在检测到内存泄露时,
会发送消息到系统通知栏。点击后打开名称DisplayLeakActivity的页面,
并显示泄露的跟踪信息,
Logcat上面也会有对应的日志输出。同时如果跟踪信息不足以定位时,
DisplayLeakActivity还为开发者默认保存了最近7个dump文件到App的目录中,
可以使用MAT等工具对dump文件进行进一步的分析;
二、内存泄漏简介
在了解了LeakCanary的接入方式后,
我们肯定着急想见识见识LeakCanary的威力。在跟大家演示LeakCanary检测和处理构成之前,
大家应该明应该对内存泄露有基本的了解和认识;
1.为什么会产生内存泄漏?
当一个对象不需要使用本该回收时,
有另外一个正在使用的对象持有它的引用,
从而导致它不能回收停留在堆内存中,
这就产生了内存泄漏;
2.内存泄露对程序产生的影响?
内存泄漏是造成应用程序OOM的主要原因之一。android系统为每个应用程序分配有限的内存,
当应用中内存泄漏较多时,
就难免会导致应用所需要的内存超出系统分配限额,
从而导致OOM应用Crash;
3.Android常见的内存泄露?
相信内存泄露对大家都早有耳闻,
但是它不像一些java异常情况,
会立即造成程序的Crash,
却有让大家比较“陌生”。下面我们就列举出日常开发中常见的内存泄露类型,
让大家对内存泄露的认识不仅仅停留在“有所耳闻 ”的层面;
单例造成:
由于单例静态特性使得单例的生命周期和应用的生命周期一样长,
如果一个对象(
如Context)
已经不使用了,
而单例对象还持有对象的引用造成这个对象不能正常被回收;
非静态内部类创建静态实例造成:
在Acitivity内存创建一个非静态内部类单例,
避免每次启动资源重新创建。但是因为非静态内部类默认持有外部类(
Activity)
的引用,
并且使用该类创建静态实例。造成该实例和应用生命周期一样长,
导致静态实例持有引用的Activity和资源不能正常回收;
Handler造成:
子线程执行网络任务,
使用Handler处理子线程发送消息。由于handler对象是非静态匿名内部类的对象,
持有外部类(
Activity)
的引用。在Handler-Message中Looper线程不断轮询处理消息,
当Activity退出还有未处理或者正在处理的消息时,
消息队列中的消息持有handler对象引用,
handler又持有Activity,
导致Activity的内存和资源不能及时回收;
线程造成:
匿名内部类Runnalbe和AsyncTask对象执行异步任务,
对当前Activity隐式引用。当Activity销毁之前,
任务还没有执行完,
将导致Activity的内存和资源不能及时回收;
资源未关闭造成的内存泄露:
对于使用了BroadcastReceiver,
ContentObserver,
File,
Cursor,
Stream,
Bitmap等资源的使用,
应该在Activity销毁时及时关闭或者注销,
否则这些资源将不会被回收,
造成内存泄露;
三、LeakCanary接入
下面我们还是以QProject项目进行演示如何在项目中接入LeakCanary,
项目目录如下:
文章图片
1.添加LeakCanary依赖
在主项目main模块的build.gradle文件中添加LeakCanary相关依赖;
/main/build.gradle文件
apply plugin: '
com.android.application'
android {
......
}
dependencies {
... ...
//添加leakcanary相关的依赖
//在release和test版本中,
使用的是LeakCanary的no-op版本,
也就是没有实际代码和操作的Wrapper版本,
只包含LeakCanary和RefWatcher类的空实现,
这样不会对生成的APK包体积和应用性能造成影响
debugCompile '
com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile '
com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile '
com.squareup.leakcanary:leakcanary-android-no-op:1.5'
... ...
compile project('
:test'
)
}
2.初始化LeakCanary
在主项目main模块的QApplication的onCreate()方法中初始化LeakCanary;
/main/src/main/java/com/qproject/main/QApplication.java文件
public class QAplication extends Application{
@
Override
public void onCreate() {
super.onCreate();
... ...
//初始化LeakCanary
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
}
OK, 到这里我们就完成了一个项目的LeankCanary的简单接入;
提示1: 集成LeakCanary后, 构建和安装apk的时候报错如下:
Error:Error converting bytecode to dex:
Cause: com.android.dex.DexException: Multiple dex files define Lcom/squareup/leakcanary/LeakCanary;
Error:Execution failed for task ' :main:transformClassesWithDexForDebug' .
> com.android.build.api.transform.TransformException: com.android.ide.common.process.ProcessException: java.util.concurrent.ExecutionException: java.lang.UnsupportedOperationException
处理1: 添加依赖debugCompile ' com.squareup.haha:haha:2.0.3' , 修改依赖的版本为1.4-beta2;
四、LeakCanary检测
通过对一些常见的内存泄露的学习, 我们已经对内存泄露有所见闻了。那么下面我们通过LeakCanary工具, 让大家感受下它在你的日常开发中的真实存在。我们以常见内存—单例造成的内存泄露为例进行实践;
1.单例内存泄露模拟
test/src/main/com/qproject/test/TestManager.java
public class TestManager {
//单例静态特性使得单例的生命周期和应用的生命周期一样长
private static TestManager instance;
private Context context;
/**
* 传入的Context的生命周期很重要:
*如果传入的是Application的Context,
则生命周期和单例生命周期一样长;
*如果传入的是Activity的Context,
由于该Context和Activity的生命周期一样长,
当Activity退出的时候它的内存不会被回收,
因为单例对象持有它的引用;
*/
private TestManager(Context context) {
this.context =
context;
}public static TestManager getInstance(Context context) {
if (instance =
=
null) {
instance =
new TestManager(context);
}
return instance;
}
}
test/src/main/com/qproject/test/leakcanary/LeakCanaryActivity.java
public class LeakCanaryActivity extends AppCompatActivity {@
Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leakcanary);
//获取单例对象,
退出Activity即可模拟出内存泄露
TestManager testManager =
TestManager.getInstance(this);
}
}
2.检测消息通知
运行App到LeakCanaryActivit页面并退出, 在检测到内存泄露的时候, 会发送消息到系统通知栏;
文章图片
文章图片
3.查看检测详情
点击通知消息, 打开名为DisplayLeakActivity的页面, 并显示泄漏的跟踪信息;
文章图片
4.查看LogCat日志
除了以上泄漏信息的显示, Logcat上面也会有对应的日志输出;
//内存泄露对象com.qproject.test.leakcanary.LeakCanaryActivity
12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * com.qproject.test.leakcanary.LeakCanaryActivity has leaked:
//static com.qproject.test.TestManager.instance的com.qproject.test.TestManager.context引用了回收的内存对象
12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * GC ROOT static com.qproject.test.TestManager.instance
12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * references com.qproject.test.TestManager.context
//内存泄露对象大小,
Reference Key,
Device和Android Version等信息
12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * leaks com.qproject.test.leakcanary.LeakCanaryActivity instance
12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * Retaining: 46 KB.
12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * Reference Key: 3d74d294-70dc-4447-a9a2-64e656ea86b8
12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * Device: Genymotion Android PREVIEW - Google Nexus 5X - 7.0.0 - API 24 - 1080x1920 vbox86p
12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * Android Version: 7.0 API: 24 LeakCanary: 1.5 00f37f5
12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * Durations: watch=
5038ms, gc=
137ms, heap dump=
2390ms, analysis=
27325ms
//内存泄露对象详细信息
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: * Details:
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: * Class com.qproject.test.TestManager
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |static instance =
com.qproject.test.TestManager@
316184816 (0x12d898f0)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |static $classOverhead =
byte[308]@
316175745 (0x12d87581)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: * Instance of com.qproject.test.TestManager
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |static instance =
com.qproject.test.TestManager@
316184816 (0x12d898f0)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |static $classOverhead =
byte[308]@
316175745 (0x12d87581)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |context =
com.qproject.test.leakcanary.LeakCanaryActivity@
315059712 (0x12c76e00)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |shadow$_klass_ =
com.qproject.test.TestManager
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |shadow$_monitor_ =
0
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: * Instance of com.qproject.test.leakcanary.LeakCanaryActivity
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |static $classOverhead =
byte[2228]@
316203009 (0x12d8e001)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mDelegate =
android.support.v7.app.AppCompatDelegateImplN@
315842128 (0x12d35e50)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mEatKeyUpEvent =
false
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mResources =
null
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mThemeId =
2131230884
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mCreated =
true
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mFragments =
android.support.v4.app.FragmentController@
316183584 (0x12d89420)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mHandler =
android.support.v4.app.FragmentActivity$1@
316163360 (0x12d84520)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mNextCandidateRequestIndex =
0
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mOptionsMenuInvalidated =
false
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mPendingFragmentActivityResults =
android.support.v4.util.SparseArrayCompat@
316172368 (0x12d86850)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mReallyStopped =
true
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mRequestedPermissionsFromFragment =
false
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mResumed =
false
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mRetaining =
false
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mStopped =
true
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mStartedActivityFromFragment =
false
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mStartedIntentSenderFromFragment =
false
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mExtraDataMap =
android.support.v4.util.SimpleArrayMap@
316171864 (0x12d86658)
12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |mActionBar =
null
12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: |mActionModeTypeStarting =
0
12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: |mActivityInfo =
android.content.pm.ActivityInfo@
315841984 (0x12d35dc0)
12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: |mActivityTransitionState =
android.app.ActivityTransitionState@
316207336 (0x12d8f0e8)
12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: |mApplication =
com.qproject.main.QAplication@
314916416 (0x12c53e40)
12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: |mCalled =
true
12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: |mChangeCanvasToTranslucent =
false
12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: |mChangingConfigurations =
false
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |mComponent =
android.content.ComponentName@
315998320 (0x12d5c070)
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |mConfigChangeFlags =
0
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |mCurrentConfig =
android.content.res.Configuration@
316178888 (0x12d881c8)
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |mDecor =
null
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |mDefaultKeyMode =
0
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |mDefaultKeySsb =
null
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |mDestroyed =
true
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |mDoReportFullyDrawn =
false
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |mEatKeyUpEvent =
false
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |mEmbeddedID =
null
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |mEnableDefaultActionBarUp =
false
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |mEnterTransitionListener =
android.app.SharedElementCallback$1@
1887062680 (0x707a4a98)
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |mExitTransitionListener =
android.app.SharedElementCallback$1@
1887062680 (0x707a4a98)
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |mFinished =
true
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |mFragments =
android.app.FragmentController@
316183536 (0x12d893f0)
12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |mHandler =
android.os.Handler@
316163296 (0x12d844e0)
12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: |mHasCurrentPermissionsRequest =
false
12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: |mIdent =
20356640
12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: |mInstanceTracker =
android.os.StrictMode$InstanceTracker@
316183552 (0x12d89400)
12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: |mInstrumentation =
android.app.Instrumentation@
314950632 (0x12c5c3e8)
12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: |mIntent =
android.content.Intent@
316215416 (0x12d91078)
12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: |mLastNonConfigurationInstances =
null
12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: |mMainThread =
android.app.ActivityThread@
314966272 (0x12c60100)
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mManagedCursors =
java.util.ArrayList@
316171816 (0x12d86628)
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mManagedDialogs =
null
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mMenuInflater =
null
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mParent =
null
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mReferrer =
java.lang.String@
316215864 (0x12d91238)
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mResultCode =
0
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mResultData =
null
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mResumed =
false
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mSearchEvent =
null
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mSearchManager =
null
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mStartedActivity =
false
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mStopped =
true
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mTaskDescription =
android.app.ActivityManager$TaskDescription@
316163328 (0x12d84500)
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mTemporaryPause =
false
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mTitle =
java.lang.String@
315129824 (0x12c87fe0)
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mTitleColor =
0
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mTitleReady =
true
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mToken =
android.os.BinderProxy@
315867328 (0x12d3c0c0)
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mTranslucentCallback =
null
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mUiThread =
java.lang.Thread@
1959751680 (0x74cf7000)
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mVisibleBehind =
false
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mVisibleFromClient =
true
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mVisibleFromServer =
true
12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |mVoiceInteractor =
null
12-25 07:50:51.716 4941-5795/com.qproject.main D/LeakCanary: |mWindow =
com.android.internal.policy.PhoneWindow@
315116864 (0x12c84d40)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |mWindowAdded =
true
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |mWindowManager =
android.view.WindowManagerImpl@
316172152 (0x12d86778)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |mInflater =
com.android.internal.policy.PhoneLayoutInflater@
316010352 (0x12d5ef70)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |mOverrideConfiguration =
null
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |mResources =
android.content.res.Resources@
316235992 (0x12d960d8)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |mTheme =
android.content.res.Resources$Theme@
316183728 (0x12d894b0)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |mThemeResource =
2131230884
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |mBase =
android.app.ContextImpl@
316155392 (0x12d82600)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |shadow$_klass_ =
com.qproject.test.leakcanary.LeakCanaryActivity
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |shadow$_monitor_ =
1316364430
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: * Excluded Refs:
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Field: android.view.Choreographer$FrameDisplayEventReceiver.mMessageQueue (always)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Thread:FinalizerWatchdogDaemon (always)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Thread:main (always)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Thread:LeakCanary-Heap-Dump (always)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Class:java.lang.ref.WeakReference (always)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Class:java.lang.ref.SoftReference (always)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Class:java.lang.ref.PhantomReference (always)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Class:java.lang.ref.Finalizer (always)
12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Class:java.lang.ref.FinalizerReference (always)
5.获取dump日志文件
如果觉得跟踪信息不足以定位时, DisplayLeakActivity还为开发者默认保存了最近7个dump文件到APP的目录中, 可以使用MAT等工具对dump文件进行进一步的分析;
vbox86p:/data/data/com.qproject.main/files/leakcanary # ls
2016-12-25_07-50-51_718.hprof 2016-12-25_07-50-51_718.hprof.result
D:\\>
adb pull ./data/data/com.qproject.main/files/leakcanary/2016-12-25_07-50-51_ 718.hprof
[100%] ./data/data/com.qproject.main/f...akcanary/2016-12-25_07-50-51_718.hprof
Android Studio-> View-> Tool Windows-> Captures, 打开Captures窗口, 将pull获取的hprof文件剪切到Capture中文件的目录下, 双击打开即可;
文章图片
具体的分析过程, 这里就不重点讲述, 大家去查询MAT相关的资料;
五、检测其他对象
如果想监听其他的对象( 例如Fragment ) , 可以通过RefWatcher的实例来实现;
文章图片
文章图片
六、LeakCanary原理
1.RefWatcher.watch()函数会为被监控的对象创建一个KeyedWeakReference弱引用对象, 是WeakReference对的子类, 增加了键值对信息, 后面会根据指定的键key找到弱引用对象;
2.在后台线程AndroidWatchExecutor中, 检查KeyedWeakReference弱引用是否已经被清楚。如果还存在, 则触发一次垃圾回收之后。垃圾回收之后, 如果弱引用对象依然存在, 说明发生了内存泄露;
新技术, 新未来! 欢迎大家关注 “1024工场”微信服务号 , 时刻关注我们的最新的技术讯息! ( 甭客气! 尽情的扫描或者长按! )
推荐阅读
- Android学习笔记(十四) Handler理论补充
- Android应用性能优化系列视图篇——隐藏在资源图片中的内存杀手
- 深入解析Android中Handler消息机制
- Android应用程序如何使用Internet资源()
- Android应用程序的结构和解析
- Android源代码下载 “Gerrit下载源代码”
- Android开发(《Gradle Recipes for Android》阅读笔记(翻译)3.3——整合resource文件)
- Android中的WeakReference 弱引用
- Android全平台书籍