安卓工程师面试准备

来源:互联网 发布:淘宝联盟历史版本5.0 编辑:程序博客网 时间:2024/04/29 05:45

android GC内存泄露问题

1. android内存泄露概念

     不少人认为JAVA程序,因为有垃圾回收机制,应该没有内存泄露。其实如果我们一个程序中,已经不再使用某个对象,但是因为仍然有引用指向它,垃圾回收器就无法回收它,当然该对象占用的内存就无法被使用,这就造成了内存泄露。如果我们的java运行很久,而这种内存泄露不断的发生,最后就没内存可用了。当然java的,内存泄漏和C/C++是不一样的。如果java程序完全结束后,它所有的对象就都不可达了,系统就可以对他们进行垃圾回收,它的内存泄露仅仅限于它本身,而不会影响整个系统的。C/C++的内存泄露就比较糟糕了,它的内存泄露是系统级,即使该C/C++程序退出,它的泄露的内存也无法被系统回收,永远不可用了,除非重启机器。

Android的一个应用程序的内存泄露对别的应用程序影响不大。为了能够使得Android应用程序安全且快速的运行,Android的每个应用程序都会使用一个专有的Dalvik虚拟机实例来运行,也就是说每个应用程序都是在属于自己的进程中运行的。Android为不同类型的进程分配了不同的内存使用上限,如果程序在运行过程中出现了内存泄漏的而造成应用进程使用的内存超过了这个上限,则会被系统视为内存泄漏,从而被kill掉,这使得仅仅自己的进程被kill掉,而不会影响其他进程(如果是system_process等系统进程出问题的话,则会引起系统重启)。

  总的来说,内存管理中的内存泄漏产生的主要原因:保留下来却永远不再使用的对象引用。

2.引起内存泄露的情况

1)资源对象没关闭造成的内存泄露

  资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。因为有些资源性对象,比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。

程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险。

 

2)一些不良代码成内存压力

  有些代码并不造成内存泄露,但是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存,对内存的回收和分配造成很大影响的,容易迫使虚拟机不得不给该应用进程分配更多的内存,造成不必要的内存开支。

  a.Bitmap没调用recycle()

  Bitmap对象在不使用时,我们应该先调用recycle()释放内存,然后才它设置为null。

虽然recycle()从源码上看,调用它应该能立即释放Bitmap的主要内存,但是测试结果显示它并没能立即释放内存。但是我它应该还是能大大的加速Bitmap的主要内存的释放。

  b.构造Adapter时,没有使用缓存的convertView

  以构造ListView的BaseAdapter为例,在BaseAdapter中提共了方法:

public View getView(int position, View convertView, ViewGroup parent)

  来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。

  由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费时间,也造成内存垃圾,给垃圾回收增加压力,如果垃圾回收来不及的话,虚拟机将不得不给该应用进程分配更多的内存,造成不必要的内存开支。

layout.xml

复制代码
 <?xml version="1.0" encoding="utf-8" ?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="60dip">  <ImageView android:id="@+id/icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:paddingLeft="9px" />   <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/icon" android:textAppearance="?android:attr/textAppearanceMedium" android:paddingLeft="9px" />   </RelativeLayout>
复制代码

能引起内存泄露的代码: BadAdapter.java

复制代码
public class BadAdapter extends BaseAdapter {
    ......    @Override
    public View getView(int position, View convertView, ViewGroup parent) {        Log.d("MyAdapter", "Position:" + position + "---"                + String.valueOf(System.currentTimeMillis()));        final LayoutInflater inflater = (LayoutInflater) mContext                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);        View v = inflater.inflate(R.layout.list_item_icon_text, null);        ((ImageView) v.findViewById(R.id.icon)).setImageResource(R.drawable.icon);        ((TextView) v.findViewById(R.id.text)).setText(mData[position]);        return v;    }}
复制代码

修正优化示例代码示例代码:GoodAdapter.java

复制代码
public class GoodAdapter extends BaseAdapter {    ......    @Override    public View getView(int position, View convertView, ViewGroup parent) {        Log.d("MyAdapter", "Position:" + position + "---"                + String.valueOf(System.currentTimeMillis()));        ViewHolder holder;        if (convertView == null) {            final LayoutInflater inflater = (LayoutInflater) mContext                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);            convertView = inflater.inflate(R.layout.list_item_icon_text, null);            holder = new ViewHolder();            holder.icon = (ImageView) convertView.findViewById(R.id.icon);            holder.text = (TextView) convertView.findViewById(R.id.text);            convertView.setTag(holder);        } else {            holder = (ViewHolder) convertView.getTag();        }        holder.icon.setImageResource(R.drawable.icon);        holder.text.setText(mData[position]);        return convertView;    }    static class ViewHolder {        ImageView icon;        TextView text;    }}
复制代码

MainActivity.java

复制代码
public class MainActivity extends ListActivity {    private BadAdapter/GoodAdapter mAdapter;    private String[] mArrData;    /** Called when the activity is first created. */    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);        mArrData = new String[1000];        for (int i = 0; i < 1000; i++) {            mArrData[i] = "Google IO Adapter";        }        mAdapter = new BadAdapter/GoodAdapter(this, mArrData);        setListAdapter(mAdapter);    }}
复制代码

3)ThreadLocal使用不当

  如果我们粗暴的把ThreadLocal设置null,而不调用remove()方法或set(null),那么就可能造成ThreadLocal绑定的对象长期也能被回收,因而产出内存泄露。

Activity生命周期

在系统中的Activity被一个Activity栈所管理。当一个新的Activity启动时,将被放置到栈顶,成为运行中的Activity,前一个Activity保留在栈中,不再放到前台,直到新的Activity退出为止。

Activity有四种本质区别的状态:

  1. 在屏幕的前台(Activity栈顶),叫做活动状态或者运行状态(active or running)
  2. 如果一个Activity失去焦点,但是依然可见(一个新的非全屏的Activity 或者一个透明的Activity 被放置在栈顶),叫做暂停状态(Paused)。一个暂停状态的Activity依然保持活力(保持所有的状态,成员信息,和窗口管理器保持连接),但是在系统内存极端低下的时候将被杀掉。
  3. 如果一个Activity被另外的Activity完全覆盖掉,叫做停止状态(Stopped)。它依然保持所有状态和成员信息,但是它不再可见,所以它的窗口被隐藏,当系统内存需要被用在其他地方的时候,Stopped的Activity将被杀掉。
  4. 如果一个Activity是Paused或者Stopped状态,系统可以将该Activity从内存中删除,Android系统采用两种方式进行删除,要么要求该Activity结束,要么直接杀掉它的进程。当该Activity再次显示给用户时,它必须重新开始和重置前面的状态。

 

下面的图显示了Activity的重要状态转换,矩形框表明Activity在状态转换之间的回调接口,开发人员可以重载实现以便执行相关代码,带有颜色的椭圆形表明Activity所处的状态。

在上图中,Activity有三个关键的循环: 

  1. 整个的生命周期,从onCreate(Bundle)开始到onDestroy()结束。Activity在onCreate()设置所有的“全局”状态,在onDestory()释放所有的资源。例如:某个Activity有一个在后台运行的线程,用于从网络下载数据,则该Activity可以在onCreate()中创建线程,在onDestory()中停止线程。
  2. 可见的生命周期,从onStart()开始到onStop()结束。在这段时间,可以看到Activity在屏幕上,尽管有可能不在前台,不能和用户交互。在这两个接口之间,需要保持显示给用户的UI数据和资源等,例如:可以在onStart中注册一个IntentReceiver来监听数据变化导致UI的变动,当不再需要显示时候,可以在onStop()中注销它。onStart(),onStop()都可以被多次调用,因为Activity随时可以在可见和隐藏之间转换。
  3. 前台的生命周期,从onResume()开始到onPause()结束。在这段时间里,该Activity处于所有 Activity的最前面,和用户进行交互。Activity可以经常性地在resumed和paused状态之间切换,例如:当设备准备休眠时,当一个 Activity处理结果被分发时,当一个新的Intent被分发时。所以在这些接口方法中的代码应该属于非常轻量级的。

 

Activity的整个生命周期都定义在下面的接口方法中,所有方法都可以被重载。所有的Activity都需要实现 onCreate(Bundle)去初始化设置,大部分Activity需要实现onPause()去提交更改过的数据,当前大部分的Activity也需要实现onFreeze()接口,以便恢复在onCreate(Bundle)里面设置的状态。


横竖屏切换的生命周期

 总结:

1、不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次

2、设置Activity的android:configChanges="orientation"时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次

3、设置Activity的android:configChanges="orientation|keyboardHidden"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法

 


总结一下整个Activity的生命周期

补充一点,当前Activity产生事件弹出Toast和AlertDialog的时候Activity的生命周期不会有改变

Activity运行时按下HOME键(跟被完全覆盖是一样的):onSaveInstanceState --> onPause --> onStop       onRestart -->onStart--->onResume

Activity未被完全覆盖只是失去焦点:onPause--->onResume

nCreate: 


在这里创建界面,做一些数据的初始化工作 


  onStart: 


到这一步变成用户可见不可交互的 


  onResume: 


变成和用户可交互的,(在 activity 栈系统通过栈的方式管理这些个Activity 
的最上面,运行完弹出栈,则回到上一个Activity) 


  onPause: 

到 这一步是可见但不可交互的,系统会停止动画等消耗CPU 的事情从上文的描 
述已经知道,应该在这里保存你的一些数据,因为这个时候你的程序的优先级降 
低,有可能被系统收回。在这里保存的数据,应该在 onResume里读出来,注意: 
这个方法里做的事情时间要短,因为下一个activity 不会等到这个方法完成才 
启动 


  onstop: 


变得不可见,被下一个activity覆盖了 


  onDestroy: 这是activity被干掉前最后一个被调用方法了,可能是外面类调 
用 finish方法或者是系统为了节省空间将它暂时性的干掉,可以用 
isFinishing()来判断它,如果你有一个Progress Dialog 在线程中转动,请在 
onDestroy里把他cancel掉,不然等线程结束的时候,调用Dialog 的cancel 
方法会抛异常的。 

onPause,onstop, onDestroy,三种状态下 activity 都有可能被系统干掉为 
了保证程序的正确性,你要在onPause()里写上持久层操作的代码,将用户编辑 
的内容都保存到存储介质上(一般 都是数据库)。实际工作中因为生命周期的 
变化而带来的问题也很多,比如你的应用程序起了新的线程在跑,这时候中断了, 
你还要去维护那个线程,是暂停还是杀 掉还是数据回滚,是吧?因为Activity 
可能被杀掉,所以线程中使用的变量和一些界面元素就千万要注意了,一般我都 
是采用Android 的消息机制 [Handler,Message]来处理多线程和界面交互的问 
题。这个我后面会讲一些,最近因为这些东西头已经很大了,等我理清思绪再跟 
大家分享。 

进程间通信:

由于android系统中应用程序之间不能共享内存。因此,在不同应用程序之间交互数据(跨进程通讯)就稍微麻烦一些。在android SDK中提供了4种用于跨进程通讯的方式。这4种方式正好对应于android系统中4种应用程序组件:Activity、Content Provider、Broadcast和Service。其中Activity可以跨进程调用其他应用程序的Activity;Content Provider可以跨进程访问其他应用程序中的数据(以Cursor对象形式返回),当然,也可以对其他应用程序的数据进行增、删、改操 作;Broadcast可以向android系统中所有应用程序发送广播,而需要跨进程通讯的应用程序可以监听这些广播;Service和Content Provider类似,也可以访问其他应用程序中的数据,但不同的是,Content Provider返回的是Cursor对象,而Service返回的是Java对象,这种可以跨进程通讯的服务叫AIDL服务。

安卓finish()、invalidate、dismiss

当你在程序中调用 Activity.finish()方法时,结果和用户按下 BACK 键一样:他告诉 Activity Manager该Activity实例可以被“回收”。随后 Activity Manager 激活处于栈第二层的 Activity 并重 新入栈,把原 Activity 压入到栈的第二层,从 Running 状态转到 Paused 状态。 
Android中实现view的更新有两组方法,一组是invalidate,另一组是postInvalidate,其中前者是在UI线程自身中使用,而后者在非UI线程中使用。 
dismiss关闭对话框
如果后台的Activity由于某原因被系统回收了,如何在被系统回收之前保存当前状态
 
  当一个Activity被pause或者stop的时候,这个Activity的对象实际上还是保存在内存中,因此这个Activity中的信息(成员和状态信息)还可以重新获取到. 
    如果系统为了整理内存而销毁了整合各Activity对象时,系统没法简单的原封不动地恢复先前的Activity对象及其状态信息. 
    从android手册上来看,Activity中提供了一个方法:onSavedInstanceState(Bundle obj).当系统销毁一个Activity时,会将Activity的状态信息已键值对形式存放在bundle对象中. 
    第一次启动Activity时,这个bundle对象是空的,null.如果Activity被系统销毁了,然后用户要回退回去看的话,系统会调用这个 Activity的onCreate方法,并把bundle对象传递过去. 
    这个函数有默认的行为,因此就算你不覆盖它,它在Activity中也有实现. 
    这回我总算明白了为什么onCreate方法的定义是"protected void onCreate (Bundle savedInstanceState)"这个样子的了. 
    另外,刚才查看了一下Activity的源码,发现Activity还有个onRestoreInstanceState(Bundle outState)方法.这个方法的描述中也写到在Activity回复先前保存的状态时会被调用.
 
 
 
当你的程序中某一个Activity A在运行时,主动或被动地运行另一个新的Activity B,这个时候A会执行onSaveInstanceState()。B完成以后又会来找A,这个时候就有两种情况:一是A被回收,二是A没有被回收,被回收的A就要重新调用onCreate()方法,不同于直接启动的是这回onCreate()里是带上了参数savedInstanceState;而没被收回的就直接执行onResume(),跳过onCreate()了。 

 调用与被调用:我们的通信使者 Intent 


要 说 Intent 了,Intent就是这个这个意图,应用程序间 Intent进行交流,打 
个电话啦,来个电话啦都会发 Intent, 这个是Android 架构的松耦合的精髓部 
分,大大提高了组件的复用性,比如你要在你的应用程序中点击按钮,给某人打 
电话,很简单啊,看下代码先: 


Java代码 : 


Intent intent = new Intent(); 


intent.setAction(Intent.ACTION_CALL); 


intent.setData(Uri.parse("tel:"+ number)); 


startActivity(intent);      复制代码 


扔出这样一个意图,系统看到了你的意图就唤醒了电话拨号程序,打出来电话。 
什么读联系人,发短信啊,邮件啊,统统只需要扔出 intent就好了,这个部分 
设计地确实很好啊。 


那 Intent通过什么来告诉系统需要谁来接受他呢? 


  通常使用 Intent有两种方法,第一种是直接说明需要哪一个类来接收代码如 
下: 


Java代码 


  Intent intent = new Intent(this,MyActivity.class); 


intent.getExtras().putString("id","1"); 


startActivity(intent); 


Intent intent = new 
Intent(this,MyActivity.class);intent.getExtras().putString("id","1"); 
tartActivity(intent);复制代码 
第一种方式很明显,直接指定了MyActivity 为接受者,并且传了一些数据给 
MyActivity,在MyActivity 里可以用getIntent()来的到这个 intent和数据。 


第二种就需要先看一下AndroidMenifest 中的intentfilter 的配置了 


    Xml代码 


  <intent-filter> 


<action android:name="android.intent.action.VIEW" /> 


<action android:value="android.intent.action.EDIT" /> 


<action android:value="android.intent.action.PICK" /> 


<category android:name="android.intent.category.DEFAULT"/> 


<data android:mimeType="vnd.android.cursor.dir/vnd.google.note" /> 


</intent-filter> 


<intent-filter> 


<action android:name="android.intent.action.VIEW"/> 


<action android:value="android.intent.action.EDIT" /> 


<action android:value="android.intent.action.PICK" /> 


<category android:name="android.intent.category.DEFAULT" /> 


<data android:mimeType="vnd.android.cursor.dir/vnd.google.note" /> 
<data android:mimeType="vnd.android.cursor.dir/vnd.google.note" /> 


</intent-filter> 


这里面配置用到了action, data, category 这些东西,那么聪明的你一定想到 
intent里也会有这些东西,然后一匹配不就找到接收者了吗? 


  action其实就是一个意图的字符串名称。 


  上 面这段 intent-filter 的配置文件说明了这个Activity 可以接受不同的 
Action,当然相应的程序逻辑也不一样咯,提一下那个 mimeType,他是在 


----------------------- Page 10-----------------------


ContentProvider 里定义的,你要是自己实现一个ContentProvider 就知道了, 
必须指定 mimeType 才能让数据被别人使用。 


不知道原理说明白没,总结一句,就是你调用别的界面不是直接new那个界面, 
而是通过扔出一个 intent,让系统帮你去调用那个界面,这样就多么松藕合啊, 
而且符合了生命周期被系统管理的原则。 


  想知道category都有啥,Android 为你预先定制好的action 都有啥等等,请 
亲自访问官方链接 Intent 
 ps:想知道怎么调用系统应用程序的同学,可以仔细看一下你的 logcat,每次 
运行一个程序的时候是不是有一些信息比如: 


Starting activity: Intent 
{action=android.intent.action.MAINcategories={android.intent.category 
.LAUNCHER}flags=0x10200000comp={com.android.camera/com.android.camera 
.GalleryPicker} } 


  再对照一下 Intent 的一些set方法,就知道怎么调用咯,希望你喜欢:) 
0 1
原创粉丝点击