在开发中,必然会new对象,一般为了方便,很可能我们在每次要用的时候,直接就new对象拿来用,这样既方便又省事。同时,又由于内存垃圾回收器的机制,一般情况下可以让我们不必担心new对象会产生内存溢出的问题(相对于C语言每次要考虑用完后释放,省去了很多麻烦)。但是,对于需要频繁执行的代码块,不必要的执行就会浪费很多性能,对于移动端的开发,这一点还是很值得我们去优化的!下面,我举几个开发中常见例子,进行说明。
*****个人经验狭隘,难免有所疏漏,若有问题,恳请斧正!*****
1.提升至成员变量,在其他位置初始化
我们时常需要去画一些自定义控件,例如用MyView继承View,然后onMeasure、onLayout、onDraw完成一系列绘制。在使用Paint的时候,我们很可能直接在onDraw中new Paint(),如下:
@Override protected void onDraw(Canvas canvas) { Paint paint = new Paint(); }
由于onDraw在绘图时会被频繁调用,这样你每次都会new Paint(),浪费性能,同时屏幕UI界面可能会不是很顺滑(由于垃圾回收器机制的影响)。
针对于这种需要重复使用的对象,我们应该提高其复用性,而不是每次都new。所以,此例中你可以将Paint提升为成员变量,先在构造函数中初始化,如下:
private Paint mPaint; public MyView(Context context) { super(context); mPaint = new Paint(); }
onLayout也是同理。同时,你还需要注意开发中的其他需要多次执行的代码块,“是不是有必要每次都new对象呢?“这块代码是否可以复用呢?”等等。
2.维护一个集合,集合中有就直接拿,没有才new
开发中有很多资源都会被频繁重复使用(例如同一张处理后的图片),我们每次都重新去处理再设置,是相当不合理的。既然我们之前已经处理过了,那么我们就应该拿一个集合(例如HashMap)将他们维护起来,需要的时候直接从集合中取出就可以了,而不是每次都去重新处理、重新new对象。
现在App开发中最常见的界面就是通过Fragment实现卡片式布局(底部有一排按钮,点击切换不同页面),而Fragment一般都是有好几个的,一个Fragment中有很多代码需要处理,对于Fragment就应该使用集合进行维护,而不是每次都new一个Fragment对象返回。
具体如何实现呢?我举出一个FragmentFactory的例子供大家参考,如下:
public class FragmentFactory {
// private static HashMap<Integer, BaseFragment> mFragmentMap = new HashMap<>();
private static SparseArray<BaseFragment> mFragmentArray = new SparseArray<>();
//数量不大,key为int类型时,效果比HashMap更好
public static BaseFragment createFragment(int pos) {
BaseFragment fragment = mFragmentArray.get(pos);//先从集合中取
if (fragment == null) {//如果集合中没有,才创建
switch (pos) {
case 0: //首页
fragment = new HomeFragment();
break;
case 1: //应用
fragment = new AppFragment();
break;
case 2: //游戏
fragment = new GameFragment();
break;
case 3: //专题
fragment = new SubjectFragment();
break;
case 4: //推荐
fragment = new RecommendFragment();
break;
}
mFragmentArray.put(pos, fragment);//将fragment保存在集合中
}
return fragment;
}
}
完成上述FragmentFactory类后,以后使用Fragment时,你只需要调用FragmentFactory.createFragment(position)即可。 ( 另外推荐大家查询一下SparseArray,SparseArray是android提供的一个工具类,在android.util下。它在key为int类型、低数量的条件下,比HashMap使用起来效果更佳。)
这样,如果集合中存在就取,不存在才会去新建一个,而不是每次都new对象了。另外,像对于图片集合的维护,这种方式也很常用。(图片缓存的维护还涉及到软引用、弱引用,LruCache缓存策略,在后面的内容中会讲到)
3.善用单例模式和全局Application类
开发中,很多时候,有的对象在App全程运行时只需要一个即可,每次使用前就去new一个是相当不合适的。这个时候就可以考虑到单例模式了,如下:
public class Demo { //单例——懒汉模式 private static Demo mInstance; private Demo() {} public static Demo getInstance() { if (mInstance == null) { synchronized (Demo.class) { if (mInstance == null) { mInstance = new Demo(); } } } return mInstance; } }
单例模式在实际开发中有很多应用,例如HttpManager、ThreadManager、DownloadManager等等。同时你还可以使用全局Application类,它是伴随着App启动到结束的一个存在。
在Application类,初始化一些恒定的对象和设置,是很不错的方案,例如常见的第三方框架OkHttp的OkHttpClient。OkHttpClient是一个重量级的对象,可以实现很大的并发量,因此我们无需为每次Http请求都去new一个OkHttpClient,而只需要在Application类中申明一个即可。实现方式如下:
public class MyApplication extends Application { private static OkHttpClient mHttpClient; @Override public void onCreate() { super.onCreate(); mHttpClient = new OkHttpClient(); //TODO 下面可以做一些OkHttp的网络设置 } public static OkHttpClient getHttpClient() { return mHttpClient; } }
最后,不要忘记在AndroidManifest.xml中设置一下name=“Application类名路径”,作为Application类的入口。例如:
<application android:name=".MyApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>
当然Application的应用不止这些方面,例如我们还可以做一些初始化设置、获取一个全局context、设置一个handler等等,开发中可能还有很多问题需要我们去思考、去处理,我们应该做到“学于此,而不止于此”。(对于这两个例子,值得注意的是static引用问题,很可能也会导致内存泄露,这个在后续讨论中会详细讲到)
至此,“Android内存溢出与优化(一)——不要随意new对象”结束,虽然只举出了几个例子,但是这些都是开发中常见的情况,实际中还有很多具有相似之处的地方,需要我们举一反三,而不是固定的只能解决这几个情况。