Android面试题详细整理系列(一)

以下这些面试题都是笔者在(2017年1月-2017年3月)这段时间所面试android工程师的总结而来,面试的公司包括巨头xx等,还有新贵公司如dd在线科技,gm金融,zk网,momo科技,zbj等,还有小型活力公司如软都科技,星云颜值,英克科技等,不足之处,还望各位不吝赐教。

1.如何在子线程创建handler?

熟悉handler机制的童鞋都知道,通常handler是在主线程创建,但有些时候要在子线程创建handler,应该怎么办(什么时候? 面试的时候 哈哈)不要急 办法总该有 嘿嘿
方法1:(直接获取当前子线程的looper)

new Thread(new Runnable() {  
            public void run() {  
                Looper.prepare(); // 此处获取到当前线程的Looper,并且prepare()  
                Handler handler = new Handler(){  
                    @Override  
                    public void handleMessage(Message msg) {  
                        Toast.makeText(getApplicationContext(), "handler msg", Toast.LENGTH_LONG).show();  
                    }  
                };  
                handler.sendEmptyMessage(1);  
                Looper.loop(); 
            };  
        }).start();

方法2(获取主线程的looper,或者说是UI线程的looper)

new Thread(new Runnable() {  
            public void run() {  
                Handler handler = new Handler(Looper.getMainLooper()){ // 区别在这!!!!  
                    @Override  
                    public void handleMessage(Message msg) {  
                        Toast.makeText(getApplicationContext(), "handler msg", Toast.LENGTH_LONG).show();  
                    }  
                };  
                handler.sendEmptyMessage(1);  
            };  
        }).start();  

2.应用程序ANR产生的原因?如何定位ANR

大家面试的时候肯定会发现,有经验的老司机提问时都是先抛出一个简单问题,然后由此引出很多深层次的问题,关键在后边,这是面试百度时候的一个问题(笔者去的是百度科技园 老偏僻了)且听我娓娓道来

产生的原因大家都知道是在主线程里做了耗时的操作导致的,ANR一般有三种类型:
1:KeyDispatchTimeout(5 seconds) –主要类型
按键或触摸事件在特定时间内无响应
2:BroadcastTimeout(10 seconds)
BroadcastReceiver在特定时间内无法处理完成
3:ServiceTimeout(20 seconds) –小概率类型
Service在特定的时间内无法处理完成
那我们就要着手解决主要类型,具体如何定位,有以下几种思路:
1.通过ANR日志定位问题
当ANR发生时,我们往往通过Logcat和traces文件(目录/data/anr/)的相关信息输出去定位问题。主要包含以下几方面:
1)基本信息,包括进程名、进程号、包名、系统build号、ANR
类型等等;
2)CPU使用信息,包括活跃进程的CPU平均占用率、IO情况等等;
3)线程堆栈信息,所属进程包括发生ANR的进程、其父进程、最近有活动的3个进程等等。
但是在平常测试中,ANR有基本测试不到,因为ANR基本发生在垃圾设备中,弱网络,频繁操作。而且问题不必现,即使看到了问题,定位麻烦:要去data/anr.txt文件里面查找,必须root,比较麻烦。

2.引入ANR检测工具
由于anr问题不必现,因此引入以下ANR检测工具,当anr问题出现时,自动dump手机中的日志信息如trace文件、堆栈信息等,基本原理如下:

2.1、基本原理
检测到UI主线程卡顿时间超过设定的时间,如4s,即dump trace文件以及堆栈信息,同时抛出异常,收集信息,根据这些文件信息即可定位到发生anr的原因
2.2、ANR检测工具在Baidu Browser中的应用
2.2.1如何在源代码中插入anr检测工具

步骤一:源代码libs中添加anr.jar

步骤二:在 Application 的onCreate中添加初始化sdk的代码

initSDK(Context context, String appKey, boolean watchdog, int time)

其中time表示检测判定线程是否超时(发生anr)的门限值,单位:ms

步骤三:正常编译打包apk

2.2.2如何测试发现并定位anr问题

安装步骤2.2.1编译打包插入anr检测的apk

测试app,任意操作(monkey/case),当发生anr时,会自动杀掉进程,并在本地生成日志文件日志路径:/sdcard/lynq_anr下有两个文件夹

以Baidu Browser启动为例。
Baidu Browser启动过程主线程过长,在低端机上容易导致发生anr;线程超时,app进程kill掉,查看手机本地trace日志,Crash信息包括trace文件以及堆栈信息
分析trace文件
Trace文件通过DDMS可以查看具体发生ANR卡顿的原因
通过real Time/Call从大到小排序,找到对应的与代码相关消耗时间最大的方法可以看出书签数据库初始化消耗CPU时间最长。

3.android的context是指什么,在一个应用程序中有多少Context实例?

这个问题还是很好回答的嘛,首先Context描述的是一个应用程序环境的信息,即上下文,是一个抽象类。
Android面试题详细整理系列(一)_第1张图片

因此总Context实例个数=Service个数+Activity个数+1个A品牌力cationContext个数

4.Jni开发步骤?

1)使用Android Studio开发工具,下载NDK
2)创建含有native方法的Java类
3)通过javah命令生成.h文件
4)在.c文件中实现以上方法
5)编写Android.mk文件
6)用NDK工具编译生成.so文件
7)把.so文件复制到lib下

5.事件冲突解决方案

针对滑动冲突这里给出两种解决方案:外部拦截法,内部拦截法。
1)外部拦截法
情景:一个ViewPager嵌套了一个Listview,一个是左右滑动,一个上下滑动。这个时候我们可以用外部拦截法,来处理冲突。在父容器ViewPager中,重写onInterceptTouchEvent()方法,判断当左右滑动时就拦截事件,上下滑动就不拦截,将事件交由子元素Listview来处理。首先我们需要重写一个ViewPager,叫MyViewPager,然后重写onInterceptTouchEvent()方法。具体代码如下:

public class MyViewPager extends ViewPager {
    private int startX;
    private int startY;
    public MyViewPager(Context context) {
        super(context);
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                startX= (int) ev.getX();
                startY= (int) ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:

                int dX= (int) (ev.getX()-startX);
                int dY= (int) (ev.getY()-startX);
                if(Math.abs(dX)>Math.abs(dY)){//左右滑动
                    return true;
                }else {//上下滑动
                    return false;
                }
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
}

2)内部拦截法
情景:一个ViewPager嵌套了一个ViewPager,两个都是左右滑动。这个时候我们可以用内部拦截法,来处理冲突。即重写子元素的dispatchTouchEvent()方法,并调用getParent().requestDisallowInterceptTouchEvent(true)是父容器不能拦截子元素需要的事件。下面来看具体代码:

public boolean dispatchTouchEvent(MotionEvent event) {
        ...

        switch (action) {
            case MotionEvent.ACTION_MOVE:
                        getParent().requestDisallowInterceptTouchEvent(true);

                break;
            case MotionEvent.ACTION_MOVE:
                if(子元素需要处理此事件)
                            getParent().requestDisallowInterceptTouchEvent(true);

                break;
            case MotionEvent.ACTION_UP: {
                break;
        }
        ...
        return super.dispatchTouchEvent(event);
;
    }

当然,还需要修改父容器的onInterceptTouchEvent()方法,代码如下:

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

            int action=ev.getAction();
            if(action==MotionEvent.ACTION_DOWN){
                return false;
            }else {
                return true;
            }
        }

6.如何查看Activty任务栈结构?

刚听到问题,小哥我一下蒙蔽了,细细一想,有以下解决方案
方法一:通过ActivityManager获取状态
Android提供了ActivityManger来帮助开发者了解运行期间的状态,通过调用getRunningTasks(int)方法,就可以在得到RunningTaskInfo的列表,其代表着当前Android设备正在运行着的Task。从RunningTaskInfo中又可以进一步得到更多的信息。

ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
List runningTaskInfoList =  am.getRunningTasks(10);
for (RunningTaskInfo runningTaskInfo : runningTaskInfoList) {
    log("id: " + runningTaskInfo.id);
    log("description: " + runningTaskInfo.description);
    log("number of activities: " + runningTaskInfo.numActivities);
    log("topActivity: " + runningTaskInfo.topActivity);
    log("baseActivity: " + runningTaskInfo.baseActivity.toString());
}

方法二:手动记录和管理Activities栈
Activity的创建和销毁都会有相应的回调函数:onCreate(),onDestroy()。因此可以自建一个静态全局Stack对象,在onCreate()时候讲当前Activity对象加入到Stack中,而在onDestroy()时把它从Stack中移除。这样我们就随时可以知道当前Activity的详细情况了。

方法三:使用adb shell指令
Android还为开发者提供了adb(Android Debug Bridge),这是非常强大的调试工具。最常用的自然是logcat来显示日志记录。另外一个很强大的指令就是这里要提到的dumpsys。dumpsys还可以添加不同的参数来指示需要输出哪一类Service的信息。对于本文提到的内容,需要查看的就是activity,指令就是:

adb shell dumpsys activity

输入上述指令,就能得到关于设备非常长的一段讯息,单是也能清晰看出它们比较详细的分类

ACTIVITY MANAGER PENDING INTENTS (dumpsys activity intents)
  * PendingIntentRecord{42b05f20 com.android.vending startService}
  ... ... ... ...

ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts)
  Historical broadcasts [foreground]:
  #0: BroadcastRecord{430d2fb8 u-1 android.intent.action.TIME_TICK}
    act=android.intent.action.TIME_TICK flg=0x50000014 (has extras)
    extras: Bundle[{android.intent.extra.ALARM_COUNT=1}]
  ... ... ... ...

ACTIVITY MANAGER CONTENT PROVIDERS (dumpsys activity providers)
  Published single-user content providers (by class):
  * ContentProviderRecord{429d18a8 u0 com.android.phone/.IccProvider}
    proc=ProcessRecord{429765d8 858:com.android.phone/1001}
    singleton=true
    authority=icc
  ... ... ... ...

ACTIVITY MANAGER SERVICES (dumpsys activity services)
  User 0 active services:
  * ServiceRecord{429f8668 u0 com.android.bluetooth/.hid.HidService}
    app=null
    created=-1h44m27s317ms started=false connections=0
  ... ... ... ...

ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
  Stack #0:
    Task id #28
      TaskRecord{43525058 #28 A=com.android.systemui U=0 sz=1}
      Intent { act=com.android.systemui.recent.action.TOGGLE_RECENTS flg=0x10c00000 cmp=com.android.systemui/.recent.RecentsActivity (has extras) }
        Hist #0: ActivityRecord{428d1ae8 u0 com.android.systemui/.recent.RecentsActivity t28}
          Intent { act=com.android.systemui.recent.action.TOGGLE_RECENTS flg=0x10800000 cmp=com.android.systemui/.recent.RecentsActivity bnds=[328,886][656,1176] }
          ProcessRecord{42968230 695:com.android.systemui/u0a12}
  ... ... ... ...

ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)
  Process LRU list (sorted by oom_adj, 28 total, non-act at 3, non-svc at 3):
    PERS #27: sys   F/ /P  trm: 0 605:system/1000 (fixed)
  ... ... ... ...

每一个类别都有一个括号内容,给出了更加详细的指令来查看该类别下更多具体内容。因此再来尝试指令:

db shell dumpsys activity activities

就能看到下边的结果

CTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
  Stack #0:
    Task id #28
    * TaskRecord{43525058 #28 A=com.android.systemui U=0 sz=1}
      ... ... ... ...
      * Hist #0: ActivityRecord{428d1ae8 u0 com.android.systemui/.recent.RecentsActivity t28}
          ... ... ... ...
    Task id #1
    * TaskRecord{429a35f8 #1 A=com.android.launcher U=0 sz=1}
      ... ... ... ...
      * Hist #0: ActivityRecord{429a1760 u0 com.android.launcher/com.android.launcher2.Launcher t1}
          ... ... ... ...

    Running activities (most recent first):
      TaskRecord{43525058 #28 A=com.android.systemui U=0 sz=1}
        Run #1: ActivityRecord{428d1ae8 u0 com.android.systemui/.recent.RecentsActivity t28}
      TaskRecord{429a35f8 #1 A=com.android.launcher U=0 sz=1}
        Run #0: ActivityRecord{429a1760 u0 com.android.launcher/com.android.launcher2.Launcher t1}

    mLastPausedActivity: ActivityRecord{428d1ae8 u0 com.android.systemui/.recent.RecentsActivity t28}

  Stack #1:
    Task id #25
    * TaskRecord{42b0ee20 #25 I=com.iderzheng/.SingleTaskActivity U=0 sz=5}
      numActivities=5 rootWasReset=false userId=0 mTaskType=0 numFullscreen=5 mOnTopOfHome=true
      intent={cmp=com.iderzheng/.SingleTaskActivity}
      realActivity=com.iderzheng/.SingleTaskActivity
      Activities=[ActivityRecord{42a7e160 u0 com.iderzheng/.SingleTaskActivity t25}, ActivityRecord{42bffdf0 u0 com.iderzheng/.StandardActivity t25}, ActivityRecord{42e9e8f8 u0 com.iderzheng/.SingleTopActivity t25}, ActivityRecord{434c2238 u0 com.iderzheng/.StandardActivity t25}, ActivityRecord{4279d2d8 u0 com.iderzheng/.SingleTopActivity t25}]
      askedCompatMode=false
      lastThumbnail=null lastDescription=null
      lastActiveTime=6229735 (inactive for 357s)
      * Hist #4: ActivityRecord{4279d2d8 u0 com.iderzheng/.SingleTopActivity t25}
          packageName=com.iderzheng processName=com.iderzheng
          launchedFromUid=10124 launchedFromPackage=com.iderzheng userId=0
          app=ProcessRecord{4312cbb0 3700:com.iderzheng/u0a124}
          Intent { cmp=com.iderzheng/.SingleTopActivity bnds=[328,580][656,870] }
          frontOfTask=false task=TaskRecord{42b0ee20 #25 I=com.iderzheng/.SingleTaskActivity U=0 sz=5}
          taskAffinity=com.iderzheng
          realActivity=com.iderzheng/.SingleTopActivity
          baseDir=/data/app/com.iderzheng-1.apk
          dataDir=/data/data/com.iderzheng
          stateNotNeeded=false componentSpecified=true mActivityType=0
          compat={320dpi} labelRes=0x7f0a0013 icon=0x7f020057 theme=0x7f0b0000
          config={1.0 310mcc?mnc en_US ldltr sw384dp w384dp h567dp 320dpi nrml port finger -keyb/v/h -nav/h s.7}
          launchFailed=false launchCount=0 lastLaunchTime=-1h40m33s397ms
          haveState=false icicle=null
          state=RESUMED stopped=false delayedResume=false finishing=false
          keysPaused=false inHistory=true visible=true sleeping=false idle=true
          fullscreen=true noDisplay=false immersive=false launchMode=1
          frozenBeforeDestroy=false thumbnailNeeded=false forceNewConfig=false
          mActivityType=APPLICATION_ACTIVITY_TYPE
          thumbHolder: 42b0ee20 bm=null desc=null
          waitingVisible=false nowVisible=true lastVisibleTime=-5m56s862ms
    ... ... ... ...

    Running activities (most recent first):
      TaskRecord{42b0ee20 #25 I=com.iderzheng/.SingleTaskActivity U=0 sz=5}
        Run #7: ActivityRecord{4279d2d8 u0 com.iderzheng/.SingleTopActivity t25}
      TaskRecord{429e9558 #24 A=com.iderzheng U=0 sz=1}
        Run #6: ActivityRecord{429d5408 u0 com.iderzheng/.SingleInstanceActivity t24}
      TaskRecord{42b0ee20 #25 I=com.iderzheng/.SingleTaskActivity U=0 sz=5}
        Run #5: ActivityRecord{434c2238 u0 com.iderzheng/.StandardActivity t25}
        Run #4: ActivityRecord{42e9e8f8 u0 com.iderzheng/.SingleTopActivity t25}
        Run #3: ActivityRecord{42bffdf0 u0 com.iderzheng/.StandardActivity t25}
        Run #2: ActivityRecord{42a7e160 u0 com.iderzheng/.SingleTaskActivity t25}
      TaskRecord{4282e508 #23 A=com.iderzheng U=0 sz=2}
        Run #1: ActivityRecord{429655d8 u0 com.iderzheng/.StandardActivity t23}
        Run #0: ActivityRecord{429564e0 u0 com.iderzheng/.MainActivity t23}

  ... ... ... ...

  Recent tasks:
  ... ... ... ...

整个log显示了当前所有在运行的任务栈,它们的id分别是什么。对于每个Task,也有Activity数量等信息,同时也列出了其中的Activity列表,并且对于每个Activity也有比较详细的描述,比如启动它的Intent的内容。

如果觉得内容过多,只想看看栈的内容,也可以直接跳到”Running activities (most recent first)”那部分,比较简洁而又明了的列出了栈中得Activity列表,就能知道当按下返回键的时候会应该会回到哪个Activity以后是要退出程序。

在写这篇文章时参考了很多同行的博客,在此特别感谢Iden,Vonnie Jade,baidu_mtc的博客,hongdameng的专栏等同行。希望大家共同成长,共同进步。

我是kris,See you next time!!!

http://blog.iderzheng.com/debug-activity-task-stack-with-adb-shell-dumpsys/ 使用adb shell dumpsys检测Android的Activity任务栈(Iden)
http://www.cnblogs.com/yxx123/p/5250101.html Android滑动事件冲突(Vonnie Jade)
http://blog.csdn.net/baidu_mtc/article/details/50396143 ANR检查定位分析工具(baidu_mtc的博客)

http://blog.csdn.net/hongdameng/article/details/42639961# Android子线程创建Handler方法(hongdameng的专栏)

你可能感兴趣的:(Android,Java)