内存泄露
内存泄露是Android开发过程中最常见的问题之一。Java是垃圾回收语言之一,使用者无须手动释放内存,交由垃圾回收器自动管理即可。但是如果使用不当,很容易造成该释放掉的内存未能及时释放,从而越积越多,最终导致内存耗尽,系统抛出OOM错误。 内存泄露的原因简单来说就是存放在堆中的对象仍存在强引用,GC无法在内存中回收这个对象。形象来说就是生命周期长的对象持有生命周期短的对象的引用。 Android中常见的场景是Activity回收。因为activity是有生命周期的,当其关闭后需要交由GC回收。但是如果此时仍有指向Activity的强引用存在,那么GC是不会回收掉这个Activity的,从而导致该Activity一直占用着系统内存。
Static静态变量
静态变量的生存周期是存在于整个App的生命周期中,所以如果有静态变量持有Activity的强引用,那么这个Activity是回收不了的。
package com.brett.aptproject;
import android.content.Context;
public class Singleton {
private static Singleton sSingleton = null;
private Context mContext;
public Singleton(Context mContext){
this.mContext = mContext;
}
public static Singleton getInstance(Context context){
if(null== sSingleton){
sSingleton = new Singleton(context);
}
return sSingleton;
}
}
如果Singleton传入的Context是Activity,那么就会导致内存泄露。因为sSingleton是一个静态变量,持有Singleton实例对象的引用,而mContext是Singleton的成员变量,在Singleton实例化的时候指向了传入的Activity。由于Singleton的实例对象被sSingleton静态变量强引用,所以会在内存中一直存在,因此其成员变量mContext同样不会被回收;而mContext又对Activity有强引用,导致Activity不能被回收,这样就发生了内存泄露。 接下来我们就来看下如何解决内存泄漏的问题。 内存泄漏的原因是生命周期长的对象持有生命周期短的对象的引用,那么我们可以考虑将两个对象的生命周期设置成一样长,例如传入getApplicationContext(),它的生命周期就是App的生命周期,跟静态变量一样,所以就不会造成内存泄漏。 关于内存泄漏的检测,还可以通过Profile来发现泄漏问题。 例如,我们创建一个泄漏的Activity:
package com.brett.aptproject.activity;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.brett.aptproject.R;
import com.brett.aptproject.Singleton;
@Route(path = "/activity/ProfileActivity",group = "app")
public class ProfileActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_profile);
Singleton.getInstance(this);
}
}
然后在MainActivity中打开ProfileActivity,在关闭ProfileActivity,接下来单击 Force garbage collection按钮,强制垃圾回收,理论上ProfileActivity应该被回收,但是从profile文件中可以看到还有ProfileActivity的引用。
InnerClass内部类
非静态内部类,包括匿名内部类、成员内部类、局部内部类,都会导致内存泄漏。因为内部类会默认持有外部类的引用,不然内部类怎么能够直接访问到外部类的数据呢? 内部类默认持有外部类的引用,意思是创建内部类对象的时候,必须传入外部类对象,作为内部类中outerClass成员变量的引用。
public class InnerClass{
private OuterClass outerClass;
public InnerClass(OuterClass outerClass){
this.outerClass = outerClass;
}
}
那这样问题就来了,如果外部类是一个Activity,当这个Activity需要被回收的时候,此时内部类仍然还在工作,仍然持有Activity的引用,那么这个Activity就不能被及时回收,从而导致内存泄漏。 以匿名内部类为例,匿名内部类没有名字,所以每次只能使用一次,而且匿名内部类必须实现一个接口或者继承父类。
1.new一个抽象类
abstract class Car{
public abstract void move();
}
public class Demo{
public static void main(String[] args) {
Car c = new Car() {
@Override
public void move() {
System.out.println("I moved");
}
};
c.move();
}
}
2.new一个接口
interface Car{
void move();
}
public class Demo{
public static void main(String[] args) {
Car c = new Car() {
@Override
public void move() {
System.out.println("I moved");
}
};
c.move();
}
}
这里并不是实例化一个抽象类或接口,而是实现了一个匿名类。 另外,常见的Handler也容易造成内存泄漏:
package com.brett.aptproject.activity;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.brett.aptproject.R;
import com.brett.aptproject.Singleton;
@Route(path = "/activity/ProfileActivity",group = "app")
public class ProfileActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_profile);
mHandler.sendEmptyMessageDelayed(1,1*60*1000);
}
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
}
可以看到,mHandler作为成员变量指向匿名内部类Handler,Handler默认持有对Activity的引用,如果在1分钟内Activity有退出,那么由于匿名内部类Handler对外部Activity有强引用,所以外部Activity并不能被回收。 因此我们可以采用静态内部类的方法来实现,考虑到Handler一般是用来更新UI的,所以肯定会对UI进行操作,如果我们采用了静态非匿名内部类的方法,那么Handler就不能直接操作外部的Activity,因此需要在初始化Handler的时候将外部Activity传入,但是这样以来又造成了Handler对Activity持有强引用的问题。因此这种情况下,可以考虑采用弱引用的方式,在系统进行垃圾回收的时候,自动解除引用关系,从而能够顺利回收掉对象。
private static class MyHandler extends Handler{
WeakReference<Activity>weakReference;
public MyHandler(Activity activity){
weakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(weakReference.get()!=null){
}
}
}
另外,在onDestory的时候还需要进行remove操作
@Override
protected void onDestroy() {
super.onDestroy();
if(mHandler!=null){
mHandler.removeCallbacksAndMessages(null);
mHandler = null;
}
}
内存泄漏也有工具可以检测。LeakCanary是Square公司基于MAT开源的一个工具,专门用来检测App的内存泄漏问题。具体用法这里就不涉及了。
编译速度
配置文件优化
在gradle.properties文件中进行参数配置,具体如下:
#jvm运行时允许分配最大堆内存空间
org.gradle.jvmargs=-Xmx4608m
#开启并行编译
org.gradle.parallel=true
#开启守护进程 通过开启守护进程,下一次构建的时候,将会连接这个守护进程进行构建,而不是重新fork一个gradle构建进程
org.gradle.daemon=true
#启用新的孵化模式
org.gradle.configureondemand=true
Gradle脚本优化
直接在命令行窗口输入:.\gradlew build -profile,完成后可以看到一份html文件,里面可以查看到gradle构建的各个task运行的时间。通过查看生成的html可以屏蔽掉一些task。
allprojects {
gradle.taskGraph.whenReady {
tasks.each { task ->
if (task.name.contains("lint")) {
task.enabled = false
}
}
}
}
|