黑马程序员技术交流社区

标题: 【阳哥面试宝典】Android高级面试核心内容(☆☆☆) [打印本页]

作者: 阳哥忠粉    时间: 2015-8-10 13:50
标题: 【阳哥面试宝典】Android高级面试核心内容(☆☆☆)




阳哥面试宝典总链接:http://bbs.itheima.com/thread-223527-1-1.html

一、Android性能优化

1、如何对Android应用进行性能分析
      
       一款App流畅与否安装在自己的真机里,玩几天就能有个大概的感性认识。不过通过专业的分析工具可以使我们更好的分析我们的应用。
       如果不考虑使用其他第三方性能分析工具的话,我们可以直接使用ddms中的工具,其实ddms工具已经非常的强大了。ddms中有traceview、heap、allocation tracker等工具都可以帮助我们分析应用的方法执行时间效率和内存使用情况。
       traceview工具在前面已经有详细的介绍,因此这里就不再赘述。
      
       heap
      
       heap工具可以帮助我们检查代码中是否存在会造成内存泄漏的地方。
       用heap监测应用进程使用内存情况的步骤如下:
       1. 启动eclipse后,切换到DDMS透视图,并确认Devices视图、Heap视图都是打开的;
       2. 点击选中想要监测的进程,比如system_process进程;
       3. 点击选中Devices视图界面中最上方一排图标中的“Update Heap”图标;
       4. 点击Heap视图中的“CauseGC”按钮;
       5. 此时在Heap视图中就会看到当前选中的进程的内存使用量的详细情况。
       说明:
       a) 点击“Cause GC”按钮相当于向虚拟机请求了一次gc操作;
       b) 当内存使用信息第一次显示以后,无须再不断的点击“Cause GC”,Heap视图界面会定时刷新,在对应用的不断的操作过程中就可以看到内存使用的变化;
       c) 内存使用信息的各项参数根据名称即可知道其意思,在此不再赘述。
      
       如何才能知道我们的程序是否有内存泄漏的可能性呢。这里需要注意一个值:Heap视图中部有一个Type叫做dataobject,即数据对象,也就是我们的程序中大量存在的类类型的对象。在data object一行中有一列是“Total Size”,其值就是当前进程中所有Java数据对象的内存总量,一般情况下,这个值的大小决定了是否会有内存泄漏。可以这样判断:
       a) 不断的操作当前应用,同时注意观察data object的Total Size值;
       b) 正常情况下Total Size值都会稳定在一个有限的范围内,也就是说由于程序中的的代码良好,没有造成对象不被垃圾回收的情况,所以说虽然我们不断的操作会不断的生成很多对象,而在虚拟机不断的进行GC的过程中,这些对象都被回收了,内存占用量会会落到一个稳定的水平;
       c) 反之如果代码中存在没有释放对象引用的情况,则data object的Total Size值在每次GC后不会有明显的回落,随着操作次数的增多Total Size的值会越来越大,直到到达一个上限后导致进程被kill掉。
       d) 此处以system_process进程为例,在我的测试环境中system_process进程所占用的内存的data object的Total Size正常情况下会稳定在2.2~2.8之间,而当其值超过3.55后进程就会被kill。  
       总之,使用DDMS的Heap视图工具可以很方便的确认我们的程序是否存在内存泄漏的可能性。

       allocation tracker

       运行DDMS,只需简单的选择应用进程并单击Allocation tracker标签,就会打开一个新的窗口,单击“StartTracing”按钮;然后,让应用运行你想分析的代码。运行完毕后,单击“Get Allocations”按钮,一个已分配对象的列表就会出现第一个表格中。单击第一个表格中的任何一项,在表格二中就会出现导致该内存分配的栈跟踪信息。通过allocation tracker,不仅知道分配了哪类对象,还可以知道在哪个线程、哪个类、哪个文件的哪一行。

2、什么情况下会导致内存泄露

       Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M。因此我们所能利用的内存空间是有限的。如果我们的内存占用超过了一定的水平就会出现OutOfMemory的错误。

       内存溢出的几点原因:
       1、资源释放问题
       程序代码的问题,长期保持某些资源,如Context、Cursor、IO流的引用,资源得不到释放造成内存泄露。
       2、对象内存过大问题
       保存了多个耗用内存过大的对象(如Bitmap、XML文件),造成内存超出限制。
       3、static关键字的使用问题
       static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多),这时就要谨慎对待了。
       public classClassName {  
              private static Context mContext;  
              //省略
       }
       以上的代码是很危险的,如果将Activity赋值到mContext的话。那么即使该Activity已经onDestroy,但是由于仍有对象保存它的引用,因此该Activity依然不会被释放。

       我们举Android文档中的一个例子:
  1. private static Drawable sBackground;  
  2.     @Override
  3.     protected void onCreate(Bundle state) {  
  4.         super.onCreate(state);  
  5.         TextView label = new TextView(this);  
  6.         label.setText("Leaks are bad");  
  7.         if (sBackground == null) {  
  8.             sBackground = getDrawable(R.drawable.large_bitmap);  
  9.         }  
  10.         label.setBackgroundDrawable(sBackground);  
  11.         setContentView(label);  
  12.     }
  13. }
复制代码
      
       sBackground是一个静态的变量,但是我们发现,我们并没有显式的保存Contex的引用,但是,当Drawable与View连接之后,Drawable就将View设 置为一个回调,由于View中是包含Context的引用的,所以,实际上我们依然保存了Context的引用。这个引用链如下:
       Drawable->TextView->Context
       所以,最终该Context也没有得到释放,发生了内存泄露。
      
       针对static的解决方案
       ① 应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。
       ② Context尽量使用ApplicationContext,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。
       ③ 使用WeakReference代替强引用。比如可以使用WeakReference<Context> mContextRef;
       4、线程导致内存溢出
       线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。

  1. public class MyActivity extends Activity {  
  2.     @Override  
  3.     public void onCreate(Bundle savedInstanceState) {  
  4.         super.onCreate(savedInstanceState);  
  5.         setContentView(R.layout.main);  
  6.         new MyThread().start();  
  7.     }  
  8.     private class MyThread extends Thread{  
  9. @Override  
  10.         public void run() {  
  11.             super.run();  
  12.             //do somthing  
  13.         }
  14.     }  
  15. }
复制代码
      
       这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设MyThread的run函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,一般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才对,然而事实上并非如此。
       由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。
       有些人喜欢用Android提供的AsyncTask,但事实上AsyncTask的问题更加严重,Thread只有在run函数不结束时才出现这种内存泄露问题,然而AsyncTask内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题。
      
       针对这种线程导致的内存泄露问题的解决方案:
       第一、将线程的内部类,改为静态内部类(因为非静态内部类拥有外部类对象的强引用,而静态类则不拥有)。
       第二、在线程内部采用弱引用保存Context引用。

3、如何避免OOM异常

       想要避免OOM异常首先我们要知道什么情况下会导致OOM异常。

       1、图片过大导致OOM
       Android 中用bitmap时很容易内存溢出,比如报如下错误:Java.lang.OutOfMemoryError: bitmap size exceeds VM budget。
       解决方法:
       方法1: 等比例缩小图片
  1. BitmapFactory.Options options = new BitmapFactory.Options();
  2. options.inSampleSize = 2;
  3. //Options 只保存图片尺寸大小,不保存图片到内存
  4. BitmapFactory.Options opts = new BitmapFactory.Options();
  5. opts.inSampleSize = 2;
  6. Bitmap bmp = null;
  7. bmp = BitmapFactory.decodeResource(getResources(), mImageIds[position],opts);                             
  8. //回收
  9. bmp.recycle();
复制代码
       以上代码可以优化内存溢出,但它只是改变图片大小,并不能彻底解决内存溢出。
       方法2:对图片采用软引用,及时地进行recyle()操作
  1. SoftReference<Bitmap> bitmap = new SoftReference<Bitmap>(pBitmap);
  2. if(bitmap != null){
  3.         if(bitmap.get() != null && !bitmap.get().isRecycled()){
  4.                 bitmap.get().recycle();
  5.                 bitmap = null;
  6.         }
  7. }
复制代码
       2、界面切换导致OOM
       有时候我们会发现这样的问题,横竖屏切换N次后 OOM了。
       这种问题没有固定的解决方法,但是我们可以从以下几个方面下手分析。
       1、看看页面布局当中有没有大的图片,比如背景图之类的。
       去除xml中相关设置,改在程序中设置背景图(放在onCreate()方法中):
  1. Drawable drawable = getResources().getDrawable(R.drawable.id);
  2. ImageView imageView = new ImageView(this);
  3. imageView.setBackgroundDrawable(drawable);
复制代码
       在Activity destory时注意,drawable.setCallback(null); 防止Activity得不到及时的释放。
       2、跟上面方法相似,直接把xml配置文件加载成view 再放到一个容器里,然后直接调用 this.setContentView(View view);方法,避免xml的重复加载。
       3、 在页面切换时尽可能少地重复使用一些代码
       比如:重复调用数据库,反复使用某些对象等等......
       常见的内存使用不当的情况
       3、查询数据库没有关闭游标
       程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会出现内存问题,这样就会给以后的测试和问题排查带来困难和风险。
       4、构造Adapter时,没有使用缓存的 convertView
       在使用ListView的时候通常会使用Adapter,那么我们应该尽可能的使用ConvertView。
       5、Bitmap对象不再使用时调用recycle()释放内存
       有时我们会手工的操作Bitmap对象,如果一个Bitmap对象比较占内存,当它不再被使用的时候,可以调用Bitmap.recycle()方法回收此对象的像素所占用的内存,但这不是必须的,视情况而定。
       6、其他
       Android应用程序中最典型的需要注意释放资源的情况是在Activity的生命周期中,在onPause()、onStop()、 onDestroy()方法中需要适当的释放资源的情况。

4、Android中如何捕获未捕获的异常

       1、自定义一个Application,比如叫MyApplication继承Application实现UncaughtExceptionHandler。
       2、覆写UncaughtExceptionHandler的onCreate和uncaughtException方法。
  1.         @Override
  2.         public void onCreate() {
  3.                 super.onCreate();
  4.                 Thread.setDefaultUncaughtExceptionHandler(this);
  5.         }

  6.         @Override
  7.         public void uncaughtException(final Thread thread, final Throwable ex) {
  8.                 new Thread(new Runnable() {
  9.                         
  10.                         @Override
  11.                         public void run() {
  12.                                 Looper.prepare();
  13.                                 System.out.println(Thread.currentThread());
  14.                                 Toast.makeText(getApplicationContext(), "thread="+thread.getId()+" ex="+ex.toString(), 1).show();
  15.                                 Looper.loop();
  16.                         }
  17.                 }).start();
  18.                 SystemClock.sleep(3000);
  19.                 android.os.Process.killProcess(android.os.Process.myPid());
  20.         }
复制代码
       注意:上面的代码只是简单的将异常打印出来。
       在onCreate方法中我们给Thread类设置默认异常处理handler,如果这句代码不执行则一切都是白搭。
       在uncaughtException方法中我们必须新开辟个线程进行我们异常的收集工作,然后将系统给杀死。
       3、在AndroidManifest中配置该Application
  1. <application
  2.         android:name="com.example.uncatchexception.MyApplication"
  3. ---------------------------------------------------------------------------------
复制代码
       4、blog分享
       关于异常数据的收集在网上有一篇不错的blog可以推荐给大家。
       http://blog.csdn.net/jdsjlzx/article/details/7606423

二、Android屏幕适配

       1、屏幕适配方式都有哪些

       1.1 适配方式之dp

       名词解释:
       分辨率:eg:480*800,1280*720。表示物理屏幕区域内像素点的总和。(切记:跟屏幕适配没有任何关系)
       因为我们既可以把1280*720的分辨率做到4.0的手机上面。我也可以把1280*720的分辨率做到5.0英寸的手机上面,如果分辨率相同,手机屏幕越小清晰。
       px(pix):像素,就是屏幕中最小的一个显示单元
       dpi(像素密度):即每英寸屏幕所拥有的像素数,像素密度越大,显示画面细节就越丰富。
       计算公式:像素密度=√{(长度像素数^2+宽度像素数^2)}/ 屏幕尺寸
       注:屏幕尺寸单位为英寸 例:分辨率为1280*720 屏幕宽度为6英寸 计算所得像素密度约等于245,屏幕尺寸指屏幕对角线的长度。
       在Android手机中dpi分类:

  
ldpi
  
Resources  for low-density (ldpi) screens (~120dpi).
mdpi
Resources  for medium-density (mdpi) screens (~160dpi). (This is the baseline  density.)
hdpi
Resources  for high-density (hdpi) screens (~240dpi).
xhdpi
Resources  for extra high-density (xhdpi) screens (~320dpi).

       在我们的Android工程目录中有如下drawable-*dpi目录,这些目录是用来适配不同分辨率手机的。



       Android应用在查找图片资源时会根据其分辨率自动从不同的文件目录下查找(这本身就是Android系统的适配策略),如果在低分辨的文件目录中比如drawable-mdpi中没有图片资源,其他目录中都有,当我们将该应用部署到mdpi分辨率的手机上时,那么该应用会查找分辨率较高目录下的资源文件,如果较高分辨率目录下也没有资源则只好找较低目录中的资源了。
      
       常见手机屏幕像素及对应分别率级别:
       ldpi 320*240     
       mdpi 480*320     
       hdpi 800*480
       xhdpi 1280*720
       xxhdpi 1920*1080
       dp和px之间的简单换算关系:
       ldpi的手机 1dp=0.75px
       mdpi的手机 1dp=1.0px
       hdpi的手机 1dp=1.5px
       xhdpi的手机 1dp=2.0px
       xxhdpi的手机 1dp=3.0px



       根据上面的描述我们得出如下结论,对于mdpi的手机,我们的布局通过dp单位可以达到适配效果。

       1.2 适配方式之dimens

       跟drawable目录类似的,在Android工程的res目录下有values目录,这个是默认的目录,同时为了适配不同尺寸手机我们可以创建一个values-1280x720的文件夹,同时将dimens.xml文件拷贝到该目录下。



       在dimens.xml中定义一个尺寸,如下图所示。



       在values-1280x720目录中的dimens.xml中定义同样的尺寸名称,但是使用不同的尺寸,如下图所示。



       当我们在布局文件中使用长或者宽度单位时,比如下图所示,应该使用@dimen/width来灵活的定义宽度。



       在values-1280x720中,中间的是大写字母X的小写形式x,而不是加减乘除的乘号。如果我们在values-1280x720中放置了dimens常量,一定记得也将该常量的对应值在values目录下的dimens.xml中放一份,因为该文件是默认配置,当用户的手机不是1280*720的情况下系统应用使用的是默认values目录中的dimens.xml。

       1.3 适配方式之layout

       跟values一样,在Android工程目录中layout目录也支持类似values目录一样的适配,在layout中我们可以针对不同手机的分辨率制定不同的布局,如下图所示。



       1.4 适配方式之java代码适配

       为了演示用java代码控制适配的效果,因此假设有这样的需求,让一个TextView控件的宽和高分别为屏幕的宽和高的一半。
       我们新创建一个Android工程,修改main_activity.xml,布局文件清单如下:

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.     xmlns:tools="http://schemas.android.com/tools"
  3.     android:layout_width="match_parent"
  4.     android:layout_height="match_parent"
  5.     tools:context=".MainActivity" >
  6.     <!-- 当前控件宽高为屏幕宽度的各50% -->
  7.     <TextView
  8.         android:id="@+id/tv"
  9.         android:background="#000000"
  10.         android:layout_width="wrap_content"
  11.         android:layout_height="wrap_content"
  12.         android:text="@string/hello_world" />
  13. </RelativeLayout>
复制代码

       在MainActivity.java类中完成用java代码控制TextView的布局效果,其代码清单如下:

  1. public class MainActivity extends Activity {

  2.         private static final String tag = null;

  3.         @Override
  4.         protected void onCreate(Bundle savedInstanceState) {
  5.                 super.onCreate(savedInstanceState);
  6.                 //去掉title
  7.                 requestWindowFeature(Window.FEATURE_NO_TITLE);
  8.                 setContentView(R.layout.activity_main);
  9.                 //获取TextView控件
  10.                 TextView tv  = (TextView) findViewById(R.id.tv);
  11.                 //找到当前控件的夫控件(父控件上给当前的子控件去设定一个规则)
  12.                 DisplayMetrics metrics  = new DisplayMetrics();
  13.                 //给当前metrics去设置当前屏幕信息(宽(像素)高(像素))
  14.                 getWindowManager().getDefaultDisplay().getMetrics(metrics);
  15.                 //获取屏幕的高度和宽度
  16.                 Constant.srceenHeight = metrics.heightPixels;
  17.                 Constant.srceenWidth = metrics.widthPixels;
  18.                 //日志输出屏幕的高度和宽度
  19.                 Log.i(tag, "Constant.srceenHeight = "+Constant.srceenHeight);
  20.                 Log.i(tag, "Constant.srceenWidth = "+Constant.srceenWidth);
  21.                                 //宽高各 50%
  22.                 RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
  23.                                 //数学角度上 四舍五入
  24.                                 (int)(Constant.srceenWidth*0.5+0.5),
  25.                                 (int)(Constant.srceenHeight*0.5+0.5));
  26.                 //给tv控件设置布局参数
  27.                 tv.setLayoutParams(layoutParams);
  28.         }
  29. }
复制代码

       其中Constant类是一个常量类,很简单,只有两个常量用来记录屏幕的宽和高,其代码清单如下:

  1. public class Constant {
  2.         public static int srceenHeight;
  3.         public static int srceenWidth;
  4. }
复制代码

       1.5适配方式之weight权重适配

       在控件中使用属性android:layout_weight="1"以起到适配效果,但是该属性的使用有如下规则:
       1、只能用在线性控件中,比如LinearLayout。
       2、竖直方向上使用权重的控件高度必须为0dp(Google官方的推荐用法)
       3、水平方向上使用权重的控件宽度必须为0dp(Google官方的推荐用法)

2、屏幕适配的处理技巧都有哪些


       手机自适应主要分为两种情况:横屏和竖屏的切换,以及分辨率大小的不同。

       2.1横屏和竖屏的切换

       1、Android应用程序支持横竖屏幕的切换,Android中每次屏幕的切换动会重启Activity,所以应该在Activity销毁(执行onPause()方法和onDestroy()方法)前保存当前活动的状态;在Activity再次创建的时候载入配置,那样,进行中的游戏就不会自动重启了!有的程序适合从竖屏切换到横屏,或者反过来,这个时候怎么办呢?可以在配置Activity的地方进行如下的配置android:screenOrientation="portrait"(landscape是横向,portrait是纵向)。这样就可以保证是竖屏总是竖屏了。
       2、而有的程序是适合横竖屏切换的。如何处理呢?首先要在配置Activity的时候进行如下的配置:
android:configChanges="keyboardHidden|orientation",另外需要重写Activity的onConfigurationChanged方法。实现方式如下:

  1. @Override
  2. public void onConfigurationChanged(Configuration newConfig){
  3.     super.onConfigurationChanged(newConfig);
  4.     if(this.getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE){
  5.         //TODO
  6.     }else if(
  7.         this.getResources().getConfiguration().orientation==Configuration.ORIENTATION_PORTRAIT){
  8.         //TODO
  9.     }
  10. }
复制代码

       2.2 分辨率大小不同

       对于分辨率问题,官方给的解决办法是创建不同的layout文件夹,这就需要对每种分辨率的手机都要写一个布局文件,虽然看似解决了分辨率的问题,但是如果其中一处或多处有修改了,就要每个布局文件都要做出修改,这样就造成很大的麻烦。那么可以通过以下几种方式解决:
       一)使用layout_weight
       目前最为推荐的Android多屏幕自适应解决方案。
       该属性的作用是决定控件在其父布局中的显示权重,一般用于线性布局中。其值越小,则对应的layout_width或layout_height的优先级就越高(一般到100作用就不太明显了);一般横向布局中,决定的是layout_width的优先级;纵向布局中,决定的是layout_height的优先级。
       传统的layout_weight使用方法是将当前控件的layout_width和layout_height都设置成fill_parent,这样就可以把控件的显示比例完全交给layout_weight;这样使用的话,就出现了layout_weight越小,显示比例越大的情况(即权重越大,显示所占的效果越小)。不过对于2个控件还好,如果控件过多,且显示比例也不相同的时候,控制起来就比较麻烦了,毕竟反比不是那么好确定的。于是就有了现在最为流行的0px设值法。看似让人难以理解的layout_height=0px的写法,结合layout_weight,却可以使控件成正比例显示,轻松解决了当前Android开发最为头疼的碎片化问题之一。
       二)清单文件配置:【不建议使用这种方式,需要对不同的界面写不同的布局】
       需要在AndroidManifest.xml文件的<manifest>元素如下添加子元素

  1. <supports-screensandroid:largeScreens="true"
  2. android:normalScreens="true"
  3. android:anyDensity="true"
  4. android:smallScreens="true"
  5. android:xlargeScreens="true">
  6. </supports-screens>
复制代码

       以上是为我们的屏幕设置多分辨率支持(更准确的说是适配大、中、小三种密度)。
       Android:anyDensity="true",这一句对整个的屏幕都起着十分重要的作用,值为true,我们的应用程序当安装在不同密度的手机上时,程序会分别加载hdpi,mdpi,ldpi文件夹中的资源。相反,如果值设置为false,即使我们在hdpi,mdpi,ldpi,xdpi文件夹下拥有同一种资源,那么应用也不会自动地去相应文件夹下寻找资源。而是会在大密度和小密度手机上加载中密度mdpi文件中的资源。
       有时候会根据需要在代码中动态地设置某个值,可以在代码中为这几种密度分别设置偏移量,但是这种方法最好不要使用,最好的方式是在xml文件中不同密度的手机进行分别设置。这里地图的偏移量可以在values-xpdi,values-hpdi,values-mdpi,values-ldpi四种文件夹中的dimens.xml文件进行设置。
       三)、其他:
       说明:
       在不同分辨率的手机模拟器下,控件显示的位置会稍有不同
       通过在layout中定义的布局设置的参数,使用dp(dip),会根据不同的屏幕分辨率进行适配
       但是在代码中的各个参数值,都是使用的像素(px)为单位的
       技巧:
       1、尽量使用线性布局,相对布局,如果屏幕放不下了,可以使用ScrollView(可以上下拖动)
       ScrowView使用的注意:
       在不同的屏幕上显示内容不同的情况,其实这个问题我们往往是用滚动视图来解决的,也就是ScrowView;需要注意的是ScrowView中使用layout_weight是无效的,既然使用ScrowView了,就把它里面的控件的大小都设成固定的吧。
       2、指定宽高的时候,采用dip的单位,dp单位动态匹配
       3、由于android代码中写的单位都是像素,所有需要通过工具类进行转化
       4、尽量使用9-patch图,可以自动的依据图片上面显示的内容被拉伸和收缩。其中在编辑的时候,灰色区域是被拉伸的,上下两个点控制水平方向的拉伸,左右两点控制垂直方向的拉伸

       3、dp和px之间的关系

       dp:是dip的简写,指密度无关的像素。
       指一个抽象意义上的像素,程序用它来定义界面元素。一个与密度无关的,在逻辑尺寸上,与一个位于像素密度为160dpi的屏幕上的像素是一致的。要把密度无关像素转换为屏幕像素,可以用这样一个简单的公式:pixels=dips*(density/160)。举个例子,在DPI为240的屏幕上,1个DIP等于1.5个物理像素。
       布局时最好使用dp来定义我们程序的界面,因为这样可以保证我们的UI在各种分辨率的屏幕上都可以正常显示。

  1. /**
  2. * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
  3. */  
  4. public static int px2dip(Context context, float pxValue) {  
  5.     final float scale = context.getResources().getDisplayMetrics().density;  
  6.     return (int) (pxValue / scale + 0.5f);  
  7. }  
  8. /**
  9. * 根据手机的分辨率从 dip 的单位 转成为 px(像素)
  10. */  
  11. public static int dip2px(Context context, float dpValue) {  
  12.     final float scale = context.getResources().getDisplayMetrics().density;  
  13.     return (int) (dpValue * scale + 0.5f);  
  14. }  
复制代码

三、AIDL

       1、什么是AIDL以及如何使用
       ①aidl是Android interface definition Language 的英文缩写,意思Android 接口定义语言。
       ②使用aidl可以帮助我们发布以及调用远程服务,实现跨进程通信。
       ③将服务的aidl放到对应的src目录,工程的gen目录会生成相应的接口类
       我们通过bindService(Intent,ServiceConnect,int)方法绑定远程服务,在bindService中有一个ServiceConnec接口,我们需要覆写该类的onServiceConnected(ComponentName,IBinder)方法,这个方法的第二个参数IBinder对象其实就是已经在aidl中定义的接口,因此我们可以将IBinder对象强制转换为aidl中的接口类。
我们通过IBinder获取到的对象(也就是aidl文件生成的接口)其实是系统产生的代理对象,该代理对象既可以跟我们的进程通信,又可以跟远程进程通信,作为一个中间的角色实现了进程间通信。

四、自定义控件

       1、如何自定义一个控件

       自定义控件可以分为两种自定义组合控件和自定义view。
       自定义组合控件
       自定义组合控件就是把多个控件做为一个整体看待、处理。这样的好处不仅可以减轻xml的代码量,也提高了代码的复用性。
       在手机卫士项目中我们第一次接触了自定义组合控件。
       1. 声明一个View 对象,继承相对布局,或者线性布局或者其他的ViewGroup。
       2. 在自定义的View 对象里面重写它的构造方法,在构造方法里面就把布局都初始化完毕。
       3. 根据业务需求添加一些api 方法,扩展自定义的组合控件;
       4. 希望在布局文件里面可以自定义一些属性。
       5. 声明自定义属性的命名空间。
       xmlns:itheima="http://schemas.android.com/apk/res/com.itheima.mobilesafe"
       6. 在res 目录下的values 目录下创建attrs.xml的文件声明我们写的属性。
       7. 在布局文件中写自定义的属性。
       8. 使用这些定义的属性。自定义View 对象的构造方法里面有一个带两个参数的构造方法布局文件里面定义的属性都放在AttributeSetattrs,获取那些定义的属性。
       自定义view
       自定义View首先要实现一个继承自View的类。添加类的构造方法,通常是三个构造方法,不过从Android5.0开始构造方法已经添加到4个了。override父类的方法,如onDraw,(onMeasure)等。如果自定义的View有自己的属性,需要在values下建立attrs.xml文件,在其中定义属性,同时代码也要做修改。
       blog分享:http://blog.csdn.net/lmj623565791/article/details/24252901

       2、请描述一下View的绘制流程

       整个View树的绘图流程是在ViewRoot.java类(该类位于Android源码下面:D:\AndroidSource_GB\AndroidSource_GB\frameworks\base\core\java\android\view)的performTraversals()函数展开的,该函数做的执行过程可简单概况为根据之前设置的状态,判断是否需要重新计算视图大小(measure)、是否重新需要安置视图的位置(layout)、以及是否需要重绘(draw),其框架过程如下:



       1、mesarue()过程
       主要作用:为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的。
       具体的调用链如下: ViewRoot根对象的属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调View/ViewGroup对象的onMeasure()方法,该方法实现的功能如下:   
       1、设置本View视图的最终大小,该功能的实现通过调用setMeasuredDimension()方法去设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth)。
       2 、如果该View对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的measure()过程。对每个子视图的measure()过程,是通过调用父类ViewGroup.java类里的measureChildWithMargins()方法去实现,该方法内部只是简单地调用了View对象的measure()方法。
       2、layout布局过程
       主要作用:为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。
       具体的调用链如下:
       1、layout方法会设置该View视图位于父视图的坐标轴,即mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现)接下来回调onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局)。
       2、如果该View是个ViewGroup类型,需要遍历每个子视图chiildView,调用该子视图的layout()方法去设置它的坐标值。
       3、draw()绘图过程
       由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。
       调用流程 :
       1 、绘制该View的背景
       2 、为显示渐变框做一些准备操作(大多数情况下,不需要改渐变框)         
       3、调用onDraw()方法绘制视图本身(每个View都需要重载该方法,ViewGroup不需要实现该方法)
       4、调用dispatchDraw()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)
       值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。
       参考blog分享:http://blog.csdn.net/qinjuning/article/details/7110211

五、Android中的事件处理


       1、Handler机制

       Android中主线程也叫UI线程,那么从名字上我们也知道主线程主要是用来创建、更新UI的,而其他耗时操作,比如网络访问,或者文件处理,多媒体处理等都需要在子线程中操作,之所以在子线程中操作是为了保证UI的流畅程度,手机显示的刷新频率是60Hz,也就是一秒钟刷新60次,每16.67毫秒刷新一次,为了不丢帧,那么主线程处理代码最好不要超过16毫秒。当子线程处理完数据后,为了防止UI处理逻辑的混乱,Android只允许主线程修改UI,那么这时候就需要Handler来充当子线程和主线程之间的桥梁了。
       我们通常将Handler声明在Activity中,然后覆写Handler中的handleMessage方法,当子线程调用handler.sendMessage()方法后handleMessage方法就会在主线程中执行。
       这里面除了Handler、Message外还有隐藏的Looper和MessageQueue对象。
       在主线程中Android默认已经调用了Looper.preper()方法,调用该方法的目的是在Looper中创建MessageQueue成员变量并把Looper对象绑定到当前线程中。当调用Handler的sendMessage(对象)方法的时候就将Message对象添加到了Looper创建的MessageQueue队列中,同时给Message指定了target对象,其实这个target对象就是Handler对象。主线程默认执行了Looper.looper()方法,该方法从Looper的成员变量MessageQueue中取出Message,然后调用Message的target对象的handleMessage()方法。这样就完成了整个消息机制。

       2、事件分发机制

       2.1 事件分发中的onTouch和onTouchEvent有什么区别,又该如何使用?

       这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。
另外需要注意的是,onTouch能够得到执行需要两个前提条件,第一mOnTouchListener的值不能为空,第二当前点击的控件必须是enable的。因此如果你有一个控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一类控件,如果我们想要监听它的touch事件,就必须通过在该控件中重写onTouchEvent方法来实现。

       2.2 请描述一下Android的事件分发机制

       Android的事件分发机制主要是Touch事件分发,有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。View在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析。
       先分析ViewGroup的处理流程:首先得有个结构模型概念:ViewGroup和View组成了一棵树形结构,最顶层为Activity的ViewGroup,下面有若干的ViewGroup节点,每个节点之下又有若干的ViewGroup节点或者View节点,依次类推。如图:



       当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。
       1.Touch事件分发中只有两个主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、onTouchEvent两个相关事件。其中ViewGroup又继承于View。
       2.ViewGroup和View组成了一个树状结构,根节点为Activity内部包含的一个ViwGroup。
       3.触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。
       4.当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。
       5.当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。由于子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在的ViewGroup对象:如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在ViewGroup1中,而ViewGroup1也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从ViewGroup0传递至ViewGroup1,再由ViewGroup1传递至TextView。
       6.当ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup自身的onTouch事件。触发的方式是调用super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在所有子View都不处理的情况下,触发Acitivity的onTouchEvent方法。
       7.onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。

六、Android签名

1、简单描述下Android 数字签名
      
       在Android系统中,所有安装到系统的应用程序都必有一个数字证书,此数字证书用于标识应用程序的作者和在应用程序之间建立信任关系。
       Android系统要求每一个安装进系统的应用程序都是经过数字证书签名的,数字证书的私钥则保存在程序开发者的手中。Android将数字证书用来标识应用程序的作者和在应用程序之间建立信任关系,不是用来决定最终用户可以安装哪些应用程序。
       这个数字证书并不需要权威的数字证书签名机构认证(CA),它只是用来让应用程序包自我认证的。
       同一个开发者的多个程序尽可能使用同一个数字证书,这可以带来以下好处。
       (1)有利于程序升级,当新版程序和旧版程序的数字证书相同时,Android系统才会认为这两个程序是同一个程序的不同版本。如果新版程序和旧版程序的数字证书不相同,则Android系统认为他们是不同的程序,并产生冲突,会要求新程序更改包名。
       (2)有利于程序的模块化设计和开发。Android系统允许拥有同一个数字签名的程序运行在一个进程中,Android程序会将他们视为同一个程序。所以开发者可以将自己的程序分模块开发,而用户只需要在需要的时候下载适当的模块。
      
       在签名时,需要考虑数字证书的有效期:
       (1)数字证书的有效期要包含程序的预计生命周期,一旦数字证书失效,持有改数字证书的程序将不能正常升级。
       (2)如果多个程序使用同一个数字证书,则该数字证书的有效期要包含所有程序的预计生命周期。
       (3)AndroidMarket强制要求所有应用程序数字证书的有效期要持续到2033年10月22日以后。
       Android数字证书包含以下几个要点:
       (1)所有的应用程序都必须有数字证书,Android系统不会安装一个没有数字证书的应用程序
       (2)Android程序包使用的数字证书可以是自签名的,不需要一个权威的数字证书机构签名认证
       (3)如果要正式发布一个Android ,必须使用一个合适的私钥生成的数字证书来给程序签名,而不能使用adt插件或者ant工具生成的调试证书来发布。
       (4)数字证书都是有有效期的,Android只是在应用程序安装的时候才会检查证书的有效期。如果程序已经安装在系统中,即使证书过期也不会影响程序的正常功能。

2、使用Eclipse如何生成数字签名
      
       可以通过Eclipse导出工程时为当前工程设置签名证书。File -> Export ->Export Android Application ->Create Newkeystore ....

七、Android中的动画


1、Android中的动画有哪几类,它们的特点和区别是什么

       Android中动画分为两种,一种是Tween动画、还有一种是Frame动画。
       Tween动画,这种实现方式可以使视图组件移动、放大、缩小以及产生透明度的变化;
       Frame动画,传统的动画方法,通过顺序的播放排列好的图片来实现,类似电影。

2、如何修改Activity进入和退出动画

       可以通过两种方式,一是通过定义Activity的主题,二是通过覆写Activity的overridePendingTransition方法。

       通过设置主题样式
       在styles.xml中编辑如下代码:

  1. <style name="AnimationActivity" parent="@android:style/Animation.Activity">  
  2.     <item name="android:activityOpenEnterAnimation">@anim/slide_in_left</item>  
  3.     <item name="android:activityOpenExitAnimation">@anim/slide_out_left</item>  
  4.     <item name="android:activityCloseEnterAnimation">@anim/slide_in_right</item>  
  5.     <item name="android:activityCloseExitAnimation">@anim/slide_out_right</item>  
  6. </style>
复制代码

       添加themes.xml文件:

  1. <style name="ThemeActivity">  
  2.     <item name="android:windowAnimationStyle">@style/AnimationActivity</item>  
  3.     <item name="android:windowNoTitle">true</item>  
  4. </style>  
复制代码

       在AndroidManifest.xml中给指定的Activity指定theme。
       覆写overridePendingTransition方法
       overridePendingTransition(R.anim.fade, R.anim.hold);

八、其他知识(非归类内容)

1、AsyncTask如何使用

       AsyncTask用于处理异步任务,该类是一个抽象的泛型类。类的签名如下:public abstract class AsyncTask<Params, Progress, Result>。
       三种泛型类型分别代表“启动任务执行的输入参数”、“后台任务执行的进度”、“后台计算结果的类型”。在特定场合下,并不是所有类型都被使用,如果没有被使用,可以用java.lang.Void类型代替。
      
       一个异步任务的执行一般包括以下几个步骤:
       1.execute(Params...params),执行一个异步任务,需要我们在代码中调用此方法,触发异步任务的执行。
       2.onPreExecute(),在execute(Params... params)被调用后立即执行,一般用来在执行后台任务前对UI做一些标记。
       3.doInBackground(Params...params),在onPreExecute()完成后立即执行,用于执行较为费时的操作,此方法将接收输入参数和返回计算结果。在执行过程中可以调用publishProgress(Progress... values)来更新进度信息。
       4.onProgressUpdate(Progress...values),在调用publishProgress(Progress... values)时,此方法被执行,直接将进度信息更新到UI组件上。
       5.onPostExecute(Resultresult),当后台操作结束时,此方法将会被调用,计算结果将做为参数传递到此方法中,直接将结果显示到UI组件上。
      
       在使用的时候,有几点需要格外注意:
       1.异步任务的实例必须在UI线程中创建。
       2.execute(Params...params)方法必须在UI线程中调用。
       3.不要手动调用onPreExecute(),doInBackground(Params...params),onProgressUpdate(Progress... values),onPostExecute(Result result)这几个方法。
       4.不能在doInBackground(Params... params)中更改UI组件的信息。
       5.一个任务实例只能执行一次,如果执行第二次将会抛出异常。
      
       我写一个简单的例子来演示AsyncTask的用法。
       MainActivity.java
  1. public class MainActivity extends Activity {

  2.         private TextView tv;

  3.         @Override
  4.         protected void onCreate(Bundle savedInstanceState) {
  5.                 super.onCreate(savedInstanceState);
  6.                 setContentView(R.layout.activity_main);
  7.                 tv = (TextView) findViewById(R.id.tv);
  8.         }
  9.         /**
  10.          * 点击button绑定的事件
  11.          */
  12.         public void start(View view){
  13.                 AsyncTask<Integer,Integer,String> asyncTask = new MyAsyncTask();
  14.                 asyncTask.execute(100);
  15.         }
  16.         class MyAsyncTask extends AsyncTask<Integer, Integer, String>{
  17.                 /**
  18.                  * 该方法在子线程中运行,因此不能有任何修改UI操作
  19.                  */
  20.                 @Override
  21.                 protected String doInBackground(Integer... params) {
  22.                         for(int i=0;i<params[0];i++){
  23.                                 try {
  24.                                         //模拟耗时操作
  25.                                         Thread.sleep(100);
  26.                                 } catch (InterruptedException e) {
  27.                                         e.printStackTrace();
  28.                                 }
  29.                                 //发送进度
  30.                                 publishProgress(i);
  31.                         }
  32.                         return "任务已经完成";
  33.                 }
  34.                 /**
  35.                  * 任务执行前在UI线程中调用
  36.                  */
  37.                 @Override
  38.                 protected void onPreExecute() {
  39.                         Toast.makeText(MainActivity.this, "开始执行任务", 0).show();
  40.                         super.onPreExecute();
  41.                 }
  42.                 /**
  43.                  * 任务执行后在UI线程中调用<br>
  44.                  * @param result 正是doInBackground的返回值
  45.                  */
  46.                 @Override
  47.                 protected void onPostExecute(String result) {
  48.                         Toast.makeText(MainActivity.this, result, 0).show();
  49.                         super.onPostExecute(result);
  50.                 }
  51.                 /**
  52.                  * 在UI线程中执行
  53.                  * 当doInBackground执行publishProgress时调用该方法
  54.                  */
  55.                 @Override
  56.                 protected void onProgressUpdate(Integer... values) {
  57.                         super.onProgressUpdate(values);
  58.                         tv.setText("当前进度:"+values[0]);
  59.                 }
  60.         }
  61. }
复制代码
       activity_main.xml
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.     xmlns:tools="http://schemas.android.com/tools"
  3.     android:layout_width="match_parent"
  4.     android:layout_height="match_parent" >

  5.     <TextView
  6.         android:id="@+id/tv"
  7.         android:layout_width="wrap_content"
  8.         android:layout_height="wrap_content"
  9.         android:layout_centerInParent="true"
  10.         android:text="显示进度"
  11.         android:textSize="30sp" />

  12.     <Button
  13.         android:layout_width="match_parent"
  14.         android:layout_height="wrap_content"
  15.         android:layout_alignParentBottom="true"
  16.         android:layout_margin="10dp"
  17.         android:onClick="start"
  18.         android:text="开始" />
  19. </RelativeLayout>
复制代码

       运行效果图:
       点击开始后,界面显示的数值不同的更改。



2、都使用过哪些框架、平台

    2.1 EventBus(事件处理)
       2.2 xUtils(网络、图片、ORM)
       2.3 JPush(推送平台)
       2.4 友盟(统计平台)
       2.5 有米(优米)(广告平台)
       2.6 百度地图
       2.7 bmob(服务器平台、短信验证、邮箱验证、第三方支付)
       2.8 阿里云OSS(云存储)
       2.9 ShareSDK(分享平台、第三方登录)

3、都使用过哪些自定义控件

       3.1 pull2RefreshListView
       3.2 LazyViewPager
       3.3 SlidingMenu
       3.4 SmoothProgressBar
       3.5 自定义组合控件
       3.6 ToggleButton
       3.7 自定义吐



~爱上海,爱黑马~



作者: 遮天    时间: 2015-8-10 16:58
进来收藏一下.......
作者: 风的自由    时间: 2015-8-10 21:03
进来看看,以后用的着
作者: xqtzzl521    时间: 2015-8-11 22:46
很有用,关注中
作者: 致逝去的青春    时间: 2015-8-13 00:54
很强大啊
作者: wakemeup    时间: 2015-8-14 22:03
每期不落空,顶一个
作者: smartisan    时间: 2015-8-16 11:32
进来看看,以后用的着
作者: gdd2463    时间: 2015-8-18 01:34
好东西呐   {:2_32:}
作者: l_15562009298    时间: 2015-8-21 15:07
哎呦,不错欧
作者: drp000    时间: 2015-8-21 21:20
感觉好牛逼
作者: 张淑亮    时间: 2015-8-25 08:03
非常棒啊,必须收藏,感谢老师的分享
作者: 武海斌    时间: 2015-8-25 11:14
好杨哥,就是好
作者: 心之谷96    时间: 2015-8-26 08:18
继续顶。。。
作者: mayongjun    时间: 2015-9-6 21:33
好东西,受教了!!大赞
作者: forjunjian    时间: 2015-9-6 21:52
阳哥的,必须收藏
作者: lz895192176    时间: 2015-9-6 22:53
好东西啊   太专业了
作者: wyd1    时间: 2015-9-6 23:18
不错啊,领教了
作者: droay    时间: 2015-9-29 13:25
此贴必须顶啊
作者: Yan_Theo    时间: 2015-10-7 10:53
给力 !!!
作者: 妖风小王子    时间: 2015-10-7 21:27
此贴就算复制的也是大有意义
作者: jiatengde    时间: 2015-10-20 17:16
四个月的学习马上就要结束了,不得不感叹“时光如梭,白驹过隙”。曾经总是嫌弃漫长,如今却感慨让时间慢一些。每次看阳哥的面试宝典都收货颇多。无论是代码质量还是语言修辞的衔接,恐怕即便是站在巨人的肩膀上,要追上技术牛人的脚步也要付出常人难以接受的努力吧。

对于这篇面试总结,简直喜欢和崇拜的不行。

说了太多的废话,占了几段篇幅,还是直接切入重点吧。

对于性能分析,也经常搜索相关资料,恕我才疏学浅,但还是忍不住发表一下观点。

从内存泄露说起:

先来看看Square公司给出的在Android中内存泄露的解释:

> What is a memory leak?
  Some objects have a limited lifetime. When their job is done, they are expected to be garbage collected. If a chain of references holds an object in memory after the end of its expected lifetime, this creates a memory leak. When these leaks accumulate, the app runs out of memory.
>
  For instance, after Activity.onDestroy() is called, the activity, its view hierarchy and their associated bitmaps should all be garbage collectable. If a thread running in the background holds a reference to the activity, then the corresponding memory cannot be reclaimed. This eventually leads to an OutOfMemoryError crash.

大概翻译就是:  有些对象有一个有限的生命周期,当他们任务结束的时候,应该被GC回收。如果在该对象生命周期结束的时候,仍然有一系列对象hold住了该对象的引用,就会导致内存泄露。随着泄露的积累,app将内存将消耗殆尽。、
比如Activity.onDestroy( )被回调之后,view层级树以及关联的bitmap都应该被GC回收,如果一个正在运行的后台线程hold住了这个activity的引用,与其相关的内存将不会被回收,最终会导致outOfMemoryError崩溃。
从而可见,OOM一般代表着更深层次的问题:Memory Leak

由此可见,内存泄露指的是程序中一些对象不会被GC回收,而始终占用内存,即在对象的引用链中变成了可达不可用。而内存溢出是指程序运行期间无法申请足够的内存而导致的错误,可以说内存泄露是内存溢出的一种诱因,但是不唯一因素。

那么看一下这个著名的诞生于2009年的static Drawable例子,然后在SDK中查看一下历代SDK版本号,会发现

    /**
    * December 2009: Android 2.0.1
    */
     public static final int ECLAIR_0_1 = 6;


    /**
    * January 2010: Android 2.1
    */
    public static final int ECLAIR_MR1 = 7;

2009年的最后一个版本是2.0.1。2.1版本才出现在2010年,而且这个年代,貌似国内做Android的人少之甚少。


所以只需要把这些SDK的版本,翻看一遍,确实会发现这个有内存泄露隐患的强引用链:

Drawable->TextView->Context

但是如果看一下划时代的3.0版本(HONEYCOMB),会发现Google的Android小组貌似已经注意到了这个问题,并且修复了它。
   


     public final void setCallback(Callback cb) {
        mCallback = cb;
    }

替换为

    public final void setCallback(Callback cb) {
        mCallback = new WeakReference<Callback>(cb);
    }

用弱引用来代替强引用,这样就避免了Context的泄露。

再来说说Bitmap.recycle()。

这个也可以算是一个历史遗留问题。在3.0版本之前。

Bitmap.java源码。

    // Note:  mNativeBitmap is used by FaceDetector_jni.cpp
    // Don't change/rename without updating FaceDetector_jni.cpp
    private final int mNativeBitmap;

Bitmap的资源是在底层C中处理的,因此3.0之前的.recycle(),并没有什么错。但是在3.0以及之后。
Bitmap.java源码。

    /**
     * Backing buffer for the Bitmap.
     */
     private byte[] mBuffer;

图片数据保存在成员变量mBuffer中,也就是说使用Heap存储代替了底层C处理。因此不用显式调用.recycle()。只需要把bitmap置null或者使用虚引用持有就行了。这样图片就能被GC回收了。

另外,由于不同版本的SDK,.recycle()注释也不同,但是有一句话是始终没有改变的

>This is an advanced call, and normally need not be called, since the normal GC process will free up this memory when there are no more references to this bitmap.

这是一个高级函数,一般情况下没必要调用,在没有引用指向bitmap时,GC会自动释放内存。


而且我的建议是不要频繁的调用System.gc();因为这在大多情况下只能显式提醒GC回收,究竟什么时候进行回收操作,还是看VM自身的算法和调度。

目前只看到这了,后面如果还有所学以及所想,还会跟阳哥交流的。如果我的理解和建议还算中肯,希望给个置顶。有人看的文,才会有动力继续创作。
作者: 枫烬    时间: 2015-11-9 22:07
mark一下,以前只知道java的测试,真不知道安卓的性能优化
作者: gawain    时间: 2015-11-14 10:04
太给力了
作者: gawain    时间: 2015-11-14 10:08
阳哥,这个帖子能提供Download么>
作者: 浅岁时光    时间: 2015-11-14 15:31
感觉好难啊,但是看起来很有意思,以后必须好好学习.
作者: 13533258481    时间: 2015-11-18 00:25
加油
作者: 三生石123    时间: 2015-11-20 01:31
收藏一下.......
作者: 切比雪夫    时间: 2015-11-21 21:20
支持一下!!!
作者: eddy1820    时间: 2015-11-22 20:10
學習到了,感謝分享
作者: 夏日星    时间: 2015-11-22 21:46
赞一个!!!
作者: 小笨笨天    时间: 2015-11-22 21:53
赞赞赞赞
作者: 切比雪夫    时间: 2015-11-22 22:02
支持一下!!
作者: chenxianzai    时间: 2015-11-24 21:57
阳哥,赞一个,坚持更新,加油,果断收藏
作者: pengzhaoyang100    时间: 2015-11-30 22:45
好东西!!!
作者: xiaoxiao147    时间: 2015-11-30 22:50
很实用,顶一个
作者: 李玉    时间: 2015-12-1 10:52
很好的面试资料

作者: 等待戈多的小鱼    时间: 2015-12-23 20:03
总结的很好,为什么我怎么不会总结呢?
作者: trueBlackHorse    时间: 2015-12-24 09:34
怎么下载不了
作者: 果维    时间: 2015-12-24 10:34
顶顶顶顶顶顶顶顶顶顶顶
作者: Yaoluhao01    时间: 2015-12-26 21:10
已收藏,留意备用
作者: 雪域雄鹰    时间: 2016-2-21 13:09
好好学习,天天向上!
作者: laoyimou    时间: 2016-2-21 17:02
今天第二天学习,对这个帖子完全看不懂,希望3个月之后可以看懂,立帖为证
作者: 雪域雄鹰    时间: 2016-2-25 20:13
好好学习,天天向上1
作者: 雪域雄鹰    时间: 2016-2-26 12:44
好好学习,天天向上!
作者: CocoNeamo    时间: 2016-2-26 14:44
留个名收藏先,以后会用得着
作者: 雪域雄鹰    时间: 2016-2-27 13:59
好好学习!
作者: yashiro    时间: 2016-2-27 14:19
非常棒啊,必须收藏,感谢老师的分享
作者: HeiMa_Mark    时间: 2016-3-10 22:42
慢慢学习,原来论坛有这么多资料
作者: HeiMa_Mark    时间: 2016-3-11 23:19
给力!一步一步向高端开发靠拢!




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2