android性能优化学习

时间:2023-01-11 22:39:06

说到性能优化,这么高级的东西,我第一想到的是 内存优化,内存泄漏,布局优化,减少多层嵌套 ,这些基本的东西,毕竟是菜鸟。。。
参考:
http://www.trinea.cn/android/database-performance/
http://www.cnblogs.com/hoolay/p/6248514.html

性能优化大致分类:
性能优化第四篇——移动网络优化
性能优化第三篇——Java(Android)代码优化
性能优化第二篇——布局优化
性能优化第一篇——数据库性能优化
扩展:
性能优化——内存篇
性能优化——JNI篇
性能优化——电量篇

参考:
http://www.cnblogs.com/wangzehuaw/p/5180110.html
避免oom 内存泄漏
集合类泄漏
集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。比如下面的典型例子就是其中一种情况

Vector v = new Vector(10);
for (int i = 1; i < 100; i++) {
Object o = new Object();
v.add(o);
o = null;
}

在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个 Vector 中,如果我们仅仅释放引用本身,那么 Vector 仍然引用该对象,所以这个对象对 GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从 Vector 中删除,最简单的方法就是将 Vector 对象设置为 null。

当然实际上我们在项目中肯定不会写这么 2B 的代码,但稍不注意还是很容易出现这种情况,比如我们都喜欢通过 HashMap 做一些缓存之类的事,这种情况就要多留一些心眼。

单例造成的内存泄漏
由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。比如下面一个典型的例子,

public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}

这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:

1、如果此时传入的是 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题。

2、如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时(destroy了),由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了。
正确的方式应该改为下面这种方式:

public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context.getApplicationContext();// 使用Application 的context
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}

或者这样写,连 Context 都不用传进来了:

在你的 Application 中添加一个静态方法,getContext() 返回 Application 的 context,

...

context = getApplicationContext();

...
/**
* 获取全局的context
* @return 返回全局context对象
*/

public static Context getContext(){
return context;
}

public class AppManager {
private static AppManager instance;
private Context context;
private AppManager() {
this.context = MyApplication.getContext();// 使用Application 的context
}
public static AppManager getInstance() {
if (instance != null) {
instance = new AppManager();
}
return instance;
}
}

总结:生命周期长的引用 持有生命周期短的引用,特别是android中activity的context,当activity销毁,这个引用任然被生命周期长的引用 持有,造成activity无法及时销毁,占用内存。

非静态内部类创建静态实例造成的内存泄漏
有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,可能会出现这种写法:

public class MainActivity extends AppCompatActivity {
private static TestResource mManager = null;//未初始化
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mManager == null){
mManager = new TestResource();//真正初始化,持有MainActivity 引用
}
//...
}
class TestResource {
//...
}
}

这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。

意思只要一个类中的生命周期长的比较厉害,如果持有生命周期短的,短的退出,但是没有用,你不会被回收,因为长的依旧持有你!!!

这里讲到static成员变量和整个应用生命周期一样长的意思,不懂的可以看这篇文档:
http://blog.csdn.net/harryweasley/article/details/49179775

package com.example.teststatic;

import android.app.Activity;
import android.os.Bundle;
import android.text.StaticLayout;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

public class MainActivity extends Activity {
static int i = 100;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toast.makeText(this, "i----" + i, 0).show();

i = 0;
//报错,非静态方法不能创建局部变量
// static int test = 100;

}

}

如果你首次打开应用,i=100,
之后如果你点击返回按钮,退出整个应用,之后再次点击应用,你会发现i=0,发生了什么???我曹????
我不是退出应用了么,为什么i不是100???
如果你打开应用,点击任务键,从最近打开应用列表直接移除,再次打印就没有问题了!!!!
原因:
当窗口消毁时,Activity的静态变量是存在的,因为静态变量是属全局变量、静态变量是整个应用程序的公共变量(即使上面写的是私有),所以Activity销毁了,但是这个应用的进程没有被kill,还在。所以静态变量是不会清除的。但当什么时候才会清除呢,
重写onDestory方法,在onDestory里添加Android.os.Process.killProcess(android.os.Process.myPid());把进程干掉,再次调用就正常了。
上面说过静态变量是整个应用程序的,所以只有当各个应用程序的进程消毁时,静态变量才会毁,所以调用android.os.Process.killProcess(android.os.Process.myPid());就正常了。
正确的做法为:
将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请按照上面推荐的使用Application 的 Context。当然,Application 的 context 不是万能的,所以也不能随便乱用,对于有些地方则必须使用 Activity 的 Context,对于Application,Service,Activity三者的Context的应用场景如下:
android性能优化学习

其中: NO1表示 Application 和 Service 可以启动一个 Activity,不过需要创建一个新的 task 任务队列,对应一个新的task栈。
而对于 Dialog 而言,只有在 Activity 中才能创建,使用activity的context!!!

匿名内部类
android开发经常会继承实现Activity/Fragment/View,此时如果你使用了匿名类,并被异步线程持有了,那要小心了,如果没有任何措施这样一定会导致泄露

public class MainActivity extends Activity {
...
Runnable ref1 = new MyRunable();
Runnable ref2 = new Runnable() {
@Override
public void run() {

}
};
//public static class MyRunnable implements Runnable {
//
// @Override
// public void run() {
// // TODO Auto-generated method stub
//
// }
//
// }

...
}

ref1和ref2的区别是,ref2使用了匿名内部类。我们来看看运行时这两个引用的内存:
android性能优化学习
可以看到,ref1没什么特别的。
但ref2这个匿名类的实现对象里面多了一个引用:
this$0这个引用指向MainActivity.this,
也就是说当前的MainActivity实例会被ref2持有,
如果将这个引用再传入一个异步线程,
此线程和此Acitivity生命周期不一致的时候(activity销毁,线程没有执行结束),就造成了Activity的泄露。

ps:这里扩展一下,如果MyRunable是非静态内部类形式写的,
也会造成和上面一样的问题!!!!
原因就是非静态内部类持有外部类的引用。
http://blog.csdn.net/lsyz0021/article/details/51473819

常见问题和解决思路

Handler 造成的内存泄漏
Handler 的使用造成的内存泄漏问题应该说是最为常见了,很多时候我们为了避免 ANR 而不在主线程进行耗时操作,在处理网络任务或者封装一些请求回调等api都借助Handler来处理,但 Handler 不是万能的,对于 Handler 的使用代码编写一不规范即有可能造成内存泄漏。另外,我们知道 Handler、Message 和 MessageQueue 都是相互关联在一起的,万一 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。
由于 Handler 属于 TLS(Thread Local Storage) 变量, 生命周期和 Activity 是不一致的。因此这种实现方式一般很难保证跟 View 或者 Activity 的生命周期保持一致,故很容易导致无法正确释放。

public class SampleActivity extends Activity {

private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// Post a message and delay its execution for 10 minutes.
mLeakyHandler.postDelayed(new Runnable() {
@Override
public void run() { /* ... */ }
}, 1000 * 60 * 10);

// Go back to the previous Activity.
finish();
}
}

在该 SampleActivity 中声明了一个延迟10分钟执行的消息 Message,mLeakyHandler 将其 push 进了消息队列 MessageQueue 里。当该 Activity 被 finish() 掉时,延迟执行任务的 Message 还会继续存在于主线程中,它持有该 Activity 的 Handler 引用,所以此时 finish() 掉的 Activity 就不会被回收了从而造成内存泄漏(因 Handler 为非静态内部类,它会持有外部类的引用,在这里就是指 SampleActivity)。

handler 没有销毁—handler中有activity的引用—所以activity无法回收

修复方法:在 Activity 中避免使用非静态内部类,比如上面我们将 Handler 声明为静态的,则其存活期跟 Activity 的生命周期就无关了。同时通过弱引用的方式引入 Activity,避免直接将 Activity 作为 context 传进去,见下面代码:

public class SampleActivity extends Activity {

/**
* Instances of static inner classes do not hold an implicit
* reference to their outer class.
*/

private static class MyHandler extends Handler {
private final WeakReference<SampleActivity> mActivity;

public MyHandler(SampleActivity activity) {
mActivity = new WeakReference<SampleActivity>(activity);
}

@Override
public void handleMessage(Message msg) {
SampleActivity activity = mActivity.get();
if (activity != null) {
// ...
}
}
}

private final MyHandler mHandler = new MyHandler(this);

/**
* Instances of anonymous classes do not hold an implicit
* reference to their outer class when they are "static".
*/

private static final Runnable sRunnable = new Runnable() {
@Override
public void run() { /* ... */ }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// Post a message and delay its execution for 10 minutes.
mHandler.postDelayed(sRunnable, 1000 * 60 * 10);

// Go back to the previous Activity.
finish();
}
}

我们写代码注意
1



尽量避免使用 static 成员变量

都知道其生命周期将与整个app进程生命周期一样。

这会导致一系列问题,如果你的app进程
设计上是长驻内存的,那即使app切到后台,
内存管理机制,占内存较大的后台进程将优先回收,
yi'wei如果此app做过进程互保保活,
那会造成app在后台频繁重启。当手机安装了
你参与开发的app以后一夜时间手机被消耗空了
电量、流量,你的app不得不被用户卸载或者静默。
这里修复的方法是:

不要在类初始时初始化静态成员。
可以考虑lazy初始化。
架构设计上要思考是否真的有必要这样做,
尽量避免。如果架构需要这么设计,
那么此对象的生命周期你有责任管理起来。

2



当集合里面的对象属性被修改后,再调用remove()方法时不起作用。
例:
public static void main(String[] args)
{
Set<Person> set = new HashSet<Person>();
Person p1 = new Person("唐僧","pwd1",25);
Person p2 = new Person("孙悟空","pwd2",26);
Person p3 = new Person("猪八戒","pwd3",27);
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:3 个元素!
p3.setAge(2); //修改p3的年龄,
此时p3元素对应的hashcode值发生改变

set.remove(p3); //此时remove不掉,
造成内存泄漏

set.add(p3); //重新添加,居然添加成功
System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:4 个元素!
for (Person person : set)
{
System.out.println(person);
}
}

原因:http://www.cnblogs.com/xietianhang/p/4993094.html
3



资源未关闭造成的内存泄漏

对于使用了BraodcastReceiver,
ContentObserver,File,游标
Cursor,Stream,Bitmap等资源的使用,
应该在Activity销毁时及时关闭或者注销,
否则这些资源将不会被回收,造成内存泄漏。

4



一些不良代码造成的内存压力

有些代码并不造成内存泄露,但是它们,
或是对没使用的内存没进行有效及时的释放,
或是没有有效的利用已有的对象而是频繁的申请新内存。

比如:

Bitmap 没调用 recycle()方法,
对象的内存空间,一部分是 java 的,一
部分 C 的(因为 Bitmap 分配的底层是通过 JNI 调用的 )。
而这个 recyle() 就是针对 C 部分的内存释放。
构造 Adapter 时,没有使用缓存的 convertView ,
每次都在创建新的 converView。这里推荐使用 ViewHolder。

好了,大概就这些,基础吧!!!

2017/6/19
布局优化
(1) 标签
include标签常用于将布局中的公共部分提取出来供其他layout共用,以实现布局模块化,这在布局编写方便提供了大大的便利。

<include>标签最需要的属性是layout属性,指定需要包含的布局文件。可以定义android:id和android:layout_xxx属性来覆盖被引入布局根节点的对应属性值。注意重新定义android:id后,子布局的顶结点id就变化了。

(2) <viewstub>标签
viewstub标签同include标签一样可以用来引入一个外部布局,不同的是,viewstub引入的布局默认不会扩张,即既不会占用显示也不会占用位置,从而在解析layout时节省cpu和内存。
viewstub常用来引入那些默认不会显示,只在特殊情况下显示的布局,如进度布局、网络失败显示的刷新布局、信息出错出现的提示布局等。

通过viewstub的原理我们可以知道将一个view设置为GONE不会被解析,从而提高layout解析速度,而VISIBLE和INVISIBLE这两个可见性属性会被正常解析。

区别在于不显示的时候gone不会解析,节约内存。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >

……
<ViewStub
android:id="@+id/network_error_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/network_error" />

</RelativeLayout>

private View networkErrorView;

private void showNetError() {
// not repeated infalte
if (networkErrorView != null) {
networkErrorView.setVisibility(View.VISIBLE);
return;
}

ViewStub stub = (ViewStub)findViewById(R.id.network_error_layout);
networkErrorView = stub.inflate();
Button networkSetting = (Button)networkErrorView.findViewById(R.id.network_setting);
Button refresh = (Button)findViewById(R.id.network_refresh);
}

private void showNormal() {
if (networkErrorView != null) {
networkErrorView.setVisibility(View.GONE);
}
}


其中:
private View networkErrorView;
ViewStub stub = (ViewStub)findViewById(R.id.network_error_layout);
networkErrorView = stub.inflate();
=====等价于====
private View networkErrorView;
View viewStub = findViewById(R.id.network_error_layout);
viewStub.setVisibility(View.VISIBLE); // ViewStub被展开后的布局所替换
networkErrorView = findViewById(R.id.network_error_layout); // 获取展开后的布局

最终找到networkErrorView 对象
(3) <merge>标签

在使用了include后可能导致布局嵌套过多,多余不必要的layout节点,从而导致解析变慢,不必要的节点和嵌套可通过hierarchy viewer(下面布局调优工具中有具体介绍)或设置->开发者选项->显示布局边界查看。

merge标签可用于两种典型情况:
a. 布局顶结点是FrameLayout且不需要设置background或padding等属性,可以用merge代替,因为Activity内容视图的parent view就是个FrameLayout,所以可以用merge消除只剩一个。
b. 某布局作为子布局被其他布局include时,使用merge当作该布局的顶节点,这样在被引入时顶结点会自动被忽略,而将其子节点全部合并到主布局中。

以(1) <include>标签的示例为例,用hierarchy viewer查看main.xml布局如下图:
android性能优化学习

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >


<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_40"
android:layout_above="@+id/text"/>


<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_40"
android:layout_alignParentBottom="true"
android:text="@string/app_name" />


</RelativeLayout>

include:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >


<ListView
android:id="@+id/simple_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/dp_80" />


<include layout="@layout/foot.xml" />

</RelativeLayout>

可以发现多了一层没必要的RelativeLayout,将foot.xml中RelativeLayout改为merge,如下:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >


<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_40"
android:layout_above="@+id/text"/>


<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_40"
android:layout_alignParentBottom="true"
android:text="@string/app_name" />


</merge>

android性能优化学习

少了一层 RelativeLayout布局,相当于直接写在一个布局里面的。

2、去除不必要的嵌套和View节点
3、减少不必要的infalte
4、其他点
(1) 用SurfaceView或TextureView代替普通View
(2) 使用RenderJavascript
(3) 使用OpenGL绘图
(4) 尽量为所有分辨率创建资源

更多:
http://www.trinea.cn/android/layout-performance/