面试题总结(2018.7.26开始,持续更新中)
Android 部分
目录
1. Android中的四大组件有哪些?各自有什么作用?
2. activity的生命周期
- 启动Activity: onCreate()—>onStart()—>onResume(),Activity进入运行状态。
- Activity退居后台: 当前Activity转到新的Activity界面或按Home键回到主屏: onPause()—>onStop(),进入停滞状态。
- Activity返回前台: onRestart()—>onStart()—>onResume(),再次回到运行状态。
- Activity退居后台,且系统内存不足, 系统会杀死这个后台状态的Activity,若再次回到这个Activity,则会走onCreate()–>onStart()—>onResume()
- 锁定屏与解锁屏幕 只会调用onPause(),而不会调用onStop方法,开屏后则调用onResume()
3. Activity销毁但Task如果没有销毁掉,当Activity重启时这个AsyncTask该如何解决?
还是屏幕旋转这个例子,在重建Activity的时候,会回调Activity.onRetainNonConfigurationInstance()
重新传递一个新的对象给AsyncTask,完成引用的更新
4. Asynctask为什么要设置为只能够一次任务
个AsyncTask对象只能被执行一次,即只能调用一次execute;sDefaultExecutor是SerialExecutor的一个实例,而且它是个静态变量。也就是说,一个进程里面所有AsyncTask对象都共享同一个SerialExecutor对象。考虑到线程安全问题,所以只能够执行一次
5. 若Activity已经销毁,此时AsynTask执行完并返回结果,会报异常么?
当一个App旋转时,整个Activity会被销毁和重建。当Activity重启时,AsyncTask中对该Activity的引用是无效的,因此onPostExecute()就不会起作用,若AsynTask正在执行,则会报 view not attached to window manager 异常。同样也是生命周期的问题,在 Activity 的onDestory()方法中调用Asyntask.cancal方法,让二者的生命周期同步
6. 内存不足时,系统会杀死后台的Activity,如果需要进行一些临时状态的保存,在哪个方法进行(activity的状态保存)
Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,不同于 onCreate()、onPause()等生命周期方法,它们并不一定会被触发。当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity,onSaveInstanceState() 会被调用。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。除非该activity是被用户主动销毁的,通常onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。
7. activity的launchMode及其使用场景
- standard: standard模式是默认的启动模式,不用为配置android:launchMode属性即可,当然也可以指定值为standard。standard启动模式,不管有没有已存在的实例,都生成新的实例。
- singleTop: 我们在上面的基础上为指定属性android:launchMode=”singleTop”,系统就会按照singleTop启动模式处理跳转行为。跳转时系统会先在栈结构中寻找是否有一个Activity实例正位于栈顶,如果有则不再生成新的,而是直接使用( 会调用实例的 onNewIntent() )。如果系统发现存在有Activity实例,但不是位于栈顶,重新生成一个实例。 这就是singleTop启动模式,如果发现有对应的Activity实例正位于栈顶,则重复利用,不再生成新的实例。适合接收通知启动的内容显示页面,例如,某个新闻客户端的新闻内容页面或者阅读类App的内容页面。
- singleTask: 如果发现有对应的Activity实例,则使此Activity实例之上的其他Activity实例统统出栈,使此Activity实例成为栈顶对象,显示到幕前。适合作为程序入口点,例如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。
- singleInstance: 这种启动模式比较特殊,因为它会启用一个新的栈结构,将Acitvity放置于这个新的栈结构中,并保证不再有其他Activity实例进入。 闹铃的响铃界面。 你以前设置了一个闹铃:上午6点。在上午5点58分,你启动了闹铃设置界面,并按 Home 键回桌面;在上午5点59分时,你在微信和朋友聊天;在6点时,闹铃响了,并且弹出了一个对话框形式的 Activity(名为 AlarmAlertActivity) 提示你到6点了(这个 Activity 就是以 SingleInstance 加载模式打开的),你按返回键,回到的是微信的聊天界面,这是因为 AlarmAlertActivity 所在的 Task 的栈只有他一个元素, 因此退出之后这个 Task 的栈空了。如果是以 SingleTask 打开 AlarmAlertActivity,那么当闹铃响了的时候,按返回键应该进入闹铃设置界面。
8. 如何把一个应用设置为系统应用
- Android设置是Debug版本,且root,直接将该apk用adb工具push到system/app或system/priv-app
- 如果是非root设备,需要编译后烧写镜像
- 有些权限(如WRITE_SECURE_SETTINGS)不开放给第三方应用,只能在对应设备源码总编译然后作为系统app使用
9. service的生命周期,它的启动方式有什么区别?(Activity启动Service的两种方式)
service 启动方式有两种,一种是通过startService()方式进行启动,另一种是通过bindService()方式进行启动。不同的启动方式他们的生命周期是不一样.
- startService:生命周期和调用者不同.启动后若调用者未调用stopService而直接退出,Service仍会运行。这种方式启动的service,生命周期是这样:调用startService() --> onCreate()--> onStartConmon()--> onDestroy()。这种方式启动的话,需要注意一下几个问题,第一:当我们通过startService被调用以后,多次在调用startService(),onCreate()方法也只会被调用一次,而onStartConmon()会被多次调用,当我们调用stopService()的时候,onDestroy()就会被调用,从而销毁服务。第二:当我们通过startService启动时候,通过intent传值,在onStartConmon()方法中获取值的时候,一定要先判断intent是否为null。
- bindService:生命周期与调用者绑定,调用者一旦退出,Service就会调用unBind->onDestory。这种方式绑定service,生命周期走法:bindService-->onCreate()-->onBind()-->unBind()-->onDestroy() bingservice 这种方式进行启动service好处是更加便利activity中操作service,比如加入service中有几个方法,a,b ,如果要在activity中调用,在需要在activity获取ServiceConnection对象,通过ServiceConnection来获取service中内部类的类对象,然后通过这个类对象就可以调用类中的方法,当然这个类需要继承Binder对象
10. Android两个应用能在同一个任务栈吗?
栈一般以包名命名,两个应用的签名和udid要相同
11. Fragment是什么?你曾经遇到哪些有关Fragment的问题?
- Fragment可以作为Activity界面的一部分组成出现.一个Activity中可以同时出现多个Fragment,并一个Fragment也可以在多个Activity中使用.
- Fragment重叠的问题(重启应用就好了) :首先,Android管理Fragment有两种方式,使用add、hide、show的方式和replace方式,两种方式各有优缺点。
- replace方式 :如果使用这种方式,是可以避免重叠的问题,但是每次replace会把生命周期全部执行一遍,如果在这些生命周期函数 里拉取数据的话,就会不断重复的加载刷新数据,所以我们并不推荐使用这种方式。
- add、hide、show的方式:虽然这种方式避免了可能重复加载刷新数据的问题,但是会出现重叠的问题。
- 产生重叠的原因:当系统内存不足,Fragment 的宿主 Activity 回收的时候,Fragment 的实例并没有随之被回收。Activity 被系统回收时,会主动调用 onSaveInstance() 方法来保存视图层(View Hierarchy),所以当 Activity 通过导航再次被重建时,之前被实例化过的 Fragment 依然会出现在 Activity 中,此时的 FragmentTransaction 中的相当于又再次 add 了 fragment 进去的,hide()和show()方法对之前保存的fragment已经失效了,所以就出现了重叠。
- 解决重叠的方法:
- 方法一:通过注释掉这句话,这样主 Activity 因为种种原因被回收的时候就不会保存之前的 fragment state
@Override protectedvoidonSaveInstanceState(Bundle outState) { //如果用以下这种做法则不保存状态,再次进来的话会显示默认tab //总是执行这句代码来调用父类去保存视图层的状态 //super.onSaveInstanceState(outState); }
- 方法二(推荐使用):重写onAttachFragment,重新让新的Fragment指向了原本未被销毁的fragment,它就是onAttach方法对应的Fragment对象
@Override public void onAttachFragment(Fragment fragment) { if (tab1 == null && fragment instanceof Tab1Fragment) tab1 = fragment; if (tab2 == null && fragment instanceof Tab2Fragment) tab2 = fragment; if (tab3 == null && fragment instanceof Tab3Fragment) tab3 = fragment; if (tab4 == null && fragment instanceof Tab4Fragment) tab4 = fragment; }
- 方法三: 思路同样是阻止系统恢复Fragment state,在FragmentActivity保存所有Fragment状态前把Fragment从FragmentManager中移除掉。
protected void onSaveInstanceState(Bundle outState) { FragmentTransaction transaction = fm.beginTransaction(); transaction.remove(tab1); transaction.remove(tab2); transaction.remove(tab3); transaction.remove(tab4); transaction.commitAllowingStateLoss(); super.onSaveInstanceState(outState); }
- 方法四:在onSaveInstanceState(outState);中去保存fragment,当activity被恢复时,取出这些fragment即可。(个人比较喜欢)
-
@Override protected void onSaveInstanceState(Bundle outState) { L.i("MainActivity onSaveInstanceState"); /*fragment不为空时 保存*/ if (homeFragment != null) { getSupportFragmentManager().putFragment(outState, HOME_FRAGMENT_KEY, homeFragment); } if (dashboardFragment != null) { getSupportFragmentManager().putFragment(outState, DASHBOARD_FRAGMENT_KEY, dashboardFragment); } if (noticeFragment != null) { getSupportFragmentManager().putFragment(outState, NOTICE_FRAGMENT_KEY, noticeFragment); } super.onSaveInstanceState(outState); }
以上便是解决fragment重叠问题的方法if (savedInstanceState != null) { /*获取保存的fragment 没有的话返回null*/ homeFragment = (HomeFragment) getSupportFragmentManager().getFragment(savedInstanceState, HOME_FRAGMENT_KEY); dashboardFragment = (DashboardFragment) getSupportFragmentManager().getFragment(savedInstanceState, DASHBOARD_FRAGMENT_KEY); noticeFragment = (NoticeFragment) getSupportFragmentManager().getFragment(savedInstanceState, NOTICE_FRAGMENT_KEY); addToList(homeFragment); addToList(dashboardFragment); addToList(noticeFragment); } else { initFragment(); }
12. 是否使用过本地广播,和全局广播有什么区别?
本地广播在本应用范围内传播,不用担心隐私数据泄露,不用担心别的应用伪造广播.相比全局广播,本地广播更高效.
13. broadcast的注册方式与区别
- 广播是分为有序广播和无序广播。
- 静态注册:在清单文件(Androidmanifest.xml)中注册, 常见的有监听设备启动,常驻注册不会随程序生命周期改变 ,即使退出了页面,也可以收到广播这种广播一般用于想开机自启动啊等等,由于这种注册的方式的广播是常驻型广播,所以会占用CPU的资源。
- 动态注册:在代码中注册,这种注册方式也叫非常驻型广播,随着程序的结束,也就停止接受广播了(有些广播只能通过动态方式注册,比如时间变化事件、屏幕亮灭事件、电量变更事件,因为这些事件触发频率通常很高,如果允许后台监听,会导致进程频繁创建和销毁,从而影响系统整体性能)。这种注册方式优先级较高。最后需要解绑,否会会内存泄露
14. 为什么Android引入广播机制?
引入广播机制可以方便几大组件的信息和数据交互。
b:程序间互通消息(例如在自己的应用程序内监听系统来电)
c:效率上(参考UDP的广播协议在局域网的方便性)
d:设计模式上(反转控制的一种应用,类似监听者模式)
15. IntentService有何优点?
- IntentService是Service的子类,是一个异步的,会自动停止的服务,很好解决了传统的Service中处理完耗时操作忘记停止并销毁Service的问题
- 生成一个默认的且与线程相互独立的工作线程执行所有发送到onStartCommand()方法的Intent,可以在onHandleIntent()中处理.
- 串行队列,每次只运行一个任务,不存在线程安全问题,所有任务执行完后自动停止服务,不需要自己手动调用stopSelf()来停止.
- 内部保存着一个HandlerThread,looper,与handler等成员变量,维护者自身的消息队列
- 每次后台任务执行完都会尝试关闭自身,但是当且仅当IntentServerce 消息队列中的最后一条执行完成才会真正stop自己
- 会创建独立的worker线程来处理所有的Intent请求;
- 会创建独立的worker线程来处理onHandleIntent()方法实现的代码,无需处理多线程问题;
- 所有请求处理完成后,IntentService会自动停止,无需调用stopSelf()方法停止Service;
- 为Service的onBind()提供默认实现,返回null;
- 为Service的onStartCommand提供默认实现,将请求Intent添加到队列中;
- IntentService不会阻塞UI线程,而普通Serveice会导致ANR异常
- Intentservice若未执行完成上一次的任务,将不会新开一个线程,是等待之前的任务完成后,再执行新的任务,等任务完成后再次调用stopSelf()
16. Android 实现数据存储的几种方式?
- SharedPreference: SharedPreferences是一种轻量级的数据存储机制,他将一些简单的数据类型的数据,包括boolean类型,int类型,float类型,long类型以及String类型的数据,以键值对的形式存储在应用程序的私有Preferences目录(/data/data/<包名>/shared_prefs/)中,这种Preferences机制广泛应用于存储应用程序中的配置信息。
- 内部存储
- 外部存储
- SQLite数据库: 当应用程序需要处理的数据量比较大时,为了更加合理地存储、管理、查询数据,我们往往使用关系数据库来存储数据。Android系统的很多用户数据,如联系人信息,通话记录,短信息等,都是存储在SQLite数据库当中的,所以利用操作SQLite数据库的API可以同样方便的访问和修改这些数据。
- 网络存储:
- 文件存储: 通过Java.io.FileInputStream和java.io.FileOutputStream这两个类来实现对文件的读写,java.io.File类则用来构造一个具体指向某个文件或者文件夹的对象。
- ContentProvider: 主要用于在不同的应用程序之间实现数据共享的功能,不同于sharepreference和文件存储中的两种全局可读写操作模式,内容提供其可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄漏的风险
17. 介绍一下ContentProvider 是如何实现数据共享的
当一个应用程序需要把自己的数据暴露给其他程序使用时,该应用程序就可通过提供ContentProvider来实现;其他应用程序就可通过ContentResolver来操作ContentProvider暴露的数据。 一旦某个应用程序通过ContentProvider暴露了自己的数据操作接口,那么不管该应用程序是否启动,其他应用程序都可以通过该接口来操作该应用程序的内部数据,包括增加数据、删除数据、修改数据、查询数据等。
ContentProvider以某种Uri的形式对外提供数据,允许其他应用访问或修改数据;其他应用程序使用ContentResolver根据Uri去访问操作指定数据。 步骤:
1. 定义自己的ContentProvider类,该类需要继承Android提供的ContentProvider基类。
2. 在AndroidManifest.xml文件中注册个ContentProvider,注册ContenProvider时需要为它绑定一个URL。 例: android:authorities=”com.myit.providers.MyProvider” /> 说明:authorities就相当于为该ContentProvider指定URL。 注册后,其他应用程序就可以通过该Uri来访问MyProvider所暴露的数据了。
3. 接下来,使用ContentResolver操作数据,Context提供了如下方法来获取ContentResolver对象。 一般来说,ContentProvider是单例模式,当多个应用程序通过ContentResolver来操作 ContentProvider提供的数据时,ContentResolver调用的数据操作将会委托给同一个ContentProvider处理。 使用ContentResolver操作数据只需两步:
- 调用Activity的ContentResolver获取ContentResolver对象。
- 根据需要调用ContentResolver的insert()、delete()、update()和query()方法操作数据即可
18. ContentProvider和sql的区别
ContentProvider的主要还是用于数据共享,其可以对Sqlite,SharePreferences,File等进行数据操作用来共享数据。而sql的可以理解为数据库的一门语言,可以使用它完成CRUD等一系列的操作
19. [Android]如何导入已有的外部数据库
我们都知道android系统下数据库应该存放在 /data/data/com.*.*(package name)/ 目录下,所以我们需要做的是把已有的数据库传入那个目录下。操作方法是用FileInputStream读取原数据库,再用FileOutputStream把读取到的东西写入到那个目录。
操作方法:
- 把原数据库包括在项目源码的 res/raw 目录下,然后建立一个DBManager类
- 然后在程序的首个Activity中示例化一个DBManager对象,然后对其执行openDatabase方法就可以完成导入了,可以把一些要对数据库进行的操作写在DBManager类里,然后通过DBManager类的对象调用;也可以在完成导入之后通过一个SQliteDatabase类的对象打开数据库,并执行操作。
20. 如何将打开res aw目录中的数据库文件?
在Android中不能直接打开res aw目录中的数据库文件,而需要在程序第一次启动时将该文件复制到手机内存或SD卡的某个目录中,然后再打开该数据库文件。复制的基本方法是使用getResources().openRawResource方法获得res aw目录中资源的 InputStream对象,然后将该InputStream对象中的数据写入其他的目录中相应文件中。在Android SDK中可以使用SQLiteDatabase.openOrCreateDatabase方法来打开任意目录中的SQLite数据库文件。
21. 一条最长的短信息约占多少byte?
中文70(包括标点),英文160,160个字节。
22. Context与ApplicationContext的区别
Application的Context是一个全局静态变量,SDK的说明是只有当你引用这个context的生命周期超过了当前activity的生命周期,而和整个应用的生命周期挂钩时,才去使用这个application的context。
在android中context可以作很多操作,但是最主要的功能是加载和访问资源。在android中有两种context,一种是 application context,一种是activity context,通常我们在各种类和方法间传递的是activity context。
23. 什么是aar?aar是jar有什么区别?
一、arr:
- Android库项目的二进制归档文件,包含所有资源,class以及res资源文件全部包含。
- 将aar解压(后缀改为.zip,再解压文件)打开后,可以看到每个aar解压后的内容可能不完全一样,但是都会包含AndroidManifest.xml,classes.jar,res,R.txt。
打aar包方法:
- 先把想要打包成sdk的项目做成Android libraries B,不要建成Android project ;
- 然后建立一个新的Android project A 去调用写好的libraries B;
- 运行后,studio就自动把我们的librarys B自动打包成aar包了,这就是我们想要的sdk了(路径:module 下,build/outputs/aar/)
- 如果运行后,没在目录下看到 aar,可以按照下图方式执行,执行成功后,就可以在左侧的:build/outputs/aar/ 下看到对应的 aar 文件了:
作者:viky_lyn
链接:https://www.jianshu.com/p/0a2572a63ed5
來源:简书
Android Studio使用aar方式:
- 拷贝到:libs目录
- build.gradle 配置文件中更改为
repositories {
flatDir {
dirs'libs'
}
}
dependencies {
compile(name:'genius', ext:'aar')
}
二、jar
- 只包含了class文件与清单文件 ,不包含资源文件,如图片等所有res中的文件。
- JAR(Java Archive,Java 归档文件)是与平台无关的文件格式,它允许将许多文件组合成一个压缩文件。
- Jar的优点:安全性、减少下载时间、传输平台扩展、包密封、包版本控制、可移植性。
- 打jar包时,项目里的res文件是用不了的,若想用图片文件,可以将图片文件放进assets文件里面打进jar包再进行调用,但必须注意jar里面assets文件夹里面的文件不能和调用项目里面assets文件夹里面的文件重名。
打jar包方法:
- 网上方法很多也很详细,不再赘述,给个链接:http://blog.csdn.net/u013895206/article/details/52692415
使用jar方式:
*.jar:拷贝到:libs目录,eclipse直接导入即可,AndroidStudio项目中添加:
dependencies {
compile fileTree(include: ['*.jar'], dir:'libs')
}
重新编译一次项目既可完成加载。
23. 如何将SQLite数据库(dictionary.db文件)与apk文件一起发布?
可以将dictionary.db文件复制到Eclipse Android工程中的res aw目录中。所有在res aw目录中的文件不会被压缩,这样可以直接提取该目录中的文件。可以将dictionary.db文件复制到res aw目录中
24. Service和Activity通信
- 通过Binder
- 通过broadcast
25. 如何保证Service在后台不被kill
- Service设置成START_STICKY kill 后会被重启(等待5秒左右),重传Intent,保持与重启前一样
- 通过 startForeground将进程设置为前台进程, 做前台服务,优先级和前台应用一个级别,除非在系统内存非常缺,否则此进程不会被 kill
- 双进程Service: 让2个进程互相保护**,其中一个Service被清理后,另外没被清理的进程可以立即重启进程
- QQ黑科技: 在应用退到后台后,另起一个只有 1 像素的页面停留在桌面上,让自己保持前台状态,保护自己不被后台清理工具杀死
- 在已经root的设备下,修改相应的权限文件,将App伪装成系统级的应用 Android4.0系列的一个漏洞,已经确认可行
- 用C编写守护进程(即子进程) : Android系统中当前进程(Process)fork出来的子进程,被系统认为是两个不同的进程。当父进程被杀死的时候,子进程仍然可以存活,并不受影响。鉴于目前提到的在Android->- Service层做双守护都会失败,我们可以fork出c进程,多进程守护。死循环在那检查是否还存在,具体的思路如下(Android5.0以上的版本不可行)
- 用C编写守护进程(即子进程),守护进程做的事情就是循环检查目标进程是否存在,不存在则启动它。
- 在NDK环境中将1中编写的C代码编译打包成可执行文件(BUILD_EXECUTABLE)。主进程启动时将守护进程放入私有目录下,赋予可执行权限,启动它即可。
- 联系厂商,加入白名单
26. Android中如何获得手机的唯一标示.
- 首先尝试读取IMEI、Mac地址、CPU号等物理信息(有不少工具可以修改IMEI);
- 如果均失败,可以自己生成UUID然后保存到文件(文件也可能被篡改或删除)
27. Android应用中验证码登录都有哪些实现方案
验证码应该只有两种获取方式: 从服务器端获取图片, 通过短信服务,将验证码发送给客户端这两种
28. 为什么要设计Bundle而不是直接使用Map?
- Bundle父类 BaseBundle内部是由ArrayMap实现的,ArrayMap的内部实现是两个数组,一个int数组是存储对象数据对应下标,一个对象数组保存key和value,内部使用二分法对key进行排序,所以在添加、删除、查找数据的时候,都会使用二分法查找,只适合于小数据量操作,如果在数据量比较大的情况下,那么它的性能将退化。而HashMap内部则是数组+链表结构,所以在数据量较少的时候,HashMap的Entry Array比ArrayMap占用更多的内存。因为使用Bundle的场景大多数为小数据量,我没见过在两个Activity之间传递10个以上数据的场景,所以相比之下,在这种情况下使用ArrayMap保存数据,在操作速度和内存占用上都具有优势,因此使用Bundle来传递数据,可以保证更快的速度和更少的内存占用。
- 另外一个原因,则是在Android中如果使用Intent来携带数据的话,需要数据是基本类型或者是可序列化类型,HashMap使用Serializable进行序列化,而Bundle则是使用Parcelable进行序列化。而在Android平台中,更推荐使用Parcelable实现序列化,虽然写法复杂,但是开销更小,所以为了更加快速的进行数据的序列化和反序列化,系统封装了Bundle类,方便我们进行数据的传输。
29. Android中有哪几种解析xml的类,官方推荐的是哪种?他们的原理是什么,区别在哪
- DOM,SAX,Pull解析。
- SAX解析器的优点是解析速度快,占用内存少,基于事件驱动的;单地说就是对文档进行顺序扫描,当扫描到文档(document)开始与结束、元素(element)开始与结束、文档(document)结束等地方时通知事件处理函数,由事件处理函数做相应动作,然后继续同样的扫描,直至文档结束。
- DOM在内存中以树形结构存放,因此检索和更新效率会更高。先把xml文档都读到内存中,然后再用DOM API来访问树形结构,并获取数据。但是对于特别大的文档,解析和加载整个文档将会很耗资源,不适合移动端;
- PULL解析器的运行方式和SAX类似,都是基于事件的模式,PULL解析器小巧轻便,解析速度快,简单易用,非常适合在Android移动设备中使用,Android系统内部在解析各种XML时也是用PULL解析器。我们可以调用它的next()方法,来获取下一个解析事件(就是开始文档,结束文档,开始标签,结束标签),当处于某个元素时可以调用XmlPullParser的getAttributte()方法来获取属性的值,也可调用它的nextText()获取本节点的值。
30. Android 五种布局的性能对比?
31. LinearLayout和RelativeLayout性能对比
- RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,也会调用子View2次onMeasure
- RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin。
- 在不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是RelativeLayout。
- 最后再思考一下文章开头那个矛盾的问题,为什么Google给开发者默认新建了个RelativeLayout,而自己却在DecorView中用了个LinearLayout。因为DecorView的层级深度是已知而且固定的,上面一个标题栏,下面一个内容栏。采用RelativeLayout并不会降低层级深度,所以此时在根节点上用LinearLayout是效率最高的。而之所以给开发者默认新建了个RelativeLayout是希望开发者能采用尽量少的View层级来表达布局以实现性能最优,因为复杂的View嵌套对性能的影响会更大一些。
32. dp, dip, dpi, px, sp是什么意思以及他们的换算公式?layout-sw400dp, layout-h400dp分别代表什么意思
- dip(dp): device independent pixels(设备独立像素) ,就是把屏幕的高分成480分,宽分成320分。比如你做一条160dip的横线,无论你在320还480的模拟器上,都是一半屏的长度。
- dpi:dot per inch ,dpi=(√(横向分辨率^2+纵向分辨率^2))/屏幕尺寸)
- px:pixel
- sp:scaled pixels(放大像素),主要用于字体显示。
33. 布局优化
- 避免OverDraw过渡绘制
- 优化布局层级
- 避免嵌套过多无用布局
- 当我们在画布局的时候,如果能实现相同的功能,优先考虑相对布局,然后在考虑别的布局,不要用绝对布局。
- 使用
<include />
标签把复杂的界面需要抽取出来 - 使用
<merge />
标签,因为它在优化UI结构时起到很重要的作用。目的是通过删减多余或者额外的层级,从而优化整个Android Layout的结构。核心功能就是减少冗余的层次从而达到优化UI的目的! - ViewStub 是一个隐藏的,不占用内存空间的视图对象,它可以在运行时延迟加载布局资源文件。
34. mipmap文件夹和drawable文件夹的区别
- 把图片放到mipmaps可以提高系统渲染图片的速度,提高图片质量,减少GPU压力。
- icon图标放mipmap
- 普通图片,xml自定义图片,.9图片放drawable
35. listview 的优化优化方式
-
重用convertView: 通过复用convertview来减少不必要的view的创建,另外Inflate操作会把xml文件实例化成相应的View实例,属于IO操作,是耗时操作。
-
减少findViewById()操作: 将xml文件中的元素封装成viewholder静态类,通过convertview的setTag和getTag方法将view与相应的holder对象绑定在一起,避免不必要的findviewbyid操作
-
避免在 getView 方法中做耗时的操作: 例如加载本地 Image 需要载入内存以及解析 Bitmap ,都是比较耗时的操作,如果用户快速滑动listview,会因为getview逻辑过于复杂耗时而造成滑动卡顿现象。用户滑动时候不要加载图片,待滑动完成再加载,可以使用这个第三方库glide
-
Item的布局层次结构尽量简单,避免布局太深或者不必要的重绘
-
尽量能保证 Adapter 的 hasStableIds() 返回 true 这样在 notifyDataSetChanged() 的时候,如果item内容并没有变化,ListView 将不会重新绘制这个 View,达到优化的目的
-
在一些场景中,ScollView内会包含多个ListView,可以把listview的高度写死固定下来。 由于ScollView在快速滑动过程中需要大量计算每一个listview的高度,阻塞了UI线程导致卡顿现象出现,如果我们每一个item的高度都是均匀的,可以通过计算把listview的高度确定下来,避免卡顿现象出现
-
使用 RecycleView 代替listview: 每个item内容的变动,listview都需要去调用notifyDataSetChanged来更新全部的item,太浪费性能了。RecycleView可以实现当个item的局部刷新,并且引入了增加和删除的动态效果,在性能上和定制上都有很大的改善
-
ListView 中元素避免半透明: 半透明绘制需要大量乘法计算,在滑动时不停重绘会造成大量的计算,在比较差的机子上会比较卡。 在设计上能不半透明就不不半透明。实在要弄就把在滑动的时候把半透明设置成不透明,滑动完再重新设置成半透明。
-
尽量开启硬件加速: 硬件加速提升巨大,避免使用一些不支持的函数导致含泪关闭某个地方的硬件加速。当然这一条不只是对 ListView。
36. 如何实现一个局部更新的ListView
- 结合Adapter的hasStableIds()以及getItemId(): 局部刷新的实质是找到那些数据发生改变Item,并且重新调用它的一次getView()方法。设置Adapter的hasStableIds()返回false,代表子元素持有不稳定的ID。如果hasStableIds()返回了false,每次调用notifyDataSetChanged()方法adapter就会判断getItemId()方法的返回值,如果该返回值发生了变化,即某个Item发生了变化,那么就只调用发生变化的那些Item的getView()方法,从而达到局部刷新的效果。
- 直接找到指定位置的holder:
- private void updateItem(int position) {
View view = mListview.getChildAt(position);
MyAdapter.ViewHolder holder = (MyAdapter.ViewHolder)view.getTag();
holder.textView.setText("This is new");
//记得更新list数据源中position位置的数据,避免滑动后局部刷新失效
}
37. 如何实现ListView多种布局
重写getViewTypeCount() 和getItemViewType(int position)这两个方法:
38. ViewHolder为什么要被声明成静态内部类
这个是考静态内部类和非静态内部类的主要区别之一。非静态内部类会隐式持有外部类的引用,就像大家经常将自定义的adapter在Activity类里,然后在adapter类里面是可以随意调用外部activity的方法的。当你将内部类定义为static时,你就调用不了外部类的实例方法了,因为这时候静态内部类是不持有外部类的引用的。声明ViewHolder静态内部类,可以将ViewHolder和外部类解引用。大家会说一般ViewHolder都很简单,不定义为static也没事吧。确实如此,但是如果你将它定义为static的,说明你懂这些含义。万一有一天你在这个ViewHolder加入一些复杂逻辑,做了一些耗时工作,那么如果ViewHolder是非静态内部类的话,就很容易出现内存泄露。如果是静态的话,你就不能直接引用外部类,迫使你关注如何避免相互引用。 所以将 ViewHolder内部类 定义为静态的,是一种好习惯
39. 有哪些进程通信的方式?(Android中几种跨进通讯的方式)
- Bundle/Intent传递数据:可传递基本类型,String,实现了Serializable或Parcellable接口的数据结构。Serializable是Java的序列化方法,Parcellable是Android的序列化方法,前者代码量少(仅一句),但I/O开销较大,一般用于输出到磁盘或网卡;后者实现代码多,效率高,一般用户内存间序列化和反序列化传输。
-
Messenger:Messenger是基于AIDL实现的,服务端(被动方)提供一个Service来处理客户端(主动方)连接,维护一个Handler来创建Messenger,在onBind时返回Messenger的binder。
双方用Messenger来发送数据,用Handler来处理数据。Messenger处理数据依靠Handler,所以是串行的,也就是说,Handler接到多个message时,就要排队依次处理。 - AIDL:AIDL通过定义服务端暴露的接口,以提供给客户端来调用,AIDL使服务器可以并行处理,而Messenger封装了AIDL之后只能串行运行,所以Messenger一般用作消息传递。通过编写aidl文件来设计想要暴露的接口,编译后会自动生成响应的java文件,服务器将接口的具体实现写在Stub中,用iBinder对象传递给客户端,客户端bindService的时候,用asInterface的形式将iBinder还原成接口,再调用其中的方法。
- ContentProvider:底层也是Binder实现,主要用来为其他APP提供数据,可以说天生就是为进程通信而生的。自己实现一个ContentProvider需要实现6个方法,其中onCreate是主线程中回调的,其他方法是运行在Binder之中的。自定义的ContentProvider注册时要提供authorities属性,应用需要访问的时候将属性包装成Uri.parse("content://authorities")。还可以设置permission,readPermission,writePermission来设置权限。 ContentProvider有query,delete,insert等方法,看起来貌似是一个数据库管理类,但其实可以用文件,内存数据等等一切来充当数据源,query返回的是一个Cursor,可以自定义继承AbstractCursor的类来实现。
- BroadcastReceiver
这里再对比总结一下:
- 只有允许不同应用的客户端用 IPC 方式调用远程方法,并且想要在服务中处理多线程时,才有必要使用
AIDL
- 如果需要调用远程方法,但不需要处理并发 IPC,就应该通过实现一个
Binder
创建接口 - 如果您想执行 IPC,但只是传递数据,不涉及方法调用,也不需要高并发,就使用
Messenger
来实现接口 - 如果需要处理一对多的进程间数据共享(主要是数据的 CRUD),就使用
ContentProvider
- 如果要实现一对多的并发实时通信,就使用
Socket
40. AIDL
- AIDL全程是Android Interface Definition Language,用于生成两个进程之间通信(IPC)的代码.
- AIDL 的设计思想:代理
41. Binde机制的简单说明
42. Handler机制的原理
- Handler通过调用sendmessage方法把消息放在消息队列MessageQueue中,Looper负责把消息从消息队列中取出来,重新再交给Handler进行处理,三者形成一个循环
- 通过构建一个消息队列,把所有的Message进行统一的管理,当Message不用了,并不作为垃圾回收,而是放入消息队列中,供下次handler创建消息时候使用,提高了消息对象的复用,减少系统垃圾回收的次数
- 每一个线程,都会单独对应的一个looper,这个looper通过ThreadLocal来创建,保证每个线程只创建一个looper,looper初始化后就会调用looper.loop创建一个MessageQueue,这个方法在UI线程初始化的时候就会完成,我们不需要手动创建
43. Android中的动画有哪些?
- 逐帧动画(Drawable Animation): 加载一系列Drawable资源来创建动画,简单来说就是播放一系列的图片来实现动画效果,可以自定义每张图片的持续时间
- 补间动画(Tween Animation): Tween可以对View对象实现一系列简单的动画效果,比如位移,缩放,旋转,透明度等等。但是它并不会改变View属性的值,只是改变了View的绘制的位置,比如,一个按钮在动画过后,不在原来的位置,但是触发点击事件的仍然是原来的坐标。
- 属性动画(Property Animation): 动画的对象除了传统的View对象,还可以是Object对象,动画结束后,Object对象的属性值被实实在在的改变了
44. Android动画原理
Animation框架定义了透明度,旋转,缩放和位移几种常见的动画,而且控制的是整个View,实现原理是每次绘制视图时View所在的ViewGroup中的drawChild函数获取该View的Animation的Transformation值,然后调用canvas.concat(transformToApply.getMatrix()),通过矩阵运算完成动画帧,如果动画没有完成,继续调用invalidate()函数,启动下次绘制来驱动动画,动画过程中的帧之间间隙时间是绘制函数所消耗的时间,可能会导致动画消耗比较多的CPU资源,最重要的是,动画改变的只是显示,并不能相应事件
45. Android属性动画特性
如果你的需求中只需要对View进行移动、缩放、旋转和淡入淡出操作,那么补间动画确实已经足够健全了。但是很显然,这些功能是不足以覆盖所有的场景的,一旦我们的需求超出了移动、缩放、旋转和淡入淡出这四种对View的操作,那么补间动画就不能再帮我们忙了,也就是说它在功能和可扩展方面都有相当大的局限性,那么下面我们就来看看补间动画所不能胜任的场景。
注意上面我在介绍补间动画的时候都有使用“对View进行操作”这样的描述,没错,补间动画是只能够作用在View上的。也就是说,我们可以对一个Button、TextView、甚至是LinearLayout、或者其它任何继承自View的组件进行动画操作,但是如果我们想要对一个非View的对象进行动画操作,抱歉,补间动画就帮不上忙了。可能有的朋友会感到不能理解,我怎么会需要对一个非View的对象进行动画操作呢?这里我举一个简单的例子,比如说我们有一个自定义的View,在这个View当中有一个Point对象用于管理坐标,然后在onDraw()方法当中就是根据这个Point对象的坐标值来进行绘制的。也就是说,如果我们可以对Point对象进行动画操作,那么整个自定义View的动画效果就有了。显然,补间动画是不具备这个功能的,这是它的第一个缺陷。
然后补间动画还有一个缺陷,就是它只能够实现移动、缩放、旋转和淡入淡出这四种动画操作,那如果我们希望可以对View的背景色进行动态地改变呢?很遗憾,我们只能靠自己去实现了。说白了,之前的补间动画机制就是使用硬编码的方式来完成的,功能限定死就是这些,基本上没有任何扩展性可言。
最后,补间动画还有一个致命的缺陷,就是它只是改变了View的显示效果而已,而不会真正去改变View的属性。什么意思呢?比如说,现在屏幕的左上角有一个按钮,然后我们通过补间动画将它移动到了屏幕的右下角,现在你可以去尝试点击一下这个按钮,点击事件是绝对不会触发的,因为实际上这个按钮还是停留在屏幕的左上角,只不过补间动画将这个按钮绘制到了屏幕的右下角而已。
46. SurfaceView和View的区别
- 在一般的情况下,应用程序的View都是在相同的GUI线程中绘制的。这个主应用程序线程同时也用来处理所有的用户交互(如:按钮单击或者文本输入)。SurfaceView封装了一个Surface对象,而不是Canvas。这一点很重要,因为Surface可以使用后台线程绘制。对于那些资源敏感的操作,或者那些要求快速更新或者高速帧率的地方(例如,使用3D图形,创建游戏,或者实时预览摄像头),这一点特别有用。 但是独立于GUI线程进行绘图也是有坏处的,代价便是额外的内存消耗。所以,虽然它是创建定制的View的有效方式(有时甚至是必须的),但是使用SurfaceView的时候仍然要保持谨慎。
- SurfaceView是View的子类,采用了双缓存技术在,新的工作线程刷新界面,速度比View快。存在事件同步问题,SurfaceView自身有GLSurfaceView和VideoSurfaceView,GL、视频播放、Camera摄像头都采用SurfaceView
- View必须在UI主线程更新,速度慢,如果更新耗时,UI主线程阻塞,造成ANR
47. 介绍下自定义view的基本流程(自定义过 view 吗?它的步骤是什么?说说你自定义 view 过程中出现的问题,以及是如何解决的?)
- 明确需求,确定你想实现的效果
- 确定是使用组合控件的形式还是全新自定义的形式,组合控件即使用多个系统控件来合成一个新控件,你比如titilebar,这种形式相对简单,参考
- 如果是完全自定义一个view的话,你首先需要考虑继承哪个类,是View呢,还是ImageView等子类。
- 根据需要去复写View#onDraw、View#onMeasure、View#onLayout方法
- 根据需要去复写dispatchTouchEvent、onTouchEvent方法
- 根据需要为你的自定义view提供自定义属性,即编写attr.xml,然后在代码中通过TypedArray等类获取到自定义属性值
- 需要处理滑动冲突、像素转换等问题
- 组合控件。这种自定义控件不需要我们自己绘制,而是使用原生控件组合成的新控件。如标题栏。
- 继承原有的控件。这种自定义控件在原生控件提供的方法外,可以自己添加一些方法。如制作圆角,圆形图片。
- 完全自定义控件:这个View上所展现的内容全部都是我们自己绘制出来的。比如说制作水波纹进度条。
- View的绘制流程:OnMeasure()——>OnLayout()——>OnDraw() 第一步:OnMeasure():测量视图大小。从顶层父View到子View递归调用measure方法,measure方法又回调OnMeasure。第二步:OnLayout():确定View位置,进行页面布局。从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。第三步:OnDraw():绘制视图。ViewRoot创建一个Canvas对象,然后调用OnDraw()。六个步骤:①、绘制视图的背景;②、保存画布的图层(Layer);③、绘制View的内容;④、绘制View子视图,如果没有就不用;
⑤、还原图层(Layer);⑥、绘制滚动条。
48. view的绘制流程
measure()方法,layout(),draw()三个方法主要存放了一些标识符,来判断每个View是否需要再重新测量,布局或者绘制,主要的绘制过程还是在onMeasure,onLayout,onDraw这个三个方法中
1.onMesarue() 为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性: mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的。
2.onLayout() 为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。
3. onDraw() 开始绘制图像,绘制的流程如下
- 首先绘制该View的背景
- 绘制画布图层
- 调用onDraw()方法绘制视图本身 (每个View都需要重载该方法,ViewGroup不需要实现该方法)
- 如果该View是ViewGroup,调用dispatchDraw ()方法绘制子视图
- 还原图层
- 绘制滚动条
49. 自定义View执行invalidate()方法,为什么有时候不会回调onDraw()
- 自定义一个view时,重写onDraw。调用view.invalidate(),会触发onDraw和computeScroll()。前提是该view被附加在当前窗口
view.postInvalidate(); //是在非UI线程上调用的
- 自定义一个ViewGroup,重写onDraw。onDraw可能不会被调用,原因是需要先设置一个背景(颜色或图)。表示这个group有东西需要绘制了,才会触发draw,之后是onDraw。因此,一般直接重写dispatchDraw来绘制viewGroup.自定义一个ViewGroup,dispatchDraw会调用drawChild.
50. view,viewgroup事件分发(谈谈touch事件的传递流程,自定义View的事件分发机制)
- 事件分发的顺序:事件传递为Activity->PhoneWindow->DecorView->ViewGroup->View
1. 所有Touch事件都被封装成了MotionEvent对象,包括Touch的位置、时间、历史记录以及第几个手指(多指触摸)等。
2. 事件类型分为ACTION_DOWN, ACTION_UP, ACTION_MOVE, ACTION_POINTER_DOWN, ACTION_POINTER_UP, ACTION_CANCEL,每个事件都是以ACTION_DOWN开始ACTION_UP结束。
3. 对事件的处理包括三类,分别为传递——dispatchTouchEvent()
函数、拦截——onInterceptTouchEvent()
函数、消费——onTouchEvent()
函数和OnTouchListener()
简单来说:
-
事件从Activity.dispatchTouchEvent()开始传递,只要没有被停止或拦截,从最上层的View(ViewGroup)开始一直往下(子View)传递。子View可以通过onTouchEvent()对事件进行处理。
-
事件由父View(ViewGroup)传递给子View,ViewGroup可以通过onInterceptTouchEvent()对事件做拦截,停止其往下传递。
-
如果事件从上往下传递过程中一直没有被停止,且最底层子View没有消费事件,事件会反向往上传递,这时父View(ViewGroup)可以进行消费,如果还是没有被消费的话,最后会到Activity的onTouchEvent()函数。
-
如果View没有对ACTION_DOWN进行消费,之后的其他事件不会传递过来。
-
OnTouchListener优先于onTouchEvent()对事件进行消费。
上面的消费即表示相应函数返回值为true。
51. View中setOnTouchListener中的onTouch,onTouchEvent,onClick的执行顺序
- view在执行dispatchTouchEvent的时候先要去判断该view是否有touchListener和执行TouchListener的onTouch方法的返回值。
- TouchListener的onTouch方法返回false,事件继续传递在dispatch方法中调用了同级的onTouchEvent方法
- 然后在onTouchEvent方法中的UP事件中判断是否注册有onClickListener,有就执行onclick方法。
- onTouch>onTouchEvent>onClick
52. Android下滑冲突的常见解决思路
- 相关的滑动组件 重写onInterceptTouchEvent,然后判断根据xy值,来决定是否要拦截当前操作
- onInterceptTouchEvent中ACTION_DOWN做标记,不拦截;在ACTION_MOVE中作条件判断,拦截。然后交给onTouchEvent事件
- 内部拦截,parent.requestDisallowInterceptTouchEvent
53. 对Bitmap的理解,以及什么时候该bitmap.recycle()
Bitmap是android中经常使用的一个类,它代表了一个图片资源。 Bitmap消耗内存很严重,如果不注意优化代码,经常会出现OOM问题,优化方式通常有这么几种: 1. 使用缓存; 2. 压缩图片; 3. 及时回收;
至于什么时候需要手动调用recycle,这就看具体场景了,原则是当我们不再使用Bitmap时,需要回收。另外,我们需要注意,2.3之前Bitmap对象与像素数据是分开存放的,Bitmap对象存在Java Heap中而像素数据存放在Native Memory中,这时很有必要调用recycle回收内存。但是2.3之后,Bitmap对象和像素数据都是存在Heap中,GC可以回收其内存。
54. 什么时候会用到反射?
JAVA反射机制是在#运行时#,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。 Java反射机制主要提供了以下功能: a)在运行时判断任意一个对象所属的类; b)在运行时构造任意一个类的对象; c)在运行时判断任意一个类所具有的成员变量和方法; d)在运行时调用任意一个对象的方法;生成动态代理。
55. NDK是什么?
- NDK是一些列工具的集合,
- NDK提供了一系列的工具,帮助开发者迅速的开发C/C++的动态库,并能自动将so和java 应用打成apk包。
- NDK集成了交叉编译器,并提供了相应的mk文件和隔离cpu、平台等的差异,开发人员只需简单的修改mk文件就可以创建出so
56. Android ndk主要在哪些场景下使用?有啥坑?
- 加密
- 音视频解码
- 图像操作
- 安全相关,比如hookt
- 增量更新
- 游戏开发
遇到的坑
- UnsatisfiedLinkError基本介绍 : 全名 java.lang.UnsatisfiedLinkError,官方解释 Throw if the java Virtural Machine cannot find an appropriate native-language definition of method declared native意思就是JVM找不到native method的native实现!抛出这异常,肯定是你加载SO的姿势不对!
- 错误场景分析 1、没有SO
code System.loadLibrary(Bugly);
libs 空
运行设备 Android ARM设备
运行结果 Crash!java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/com.tencent.bugly.demo-1/base.apk”],nativeLibraryDirectories=[/vendor/lib, /systemb]]] couldn’t find “libBugly.so”
原因分析 apk安装时,系统会把apk中libs目录下armeabi的SO拷贝到应用的私有目录下。所以libs里没有放入SO,运行时肯定找不到SO。
修复方式 添加SO:libs\armeabi\libBugly.so或加载代码注释掉://System.loadLibrary(Bugly) ;
2、没有X86的SO,在X86的设备上不能
code System.loadLibrary(Bugly);
libs libs\armeabi\libBugly.so
运行设备 Android X86设备
运行结果 Crash!java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/com.tencent.bugly.demo-1/base.apk”],nativeLibraryDirectories=[/vendor/lib, /systemb]]] couldn’t find “libBugly.so”
原因分析 apk安装时,x86设备上系统会把apk中libs目录下x86的SO,拷贝到应用的私有目录下。虽然libs下有armeabi的SO,但没有放入x86的SO,运行时还是找不到libbugly.so。
修复方式 添加SO:libs\x86\libBugly.so或加载代码注释掉://System.loadLibrary(Bugly) ;
3、armeabi-v7a下没有放入libBugly2.so
code if(getArch().contain(“arm”)){//只在arm下加载System.loadLibrary(Bugly) ;System.loadLibrary(Bugly2);
}
libs libs\armeabi\libBugly.solibs\armeabi\libBugly2.solibs\armeabi-v7a\libBugly.so
运行设备 Android ARMv7设备
运行结果 Crash!java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/com.tencent.bugly.demo-1/base.apk”],nativeLibraryDirectories=[/vendor/lib, /systemb]]] couldn’t find “libBugly2.so”
原因分析 apk安装时,系统会把apk中libs目录下armeabi-v7a整个目录下的SO拷贝到应用的私有目录下。因为armeabi-v7a下没有放入libBugly2.so,运行时找不到libBugly2.so。不同的工具兼容的CPU架构不一致,就容易出这个错误了!例如:
libBugly.so提供armeabi、armeabi-v7a、x86三种。
但其它产品可能只提供了armeabi。
如果把这些so都直接拷贝进apk,就会因为上述的原因直接crash,会误以为该Crash是因为不同产品的so不能兼容导致的!
修复方式 添加SO:libs\armeabi-v7a\libBugly2.so或直接删除armeabi-v7a目录,arm设备上系统会自动选择armeabi
4、apk安装时,系统把armeabi下的libBugly.so放入应用的私有目录中了
java.lang.UnsatisfiedLinkError中couldn’t find “XX.so”的占比非常高,上面提的三个场景都是这种错误!
但你见过下面这种错误吗?
java.lang.UnsatisfiedLinkError:dlopen failed: “**/*/arm/*.so” has unexpected e_machine: 3
code if(getArch().contain(“arm”)){//只在arm下加载System.loadLibrary(Bugly) ;}
libs libs\armeabi\libBugly.so 坑爹实习生放入了x86编译的libBugly.so(同名很容易出错)
运行设备 Android ARM设备
运行结果 Crash!java.lang.UnsatisfiedLinkError: dlopen failed: “/data/app/com.tencent.bugly.crashreport.demo-2/lib/arm/libBugly.so” has unexpected e_machine: 3
原因分析 apk安装时,系统把armeabi下的libBugly.so放入应用的私有目录中了!但这个libBugly.so不是arm的,而是x86编译的libBugly.so运行时,系统检察ELF文件中的e_machine字段的值,跟arm的不匹配,就会抛出这个异常了!
5 java.lang.UnsatisfiedLinkError:No implementation found for XXX
说是要建立跟c/cpp写的代码一样的包名和java文件
57. JNI (或提问Java中如何引用本地语言)
- 使用JNI的流程?
- JAVA中声明native 方法如private native String printJNI(String inputStr);
- 使用javah工具生成.h头文件这时候头文件中就会自动生成对应的函数JNIEXPORT jstring JNICALL Java_com_wenming_HelloWorld_printJNI
- 实现JNI原生函数源文件,新建HelloWorld.c文件,对刚才自动生成的函数进行具体的逻辑书写,例如返回一个java叫做HelloWorld的字符串等
- 编译生成动态链接so文件**
- Java中调用Sysytem.load方法把刚才的so库加载进来,就可以调用native方法了
-
如何通过JNI传递String对象
constchar* str;
str = env->GetStringUTFChars(prompt,false);
赋予值char* tmpstr ="return string succeeded";
jstring rtstr = env->NewStringUTF(tmpstr);
58. 移动端获取网络数据优化的几个点(网络优化)
- 连接复用 :
节省连接建立时间,如开启 keep-alive。
对于 Android 来说默认情况下 HttpURLConnection 和 HttpClient 都开启了 keep-alive。只是 2.2 之前 HttpURLConnection 存在影响连接池的 Bug,具体可见:Android HttpURLConnection 及 HttpClient 选择 - 请求合并:
即将多个请求合并为一个进行请求,比较常见的就是网页中的 CSS Image Sprites。如果某个页面内请求过多,也可以考虑做一定的请求合并。 - 减少请求数据的大小:
对于post请求,body可以做gzip压缩的,header也可以作数据压缩(不过只支持http 2.0)。 - 返回的数据的body也可以作gzip压缩,body数据体积可以缩小到原来的30%左右。(也可以考虑压缩返回的json数据的key数据的体积,尤其是针对返回数据格式变化不大的情况,支付宝聊天返回的数据用到了)
- 根据用户的当前的网络质量来判断下载什么质量的图片(电商用的比较多)。
59. 如何设计一个良好的网络层?
在HttpUrlConnection基础上封装了常用的方法,get、post、上传、下载(包括断点下载),包括请求成功、失败、请求中、处理成功以及网络问题等封装,利用接口回调或者广播与UI交互,关于网络请求结果的缓存,我是单独处理的,比如像图片缓存用了LruCache以及File.并没有放在网络层;
59. 如何防止重复发送网络请求
点击activity上的一个按钮,发送网络请求,在网络比较慢的情况下,用户可能会继续去点击按钮,这个时候,发送其他无谓的请求,不知道大家是怎么处理这类问题来拦截?
HTTP header中加入max-age,这样某个固定的时间内都将返回empty body,当然这个方法是死的,把时间完全限制了,这个方法回掉也会同样要执行多次。
还有个晕招,就是直接设置按钮的clickable为false,或者使用progressbar,类似于楼主的方法,比如点赞的场景。
使用Map的话,在回掉的时候,还是需要回收HashMap的,维护Map还不如只维护一个boolean呢。
Volley中如果开了缓存的话, 相同的请求同时只会有一个去真正的请求, 后续都走缓存, 虽然不会请求多次, 但是回调是会执行多次的, 和这个需求不match
60. 如何实现wap联网
https://www.cnblogs.com/navy-wang/p/3281953.html
61. 如何调试Android应用程序
- Debug
- Log
62. Android中常用的测试工具?
- 内存分析:mat,ddms,leakcanary
- 静态分析:find bugs
- 压力测试:monkey
- 自动化测试:UIAutomatorMonkeyRunner,Rubotium
63. 内存泄漏及其管理
- 资源对象没有关闭造成,如查询数据库没有关闭游标
- 构造Adapter时,没有使用缓存ConvertView
- Bitmap对象在不使用时调用recycle()释放内存
- context逃逸问题
- 注册没有取消,如动态注册广播在Activity销毁前没有unregisterReceiver
- 集合对象未清理,如无用时没有释放对象的引用
- 在Activity中使用非静态的内部类,并开启一个长时间运行的线程,因为内部类持有Activity的引用,会导致Activity本来可以被gc时却长期得不到回收
64. 哪些情况下发生OOM
-
类的静态变量持有大数据对象 静态变量长期维持到大数据对象的引用,阻止垃圾回收。
-
非静态内部类存在静态实例 非静态内部类会维持一个到外部类实例的引用,如果非静态内部类的实例是静态的,就会间接长期维持着外部类的引用,阻止被回收掉。
-
资源对象未关闭 资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们, 以便它们的缓冲及时回收内存。它们的缓冲不仅存在于java虚拟机内,还存在于java虚拟机外。 如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。 解决办法: 比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭), 如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。 因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null. 在我们的程序退出时一定要确保我们的资源性对象已经关闭。 程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小, 对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险,记得try catch后,在finally方法中关闭连接
-
注册/解注册未成对使用引起的内存泄漏:注册广播接受器、EventBus等,记得解绑。
-
集合对象没有及时清理引起的内存泄漏:通常会把一些对象装入到集合中,当不使用的时候一定要记得及时清理集合,让相关对象不再被引用。
-
单例模式引起的内存泄漏:解决:Context是ApplicationContext,由于ApplicationContext的生命周期是和app一致的,不会导致内存泄漏
-
Handler内存泄漏 Handler作为内部类存在于Activity中,但是Handler生命周期与Activity生命周期往往并不是相同的,比如当Handler对象有Message在排队,则无法释放,进而导致本该释放的Acitivity也没有办法进行回收。 解决办法:
- 声明handler为static类,这样内部类就不再持有外部类的引用了,就不会阻塞Activity的释放
- 如果内部类实在需要用到外部类的对象,可在其内部声明一个弱引用引用外部类
一些不良代码习惯 有些代码并不造成内存泄露,但是他们的资源没有得到重用,频繁的申请内存和销毁内存,消耗CPU资源的同时,也引起内存抖动 解决方案 如果需要频繁的申请内存对象和和释放对象,可以考虑使用对象池来增加对象的复用。 例如ListView便是采用这种思想,通过复用converview来避免频繁的GC
65. 如何排查OOM
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0920/3478.html
66. 如何避免OOM
- 使用更加轻量的数据结构 例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构。通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效,在于他们避免了对key与value的自动装箱(autoboxing),并且避免了装箱后的解箱。
- 避免在Android里面使用Enum Android官方培训课程提到过“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”,具体原理请参考《Android性能优化典范(三)》,所以请避免在Android里面使用到枚举。
- 减小Bitmap对象的内存占用 Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用可谓是重中之重,,通常来说有以下2个措施: inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。 decode format:解码格式,选择ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差异
- Bitmap对象的复用 缩小Bitmap的同时,也需要提高BitMap对象的复用率,避免频繁创建BitMap对象,复用的方法有以下2个措施 LRUCache : “最近最少使用算法”在Android中有极其普遍的应用。ListView与GridView等显示大量图片的控件里,就是使用LRU的机制来缓存处理好的Bitmap,把近期最少使用的数据从缓存中移除,保留使用最频繁的数据, inBitMap高级特性:利用inBitmap的高级特性提高Android系统在Bitmap分配与释放执行效率。使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的Bitmap会尝试去使用之前那张Bitmap在Heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放Bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小
- 使用更小的图片 在涉及给到资源图片时,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用更小的图片。尽量使用更小的图片不仅可以减少内存的使用,还能避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,很有可能在初始化视图时会因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM。
- StringBuilder在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。
- 避免在onDraw方法里面执行对象的创建 类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。
- 避免对象的内存泄露 android中内存泄漏的场景以及解决办法,参考上一问
67. ANR
-
什么是ANR : ANR全称Application Not Responding,意思就是程序未响应。如果一个应用无法响应用户的输入,系统就会弹出一个ANR对话框,用户可以自行选择继续等待亦或者是停止当前程序。一旦出现下面两种情况,则弹出ANR对话框
1. 应用在5秒内未响应用户的输入事件(如按键或者触摸)
2. BroadcastReceiver未在10秒内完成相关的处理
3. Service在特定的时间内无法处理完成
超时的原因一般有两种:
(1)当前的事件没有机会得到处理(UI线程正在处理前一个事件没有及时完成或者looper被某种原因阻塞住)
(2)当前的事件正在处理,但没有及时完成
UI线程尽量只做跟UI相关的工作,耗时的工作(数据库操作,I/O,连接网络或者其他可能阻碍UI线程的操作)放入单独的线程处理,尽量用Handler来处理UI thread和thread之间的交互。
UI线程主要包括如下:
• Activity:onCreate(), onResume(), onDestroy(), onKeyDown(), onClick()
• AsyncTask: onPreExecute(), onProgressUpdate(), onPostExecute(), onCancel()
• Mainthread handler: handleMessage(), post(runnable r) - 如何定位ANR错误: 开发机器上,查看/data/anr/traces.text.最新的ANR信息在最开始部分.
- 如何避免ANR : 避免ANR最核心的一点就是在主线程减少耗时操作.通常需要从以下几个方案下手:
- 1.使用子线程处理耗时的IO操作
2.降低子线程优先级使用Thread或者HandlerThread时,调用Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)设置优先级,否则仍然会降低程序响应,因为默Thread的优先级和主线程相同。
3. 使用Handler处理子线程结果,而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程。
4.Activity的onCreate和onResume回调中尽量避免耗时的代码 5.BroadcastReceiver中onReceive代码也要尽量减少耗时操作建议使用IntentService处理。IntentService是一个异步的,会自动停止的服务,很好解决了传统的Service中处理完耗时操作忘记停止并销毁Service的问题
68. Java虚拟机和Dalvik 虚拟机的区别
- 什么是Dalvik虚拟机: Dalvik虚拟机是Android平台的核心。它可以支持.dex格式的程序的运行,.dex格式是专为Dalvik设计的一种压缩格式,可以减少整体文件尺寸,提高I/O操作的速度,适合内存和处理器速度有限的系统。每个程序都有一个Dalvik,支持JIT(即时编译)
- Dalvik虚拟机的作用:Dalvik虚拟机主要是完成对象生命周期管理,内存回收,堆栈管理,线程管理,安全和异常管理等等重要功能。
- Dalvik虚拟机和JVM有什么区别: Dalvik 基于寄存器,而 JVM 基于栈。基于寄存器的虚拟机对于更大的程序来说,在它们编译的时候,花费的时间更短。 Dalvik执行.dex格式的字节码(java类被编译成.class文件后,会通过一个dx工具将所有的.class文件转换成一个.dex文件,然后dalvik虚拟机会从其中读取指令和数据,而JVM执行.class格式的字节码
- 每个应用程序对应多少个Dalvik虚拟机: 每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例,其代码在虚拟机的解释下得以执行 ,而所有的Android应用的线程都对应一个Linux线程
- 一个应用,一个虚拟机实例,一个进程(所有android应用的线程都是对应一个linux线程,都运行在自己的沙盒中,不同的应用在不同的进程中运行。每个android dalvik应用程序都被赋予了一个独立的linux PID(app_*))
69. Android为每个应用程序分配的内存大小是多少
一般是16m或者24m,但是可以通过android:largeHeap申请更多内存,具体参考:
http://www.cnblogs.com/mythou/p/3203536.html
70. 如何解决方法数65k问题?
- 使用Android Studio 的gradle 可以构建MutilDex
71. Android系统启动流程分析
- 打开adb shell 然后执行ps命令,可以看到首先执行的是init方法!找到init.c这个文件.
- 然后走init里面的main方法,在这main方法里面执行mkdir进行创建很多的文件夹,和挂载一些目录
- 然后回去初始化init.rc这个配置文件!在这个配置文件里面回去启动孵化器这个服务,这个服务会去启动app_process这个文件夹,这个文件夹里面有个app_main.cpp这个文件!
- 然后在app_main.cpp这个c文件里面在main方法里面它会去启动安卓的虚拟机,然后安卓虚拟机会去启动os.zygoteinit这个服务!
- zygoteinit这是个java代码写的,然后我们找到了main方法,在这个方法里面我们看到他首先设置虚拟机的最小堆内存为5兆,然后走到preloadclasses()这个方法来加载安卓系统所有的2000多个类通过类加载器加载进来,比如activity,contentx,http,…(其实没有必要一下子全部加载下来,我们可以等用到的时候在加载也可以!)
- 然后又走preloadresources()这个方法来预加载安卓中定义好的资源比如颜色,图片,系统的id等等。。。都加载了!(其实这也是没必要的! )
- 然后又走startSystemServer(),这个方法来加载系统的服务!他会先使用natvieJNI去调用C去初始化界面和声音的服务,这就是我们为什么先听到声音和界面的原因!
- 最后等服务加载完成后也就启动起来了!
总结 linux启动->init进程启动(加载init.rc配置)->zygote启动->systemServer启动,systemServer会通过init1和init2启动navite世界和java世界
72. Android图片占用内存计算公式
- 常用的ARGB_8888存储时:占用内存 = 图片长 * 图片宽 * 4字节, 图片长 = 图片原始长 * (设备dpi/图片所在资源文件夹dpi), 图片宽 =图片原始长 * (设备dpi/图片所在资源文件夹dpi), 例如:红米dpi为320,图片200*230放在hdpi文件夹中,hdpi文件夹对应dpi为240,那么图片长 = 200 * (320/240)= 266.67。同理,xxhdpi文件夹对应480,代入公式图片长为133.33
- RGB_565存储时,2字节;ALPHA_8为1字节,ARGB_4444为2字节
73. 使用过 AsyncTask 吗?说说它的内部实现原理?它有什么缺陷?如何改进?
- AsyncTask 内部也是 Handler 机制来完成的,只不过 Android 提供了执行框架来提供线程池来执行相应地任务,因为线程池的大小问题,所以 AsyncTask 只应该用来执行耗时时间较短的任务,如 HTTP 请求,大规模的下载和数据库的更改不适用于 AsyncTask,因为会导致线程池堵塞,没有线程来执行其他的任务,导致的情形是会发生 AsyncTask 根本执行不了的问题。
74. 如何实现文件断点上传
在 Android 中上传文件可以采用 HTTP 方式,也可以采用 Socket 方式,但是 HTTP 方式不能上传大文件,这里介绍一种通过 Socket 方式来进行断点续传的方式,服务端会记录下文件的上传进度,当某一次上传过程意外终止后,下一次可以继续上传,这里用到的其实还是 J2SE 里的知识。
- 这个上传程序的原理是:客户端第一次上传时向服务端发送
- “Content-Length=35;filename=WinRAR_3.90_SC.exe;sourceid=“这种格式的字符串,服务端收到后会查找该文件是否有上传记录,如果有就返回已经上传的位置,否则返回新生成的 sourceid 以及 position 为 0,类似 sourceid=2324838389;position=0“这样的字符串,客户端收到返回后的字符串后再从指定的位置开始上传文件。
75. Fragment 如何实现类似 Activity 栈的压栈和出栈效果的?
Fragment 的事物管理器内部维持了一个双向链表结构,该结构可以记录我们每次 add 的Fragment 和 replace 的 Fragment,然后当我们点击 back 按钮的时候会自动帮我们实现退栈操作。
76. Fragment 的好处:
- Fragment 可以使你能够将 activity 分离成多个可重用的组件,每个都有它自己的生命周期和UI。
- Fragment 可以轻松得创建动态灵活的 UI 设计,可以适应于不同的屏幕尺寸。从手机到平板电脑。
- Fragment 是一个独立的模块,紧紧地与 activity 绑定在一起。可以运行中动态地移除、加入、交换等。
- Fragment 提供一个新的方式让你在不同的安卓设备上统一你的 UI。
- Fragment 解决 Activity 间的切换不流畅,轻量切换。
- Fragment 替代 TabActivity 做导航,性能更好。
- Fragment 在 4.2.版本中新增嵌套 fragment 使用方法,能够生成更好的界面效果
77. Fragment生命周期有哪些?
onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume->onPause->onStop->onDestroyView->onDestroy->onDetach
78. onSaveInstanceState()被执行的场景有哪些(保存Activity状态):
- onSaveInstanceState(Bundle)会在activity转入后台状态之前被调用,也就是onStop()方法之前,onPause方法之后被调用;
- 当用户按下HOME键时
- 长按HOME键,选择运行其他的程序时
- 锁屏时
- 从activity A中启动一个新的activity时
- 屏幕方向切换时
79. Service与Activity怎么实现通信
方法一:
- 添加一个继承Binder的内部类,并添加相应的逻辑方法
- 重写Service的onBind方法,返回我们刚刚定义的那个内部类实例
- Activity中创建一个ServiceConnection的匿名内部类,并且重写里面的onServiceConnected方法和onServiceDisconnected方法,这两个方法分别会在活动与服务成功绑定以及解除绑定的时候调用,在onServiceConnected方法中,我们可以得到一个刚才那个service的binder对象,通过对这个binder对象进行向下转型,得到我们那个自定义的Binder实例,有了这个实例,做可以调用这个实例里面的具体方法进行需要的操作了
方法二: 通过BroadCast(广播)的形式 当我们的进度发生变化的时候我们发送一条广播,然后在Activity的注册广播接收器,接收到广播之后更新视图
80. activity的启动过程(不是生命周期,是app的启动过程)
app启动的过程有两种情况,第一种是从桌面launcher上点击相应的应用图标,第二种是在activity中通过调用startActivity来启动一个新的activity。
我们创建一个新的项目,默认的根activity都是MainActivity,而所有的activity都是保存在堆栈中的,我们启动一个新的activity就会放在上一个activity上面,而我们从桌面点击应用图标的时候,由于launcher本身也是一个应用,当我们点击图标的时候,系统就会调用startActivitySately(),一般情况下,我们所启动的activity的相关信息都会保存在intent中,比如action,category等等。我们在安装这个应用的时候,系统也会启动一个PackaManagerService的管理服务,这个管理服务会对AndroidManifest.xml文件进行解析,从而得到应用程序中的相关信息,比如service,activity,Broadcast等等,然后获得相关组件的信息。当我们点击应用图标的时候,就会调用startActivitySately()方法,而这个方法内部则是调用startActivty(),而startActivity()方法最终还是会调用startActivityForResult()这个方法。而在startActivityForResult()这个方法。因为startActivityForResult()方法是有返回结果的,所以系统就直接给一个-1,就表示不需要结果返回了。而startActivityForResult()这个方法实际是通过Instrumentation类中的execStartActivity()方法来启动activity,Instrumentation这个类主要作用就是监控程序和系统之间的交互。而在这个execStartActivity()方法中会获取ActivityManagerService的代理对象,通过这个代理对象进行启动activity。启动会就会调用一个checkStartActivityResult()方法,如果说没有在配置清单中配置有这个组件,就会在这个方法中抛出异常了。当然最后是调用的是Application.scheduleLaunchActivity()进行启动activity,而这个方法中通过获取得到一个ActivityClientRecord对象,而这个ActivityClientRecord通过handler来进行消息的发送,系统内部会将每一个activity组件使用ActivityClientRecord对象来进行描述,而ActivityClientRecord对象中保存有一个LoaderApk对象,通过这个对象调用handleLaunchActivity来启动activity组件,而页面的生命周期方法也就是在这个方法中进行调用。
81. HttpClient 与HttpUrlConnection 的区别
- 此处延伸:Volley里用的哪种请求方式(2.3前HttpClient,2.3后HttpUrlConnection)
- 首先HttpClient和HttpUrlConnection 这两种方式都支持Https协议,都是以流的形式进行上传或者下载数据,也可以说是以流的形式进行数据的传输,还有ipv6,以及连接池等功能。HttpClient这个拥有非常多的API,所以如果想要进行扩展的话,并且不破坏它的兼容性的话,很难进行扩展,也就是这个原因,Google在Android6.0的时候,直接就弃用了这个HttpClient.
- 而HttpUrlConnection相对来说就是比较轻量级了,API比较少,容易扩展,并且能够满足Android大部分的数据传输。比较经典的一个框架volley,在2.3版本以前都是使用HttpClient,在2.3以后就使用了HttpUrlConnection。
82. 进程保活方式
- 此处延伸:进程的优先级是什么*
当前业界的Android进程保活手段主要分为 黑、白、灰 三种,其大致的实现思路如下:
- 黑色保活* :不同的app进程,用广播相互唤醒(包括利用系统提供的广播进行唤醒)
- 白色保活* :启动前台Service
- 灰色保活* :利用系统的漏洞启动前台Service
- 黑色保活*
所谓黑色保活,就是利用不同的app进程使用广播来进行相互唤醒。举个3个比较常见的场景:
场景1 :开机,网络切换、拍照、拍视频时候,利用系统产生的广播唤醒app
场景2 :接入第三方SDK也会唤醒相应的app进程,如微信sdk会唤醒微信,支付宝sdk会唤醒支付宝。由此发散开去,就会直接触发了下面的 场景3
场景3 :假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app,那么你打开任意一个阿里系的app后,有可能就顺便把其他阿里系的app给唤醒了。(只是拿阿里打个比方,其实BAT系都差不多)
白色保活
白色保活手段非常简单,就是调用系统api启动一个前台的Service进程,这样会在系统的通知栏生成一个Notification,用来让用户知道有这样一个app在运行着,哪怕当前的app退到了后台。如下方的LBE和QQ音乐这样:
灰色保活
灰色保活,这种保活手段是应用范围最广泛。它是利用系统的漏洞来启动一个前台的Service进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个Notification,看起来就如同运行着一个后台Service进程一样。这样做带来的好处就是,用户无法察觉到你运行着一个前台进程(因为看不到Notification),但你的进程优先级又是高于普通后台进程的。那么如何利用系统的漏洞呢,大致的实现思路和代码如下:
思路一:API < 18,启动前台Service时直接传入new Notification();
思路二:API >= 18,同时启动两个id相同的前台Service,然后再将后启动的Service做stop处理
熟悉Android系统的童鞋都知道,系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给需要的app。这套杀进程回收内存的机制就叫 Low Memory Killer ,它是基于Linux内核的 OOM Killer(Out-Of-Memory killer)机制诞生。
- 进程的重要性,划分5级:-
- 前台进程 (Foreground process)
- 可见进程 (Visible process)
- 服务进程 (Service process)
- 后台进程 (Background process)
- 空进程 (Empty process)
了解完 Low Memory Killer,再科普一下oom_adj。什么是oom_adj?它是linux内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收。对于oom_adj的作用,你只需要记住以下几点即可:
进程的oom_adj越大,表示此进程优先级越低,越容易被杀回收;越小,表示进程优先级越高,越不容易被杀回收
普通app进程的oom_adj>=0,系统进程的oom_adj才可能<0
有些手机厂商把这些知名的app放入了自己的白名单中,保证了进程不死来提高用户体验(如微信、QQ、陌陌都在小米的白名单中)。如果从白名单中移除,他们终究还是和普通app一样躲避不了被杀的命运,为了尽量避免被杀,还是老老实实去做好优化工作吧。
83. 讲解一下context
Context是一个抽象基类。在翻译为上下文,也可以理解为环境,是提供一些程序的运行环境基础信息。Context下有两个子类,ContextWrapper是上下文功能的封装类,而ContextImpl则是上下文功能的实现类。而ContextWrapper又有三个直接的子类, ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity,所以Activity和Service以及Application的Context是不一样的,只有Activity需要主题,Service不需要主题。Context一共有三种类型,分别是Application、Activity和Service。这三个类虽然分别各种承担着不同的作用,但它们都属于Context的一种,而它们具体Context的功能则是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。
getApplicationContext()和getApplication()方法得到的对象都是同一个application对象,只是对象的类型不一样。
Context数量 = Activity数量 + Service数量 + 1 (1为Application)
84. activity,view,window三者的关系
这个问题真的很不好回答。所以这里先来个算是比较恰当的比喻来形容下它们的关系吧。Activity像一个工匠(控制单元),Window像窗户(承载模型),View像窗花(显示视图)LayoutInflater像剪刀,Xml配置像窗花图纸。
- 1:Activity构造的时候会初始化一个Window,准确的说是PhoneWindow。
- 2:这个PhoneWindow有一个“ViewRoot”,这个“ViewRoot”是一个View或者说ViewGroup,是最初始的根视图。
- 3:“ViewRoot”通过addView方法来一个个的添加View。比如TextView,Button等
- 4:这些View的事件监听,是由WindowManagerService来接受消息,并且回调Activity函数。比如onClickListener,onKeyDown等。
85. 热修复原理
我们知道Java虚拟机 —— JVM 是加载类的class文件的,而Android虚拟机——Dalvik/ART VM 是加载类的dex文件,而他们加载类的时候都需要ClassLoader,ClassLoader有一个子类BaseDexClassLoader,而BaseDexClassLoader下有一个数组——DexPathList,是用来存放dex文件,当BaseDexClassLoader通过调用findClass方法时,实际上就是遍历数组,找到相应的dex文件,找到,则直接将它return。而热修复的解决方法就是将新的dex添加到该集合中,并且是在旧的dex的前面,所以就会优先被取出来并且return返回。
86. Fragment与fragment ,activity的通讯方式
- 1.直接在一个Fragment中调用另外一个Fragment中的方法
- 2.使用接口回调
- 3.使用广播
- 4.Fragment直接调用Activity中的public方法
87. Android屏幕适配
字体使用sp,使用dp,多使用match_parent,wrap_content,weight。图片资源,不同图片的的分辨率,放在相应的文件夹下可使用百分比代替。
88. app优化(工具:Hierarchy Viewer 分析布局 工具:TraceView 测试分析耗时的)
- App启动优化
- 布局优化
- 响应优化
- 内存优化
- 电池使用优化
- 网络优化
App启动优化(针对冷启动)
App启动的方式有三种:
冷启动:App没有启动过或App进程被killed, 系统中不存在该App进程, 此时启动App即为冷启动。
热启动:热启动意味着你的App进程只是处于后台, 系统只是将其从后台带到前台, 展示给用户。
介于冷启动和热启动之间, 一般来说在以下两种情况下发生:
- (1)用户back退出了App, 然后又启动. App进程可能还在运行, 但是activity需要重建。
- (2)用户退出App后, 系统可能由于内存原因将App杀死, 进程和activity都需要重启, 但是可以在onCreate中将被动杀死锁保存的状态(saved instance state)恢复。
优化:
Application的onCreate(特别是第三方SDK初始化),首屏Activity的渲染都不要进行耗时操作,如果有,就可以放到子线程或者IntentService中
布局优化
尽量不要过于复杂的嵌套。可以使用<include>,<merge>,<ViewStub>
响应优化
Android系统每隔16ms会发出VSYNC信号重绘我们的界面(Activity)。
页面卡顿的原因:
- (1)过于复杂的布局.
- (2)UI线程的复杂运算
- (3)频繁的GC,导致频繁GC有两个原因:1、内存抖动, 即大量的对象被创建又在短时间内马上被释放.2、瞬间产生大量的对象会严重占用内存区域。
内存优化:参考内存泄露和内存溢出部分
电池使用优化(使用工具:Batterystats & bugreport)
- (1)优化网络请求
- (2)定位中使用GPS, 请记得及时关闭
网络优化(网络连接对用户的影响:流量,电量,用户等待)可在Android studio下方logcat旁边那个工具Network Monitor检测
- API设计:App与Server之间的API设计要考虑网络请求的频次, 资源的状态等. 以便App可以以较少的请求来完成业务需求和界面的展示.
- Gzip压缩:使用Gzip来压缩request和response, 减少传输数据量, 从而减少流量消耗.
- 图片的Size:可以在获取图片时告知服务器需要的图片的宽高, 以便服务器给出合适的图片, 避免浪费.
- 网络缓存:适当的缓存, 既可以让我们的应用看起来更快, 也能避免一些不必要的流量消耗.
88. 图片优化
- (1)对图片本身进行操作。尽量不要使用setImageBitmap、setImageResource、BitmapFactory.decodeResource来设置一张大图,因为这些方法在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存.
- (2)图片进行缩放的比例,SDK中建议其值是2的指数值,值越大会导致图片不清晰。
- (3)不用的图片记得调用图片的recycle()方法
89. HybridApp webview 和 JS 交互
Android与JS通过WebView互相调用方法,实际上是:
Android去调用JS的代码
- 1. 通过WebView的loadUrl(),使用该方法比较简洁,方便。但是效率比较低,获取返回值比较困难。
- 2. 通过WebView的evaluateJavascript(),该方法效率高,但是4.4以上的版本才支持,4.4以下版本不支持。所以建议两者混合使用。
JS去调用Android的代码
1. 通过WebView的addJavascriptInterface()进行对象映射 ,该方法使用简单,仅将Android对象和JS对象映射即可,但是存在比较大的漏洞。
漏洞产生原因是:当JS拿到Android这个对象后,就可以调用这个Android对象中所有的方法,包括系统类(java.lang.Runtime 类),从而进行任意代码执行。
解决方式:
- (1)Google 在Android 4.2 版本中规定对被调用的函数以 @JavascriptInterface进行注解从而避免漏洞攻击。
- (2)在Android 4.2版本之前采用拦截prompt()进行漏洞修复。
2. 通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url 。这种方式的优点:不存在方式1的漏洞;缺点:JS获取Android方法的返回值复杂。(ios主要用的是这个方式)
- (1)Android通过 WebViewClient 的回调方法shouldOverrideUrlLoading ()拦截 url
- (2)解析该 url 的协议
- (3)如果检测到是预先约定好的协议,就调用相应方法
3. 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息
这种方式的优点:不存在方式1的漏洞;缺点:JS获取Android方法的返回值复杂。
90. 设计模式
单例模式:分为恶汉式和懒汉式
- 饿汉式:
public class Singleton { private static Singleton instance = new Singleton(); public static Singleton getInstance() { return instance ; } }
- 懒汉式:
public class Singleton02 { private static Singleton02 instance; public static Singleton02 getInstance() { if (instance == null) { synchronized (Singleton02.class) { if (instance == null) { instance = new Singleton02(); } } } return instance; } }
91.MVC ,MVP,MVVM
MVP模式,对应着Model--业务逻辑和实体模型,view--对应着activity,负责View的绘制以及与用户交互,Presenter--负责View和Model之间的交互,MVP模式是在MVC模式的基础上,将Model与View彻底分离使得项目的耦合性更低,在Mvc中项目中的activity对应着mvc中的C--Controllor,而项目中的逻辑处理都是在这个C中处理,同时View与Model之间的交互,也是也就是说,mvc中所有的逻辑交互和用户交互,都是放在Controllor中,也就是activity中。View和model是可以直接通信的。而MVP模式则是分离的更加彻底,分工更加明确Model--业务逻辑和实体模型,view--负责与用户交互,Presenter 负责完成View于Model间的交互,MVP和MVC最大的区别是MVC中是允许Model和View进行交互的,而MVP中很明显,Model与View之间的交互由Presenter完成。还有一点就是Presenter与View之间的交互是通过接口的
92. Rrcyclerview 和listview的区别
RecyclerView可以完成ListView,GridView的效果,还可以完成瀑布流的效果。同时还可以设置列表的滚动方向(垂直或者水平);RecyclerView中view的复用不需要开发者自己写代码,系统已经帮封装完成了。RecyclerView可以进行局部刷新。RecyclerView提供了API来实现item的动画效果。
在性能上:
如果需要频繁的刷新数据,需要添加动画,则RecyclerView有较大的优势。如果只是作为列表展示,则两者区别并不是很大。
93. Universal-ImageLoader, Picasso ,Fesco,Glide 对比
Fresco 是 Facebook 推出的开源图片缓存工具,主要特点包括:两个内存缓存加上 Native 缓存构成了三级缓存,
优点:
- 1. 图片存储在安卓系统的匿名共享内存, 而不是虚拟机的堆内存中, 图片的中间缓冲数据也存放在本地堆内存, 所以, 应用程序有更多的内存使用, 不会因为图片加载而导致oom, 同时也减少垃圾回收器频繁调用回收 Bitmap 导致的界面卡顿, 性能更高。
- 2. 渐进式加载 JPEG 图片, 支持图片从模糊到清晰加载。
- 3. 图片可以以任意的中心点显示在 ImageView, 而不仅仅是图片的中心。
- 4. JPEG 图片改变大小也是在 native 进行的, 不是在虚拟机的堆内存, 同样减少 OOM。
- 5. 很好的支持 GIF 图片的显示。
缺点:
- 1. 框架较大, 影响 Apk 体积
- 2. 使用较繁琐
Universal-ImageLoader:(估计由于HttpClient被Google放弃,作者就放弃维护这个框架)
优点:
- 1.支持下载进度监听
- 2.可以在 View 滚动中暂停图片加载,通过 PauseOnScrollListener 接口可以在 View 滚动中暂停图片加载。
- 3.默认实现多种内存缓存算法 这几个图片缓存都可以配置缓存算法,不过 ImageLoader 默认实现了较多缓存算法,如 Size 最大先删除、使用最少先删除、最近最少使用、先进先删除、时间最长先删除等。
- 4.支持本地缓存文件名规则定义
Picasso 优点
- 1. 自带统计监控功能。支持图片缓存使用的监控,包括缓存命中率、已使用内存大小、节省的流量等。
- 2.支持优先级处理。每次任务调度前会选择优先级高的任务,比如 App 页面中 Banner 的优先级高于 Icon 时就很适用。
- 3.支持延迟到图片尺寸计算完成加载
- 4.支持飞行模式、并发线程数根据网络类型而变。 手机切换到飞行模式或网络类型变换时会自动调整线程池最大并发数,比如 wifi 最大并发为 4,4g 为 3,3g 为 2。 这里 Picasso 根据网络类型来决定最大并发数,而不是 CPU 核数。
- 5.“无”本地缓存。无”本地缓存,不是说没有本地缓存,而是 Picasso 自己没有实现,交给了 Square 的另外一个网络库 okhttp 去实现,这样的好处是可以通过请求 Response Header 中的 Cache-Control 及 Expired 控制图片的过期时间。
Glide 优点
- 1. 不仅仅可以进行图片缓存还可以缓存媒体文件。Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图。甚至是 Video,所以更该当做一个媒体缓存。
- 2. 支持优先级处理。
- 3. 与 Activity/Fragment 生命周期一致,支持 trimMemory。Glide 对每个 context 都保持一个 RequestManager,通过 FragmentTransaction 保持与 Activity/Fragment 生命周期一致,并且有对应的 trimMemory 接口实现可供调用。
- 4. 支持 okhttp、Volley。Glide 默认通过 UrlConnection 获取数据,可以配合 okhttp 或是 Volley 使用。实际 ImageLoader、Picasso 也都支持 okhttp、Volley。
- 5. 内存友好。Glide 的内存缓存有个 active 的设计,从内存缓存中取数据时,不像一般的实现用 get,而是用 remove,再将这个缓存数据放到一个 value 为软引用的 activeResources map 中,并计数引用数,在图片加载完成后进行判断,如果引用计数为空则回收掉。内存缓存更小图片,Glide 以 url、view_width、view_height、屏幕的分辨率等做为联合 key,将处理后的图片缓存在内存缓存中,而不是原始图片以节省大小与 Activity/Fragment 生命周期一致,支持 trimMemory。图片默认使用默认 RGB_565 而不是 ARGB_888,虽然清晰度差些,但图片更小,也可配置到 ARGB_888。
- 6.Glide 可以通过 signature 或不使用本地缓存支持 url 过期
94. Xutils,Okhttp,Volley, Retrofit 对比
Xutils这个框架非常全面,可以进行网络请求,可以进行图片加载处理,可以数据储存,还可以对view进行注解,使用这个框架非常方便,但是缺点也是非常明显的,使用这个项目,会导致项目对这个框架依赖非常的严重,一旦这个框架出现问题,那么对项目来说影响非常大的。、
- OKhttp:Android开发中是可以直接使用现成的api进行网络请求的。就是使用HttpClient,HttpUrlConnection进行操作。okhttp针对Java和Android程序,封装的一个高性能的http请求库,支持同步,异步,而且okhttp又封装了线程池,封装了数据转换,封装了参数的使用,错误处理等。API使用起来更加的方便。但是我们在项目中使用的时候仍然需要自己在做一层封装,这样才能使用的更加的顺手。
- Volley:Volley是Google官方出的一套小而巧的异步请求库,该框架封装的扩展性很强,支持HttpClient、HttpUrlConnection, 甚至支持OkHttp,而且Volley里面也封装了ImageLoader,所以如果你愿意你甚至不需要使用图片加载框架,不过这块功能没有一些专门的图片加载框架强大,对于简单的需求可以使用,稍复杂点的需求还是需要用到专门的图片加载框架。Volley也有缺陷,比如不支持post大数据,所以不适合上传文件。不过Volley设计的初衷本身也就是为频繁的、数据量小的网络请求而生。
- Retrofit:Retrofit是Square公司出品的默认基于OkHttp封装的一套RESTful网络请求框架,RESTful是目前流行的一套api设计的风格, 并不是标准。Retrofit的封装可以说是很强大,里面涉及到一堆的设计模式,可以通过注解直接配置请求,可以使用不同的http客户端,虽然默认是用http ,可以使用不同Json Converter 来序列化数据,同时提供对RxJava的支持,使用Retrofit + OkHttp + RxJava + Dagger2 可以说是目前比较潮的一套框架,但是需要有比较高的门槛。
- Volley VS OkHttp:Volley的优势在于封装的更好,而使用OkHttp你需要有足够的能力再进行一次封装。而OkHttp的优势在于性能更高,因为 OkHttp基于NIO和Okio ,所以性能上要比 Volley更快。IO 和 NIO这两个都是Java中的概念,如果我从硬盘读取数据,第一种方式就是程序一直等,数据读完后才能继续操作这种是最简单的也叫阻塞式IO,还有一种是你读你的,程序接着往下执行,等数据处理完你再来通知我,然后再处理回调。而第二种就是 NIO 的方式,非阻塞式, 所以NIO当然要比IO的性能要好了,而 Okio是 Square 公司基于IO和NIO基础上做的一个更简单、高效处理数据流的一个库。理论上如果Volley和OkHttp对比的话,更倾向于使用 Volley,因为Volley内部同样支持使用OkHttp,这点OkHttp的性能优势就没了, 而且 Volley 本身封装的也更易用,扩展性更好些。
- OkHttp VS Retrofit:毫无疑问,Retrofit 默认是基于 OkHttp 而做的封装,这点来说没有可比性,肯定首选 Retrofit。
- Volley VS Retrofit:这两个库都做了不错的封装,但Retrofit解耦的更彻底,尤其Retrofit2.0出来,Jake对之前1.0设计不合理的地方做了大量重构, 职责更细分,而且Retrofit默认使用OkHttp,性能上也要比Volley占优势,再有如果你的项目如果采用了RxJava ,那更该使用 Retrofit 。所以这两个库相比,Retrofit更有优势,在能掌握两个框架的前提下该优先使用 Retrofit。但是Retrofit门槛要比Volley稍高些,要理解他的原理,各种用法,想彻底搞明白还是需要花些功夫的,如果你对它一知半解,那还是建议在商业项目使用Volley吧。
95.Rxjava
RxJava操作符:
- from(): 转换集合为一个每次发射集合中的一个元素的Observable对象。可用来遍历集合
- just(): 转换一个或多个Object为依次发射这些Object的Observable对象
- create():返回一个被OnSubscribe 订阅时执行特定方法的Observable 对象
- interval():返回一个每隔特定时间间隔发射一个***的Observable对象,可用来做倒计时操作
- timer():创建一个在指定延时时间后发射一条数据的Observable 对象,可用来做定时操作
- range():创建一个发射指定范围内的连续整数的Observable 对象
- empty():创建一个不发射任何数据就发出onCompleted()通知的Observable 对象
- error():创建不发射任何数据就发出onError 通知的Onbservable对象
- map():把Observable发射的元素应用与指定的函数,并发送该函数的结果
- flatMap():转换源Observable 对象为另一个Observable 对象
- ----------
- 手写算法(字符串最大共有字符串,几种排序)
- 什么情况下导致Force close ? 如何避免? 能否捕获其异常
- Java 的注解反射原理
- android 数据库的优化
- 如何将一个activity设置成窗口的样式
- Android本身的api并未声明会抛出异常,则其在运行时有无可能抛出runtime异常,你遇到过吗?若有的话会导致什么问题,是如何解决的?
- 设计一套图片异步加载缓存方案
- 刷新 view 的几种方式,他们有什么区别?
- 如何实现 Android 中的缓存的,通过使用第三方库和自定义来分别说明一下缓存技术的实现?
- Android 5.0、6.0、7.0、8.0 新特性?
- Android 中是如何实现异步通信的?
- 使用过 AsyncTask 吗?说说它的内部实现原理?它有什么缺陷?如何改进?
- 你了解广播吗?它与 EventBus 有什么区别?能互相实现吗?
- 知道 Android 中的多渠道打包吗?
- Android 签名机制的原理?反编译解压后的文件夹所包含的内容有哪些
- 你了解过模块化、组件化开发吗?
- 开始开发 APP 如何进行架构?
- 工程模块是如何划分的?你是如何进行封装的?
- 跟activity和Task 有关的 Intent启动方式有哪些?其含义?
- activity在屏幕旋转时的生命周期
- 注册广播有几种方式,这些方式有何优缺点?请谈谈Android引入广播机制的用意。
- 描述一下android的系统架构
- Service和Thread的区别?
- 如何退出Activity?如何安全退出已调用多个Activity的Application?
- 请解释下Android程序运行时权限与文件系统权限的区别。
- 系统上安装了多种浏览器,能否指定某浏览器访问指定页面?请说明原由
- android系统的优势和不足
- Android dvm的进程和Linux的进程, 应用程序的进程是否为同一个概念
- sim卡的EF文件是什么?有何作用
- 嵌入式操作系统内存管理有哪几种, 各有何特性
- 什么是嵌入式实时操作系统, Android 操作系统属于实时操作系统吗?
- 如何将SQLite数据库(dictionary.db文件)与apk文件一起发布
- SQLite支持事务吗?添加删除如何提高性能?
- SQLite优化
- 如何实现一个字体的描边与阴影效果
Java 基础部分
1.线程中的sleep和wait的区别
- sleep来自Thread类,和wait来自Object类
- 调用sleep()方法的过程中,线程不会释放对象锁。而调用 wait 方法线程会释放对象锁
- sleep睡眠后不出让系统资源,wait让出系统资源其他线程可以占用CPU
- sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒,如果时间不到只能调用interreput()来强行打断,wait()可以用notify()直接唤起
- notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在 任何地方使用
2.Thread 的start() 和run() 方法的区别
start()方法是用来启动新创建的线程,而start()内部调用了run()方法,这和直接调用run()方法是不一样的,如果直接调用run()方法,则和普通的方法没有什么区别。
3.关键字final 和static是怎么使用的
final:
- 1、final变量即为常量,只能赋值一次。
- 2、final方法不能被子类重写。
- 3、final类不能被继承。
static:
- static变量:对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。
- 2、static代码块 static代码块是类加载时,初始化自动执行的。
- 3、static方法 static方法可以直接通过类名调用,任何的实例也都可以调用,因此static方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。
4.String ,StringBuffer,StringBulder 的区别
- String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象
- StringBuffer和StringBuilder底层是 char[]数组实现的
- 三者在执行速度上:StringBuilder > StringBuffer > String (由于String是常量,不可改变,拼接时会重新创建新的对象)。
- StringBuffer是线程安全的,而StringBuilder是线程不安全的(由于StringBuffer有缓冲区)
5.Java 中的重载和重写的区别(Override和Overload的含义去区别)
- Overload顾名思义是重新加载,它可以表现类的多态性,可以是函数里面可以有相同的函数名但是参数名、返回值、类型不能相同;或者说可以改变参数、类型、返回值但是函数名字依然不变。
- Override顾名思义就是ride(重写)的意思,在子类继承父类的时候子类中可以定义某方法与其父类有相同的名称和参数,当子类在调用这一函数时自动调用子类的方法,而父类相当于被覆盖(重写)了。
6.Http 和 Https 的区别
- http(超文本传输协议)是一个基于请求与响应模式的、无状态的、应用层的协议,信息是明文传输;http请求由三部分组成,分别是:请求行、消息报头、请求正文。
- HTTP消息报头包括普通报头、请求报头、响应报头、实体报头
- HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。
- HTTPS协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
- HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
https实现原理:
- (1)客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。
- (2)Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
- (3)客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
- (4)客户端的浏览器根据双方同意的安全等级,建立会话**,然后利用网站的公钥将会话**加密,并传送给网站。
- (5)Web服务器利用自己的私钥解密出会话**。
- (6)Web服务器利用会话**加密与客户端之间的通信。
7.Http 位于TCP/IP模型的第几层
tcp/ip的五层模型:
- 从下到上:物理层->数据链路层->网络层->传输层->应用层
- 其中tcp/ip位于模型中的网络层,处于同一层的还有ICMP(网络控制信息协议)。http位于模型中的应用层
- 由于tcp/ip是面向连接的可靠协议,而http是在传输层基于tcp/ip协议的,所以说http是可靠的数据传输协议。
8.Http 链接的特点
HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。
9.TCP 和UDP的区别
- TCP/IP协议高,因为其拥有三次握手双向机制,这一机制保证校验了数据,保证了他的可靠性UDP就没有了,udp信息发出后,不验证是否到达对方,所以不可靠。但是就速度来说,还是UDP协议更高,毕竟其无需重复返回验证,只是一次性的
- tcp是面向连接的,由于tcp连接需要三次握手,所以能够最低限度的降低风险,保证连接的可靠性。
- udp 不是面向连接的,udp建立连接前不需要与对象建立连接,无论是发送还是接收,都没有发送确认信号。所以说udp是不可靠的。
- 由于udp不需要进行确认连接,使得UDP的开销更小,传输速率更高,所以实时行更好。
10.socket建立连接的步骤
建立Socket连接至少需要一对套接字,其中一个运行与客户端--ClientSocket,一个运行于服务端--ServiceSocket
- 1、服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
- 2、客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。注意:客户端的套接字必须描述他要连接的服务器的套接字,指出服务器套接字的地址和端口号,然后就像服务器端套接字提出连接请求。
- 3、连接确认:当服务器端套接字监听到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务端套接字则继续处于监听状态,继续接收其他客户端套接字的连接请求。
11.TCP/IP三次握手,四次挥手
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
【问题3】为什么不能用两次握手进行连接?
答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始***进行协商,这个***在握手过程中被发送和确认。
现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的***,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
12.HashMap ,HashTable 的区别
-
HashMap 的实现 :哈希表:由数组+链表组成的,基于 Map接口实现的
当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标)如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。 - HashTable:HashTable比较老,是基于Dictionary 类实现的
- HashTable 是线程安全的, HashMap 则是线程不安全的
- HashMap可以让你将空值作为一个表的条目的key或value
13.什么是原子操作,Java中的原子操作是什么
14.用java 写代码来解决生产者——消费者问题
15.现有T1,T2,T3 三个线程,你怎么保证T2在T1执行完后执行,T3在T2执行完后执行(join()方法的使用)
16.面向对象的三大特性,如何理解其中的多态
- 封装:
- 继承:提供了同一类对象共性的处理方法,子类继承父类共性的东西。 这样有利于代码的复用性
- 多态:多态就是同一操作(方法)作用于不同的对象时,可以有不同的解释,产生不同的执行结果。
- 多态的好处: (1) 可替换性:多态对已存在代码具有可替换性. (2)可扩充性:增加新的子类不影响已经存在的类结构. (3) 接口性:多态是超类通过方法签名,向子类提供一个公共接口,由子类来完善或者重写它来实现的.
- 代码中如何实现多态: 实现多态主要有以下三种方式:(1)接口实现 (2)继承父类重写方法 (3)同一类中进行方法重载
- 虚拟机是如何实现多态的: 动态绑定技术(dynamic binding),执行期间判断所引用对象的实际类型,根据实际类型调用对应的方法.
17.JVM 内存模型
- 程序计数器:线程私有的,生命周期与线程相同,一块较小的内存空间,可以看作是当前线程正在执行的字节码的行号指示器。分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖程序计数器来完成。如果当前线程正在执行的是一个本地方法(native方法)这是程序计数器为空。是唯一一个不会出现OutOfMemoryError的内存区域。
- Java虚拟机栈:线程私有的,生命周期与线程相同,是Java方法执行的内存模型,存储局部变量表,操作栈,动态链接,方法出口等信息。在方法运行的过程中局部变量表的大小是不会发生改变的。Java虚拟机栈会出现两种异常*Error和OutOfMemoryError。
- 本地方法栈:与虚拟机栈的作用相似,只是本地方法栈为虚拟机使用到的native方法服务,也会抛出*Error 和OutOfMemoryError异常。
- Java堆:是虚拟机所管理的内存中最大的一块,被所以线程共享,在虚拟机启动时创建,目的时存放对象,是垃圾回收的主要场所。堆的大小即可以固定也可以扩展,会抛出OutOfMemoryError。
- 所有线程共享,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。永久代,内存回收效率较低。和堆一样,允许固定大小,也允许可扩展的大小,还允许不实现垃圾回收。
- 运行时常量池:是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
- 直接内存:直接内存是除Java虚拟机之外的内存,但也有可能被Java使用。在NIO中引入了一种基于通道和缓冲的IO方式。它可以通过调用本地方法直接分配Java虚拟机之外的内存,然后通过一个存储在Java堆中的DirectByteBuffer对象直接操作该内存,而无需先将外面内存中的数据复制到堆中再操作,从而提升了数据操作的效率。直接内存的大小不受Java虚拟机控制,但既然是内存,当内存不足时就会抛出OOM异常。
https://blog.csdn.net/u010425776/article/details/51170118
18.你平常是怎么进行加密的?MD5 加密是可逆的吗?
19.接口与抽象类的区别?static 方法可以被覆盖吗?为什么?
一个类只能继承单个类,但是可以实现多个接口 接口强调特定功能的实现,而抽象类强调所属关系 抽象类中的所有方法并不一定要是抽象的,你可以选择在抽象类中实现一些基本的方法。而接口要求所有的方法都必须是抽象的
20.创建线程的方式,他们有什么区别?知道线程池吗?说说对线程池的理解?
- 线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。就好比原来去食堂打饭是每个人看谁抢的赢,谁先抢到谁先吃,有了线程池之后,就是排好队形,今天我跟你关系好,你先来吃饭。比如:一个应用要和网络打交道,有很多步骤需要访问网络,为了不阻塞主线程,每个步骤都创建个线程,在线程中和网络交互,用线程池就变的简单,线程池是对线程的一种封装,让线程用起来更加简便,只需要创一个线程池,把这些步骤像任务一样放进线程池,在程序销毁时只要调用线程池的销毁函数即可。
-
Java通过Executors提供四种线程池,分别为:
- newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。ScheduledExecutorService比Timer更安全,功能更强大
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
- 单个线程的弊端:a. 每次new Thread新建对象性能差b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或者OOM,c. 缺乏更多功能,如定时执行、定期执行、线程中断。
- java提供的四种线程池的好处在于:a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。c. 提供定时执行、定期执行、单线程、并发数控制等功能。
21.你了解过 Java 的四种引用吗?分别代表什么含义,他们有什么区别?
- 强引用:如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM 也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象
- 软引用:在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。
- 弱引用:具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象
- 虚引用:顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。
22.关于 Java 中深拷贝和浅拷贝的区别?
23.了解过 Java 的集合吗?说说 HashMap 的底层实现原理?ArrayList 和 LinkedList 的区别?Java 集合中哪些是线程安全的?
24.如何实现对象的排序?
25.知道 ThreadLocal 吗?说说对它的理解?
26.JDK1.8的特性
27.Java 中,Serializable 与 Externalizable 的区别
28.throw和throws的区别
29.Fail-fast和Fail-safe有什么区别
- Fail-Fast机制:在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast机制。这一机制在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map
30.ArrayList和HashMap默认大小?
31.volatile类型变量提供什么保证?
32.java中的++操作符线程安全么?
33.静态内部类、内部类、匿名内部类,为什么内部类会持有外部类的引用?持有的引用是this?还是其它?
- 静态内部类:使用static修饰的内部类
- 内部类:就是在某个类的内部又定义了一个类,内部类所嵌入的类称为外部类
- 匿名内部类:使用new生成的内部类
- 因为内部类的产生依赖于外部类,持有的引用是类名.this
34.Java中try catch finally的执行顺序
先执行try中代码,如果发生异常执行catch中代码,最后一定会执行finally中代码。如果一个方法内在执行try{}语句之前就已经return了,那么finally语句指定不会执行了。因为它根本没有进入try语句中如果在一个try语句中调用System.exit(0);方法,那么就会退出当前java虚拟机,那么finally也就没有执行的机会了。
35. equals与==的区别:
- ==是判断两个变量或实例是不是指向同一个内存空间
- equals是判断两个变量或实例所指向的内存空间的值是不是相
36. Object有哪些公用方法?
- 方法equals测试的是两个对象是否相等
- 方法clone进行对象拷贝
- 方法getClass返回和当前对象相关的Class对象
- 方法notify,notifyall,wait都是用来对给定对象进行线程同步的
37. Java GC 原理
- 标记回收法:遍历对象图并且记录可到达的对象,以便删除不可到达的对象,一般使用单线程工作并且可能产生内存碎片
- 标记-压缩回收法:前期与第一种方法相同,只是多了一步,将所有的存活对象压缩到内存的一端,这样内存碎片就可以合成一大块可再利用的内存区域,提高了内存利用率
- 复制回收法:把现有内存空间分成两部分,gc运行时,它把可到达对象复制到另一半空间,再清空正在使用的空间的全部对象。这种方法适用于短生存期的对象,持续复制长生存期的对象则导致效率降低。
- 分代回收发:把内存空间分为两个或者多个域,如年轻代和老年代,年轻代的特点是对象会很快被回收,因此在年轻代使用效率比较高的算法。当一个对象经过几次回收后依然存活,对象就会被放入称为老年的内存空间,老年代则采取标记-压缩算法
38. ArrayList,LinkedList的区别
- ArrayList是实现了基于动态数组的数据结构,LinkedList基于双向链表的数据结构。
- 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
- 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
39. ArrayList和Vector的主要区别是什么?
- ArrayList 和Vector底层是采用数组方式存储数据
- Vector:线程同步,当Vector中的元素超过它的初始大小时,Vector会将它的容量翻倍,
- ArrayList:线程不同步,但性能很好,当ArrayList中的元素超过它的初始大小时,ArrayList只增加50%的大小
- Vector由于使用了synchronized方法(线程安全)所以性能上比ArrayList要差
40. 给最外层的rootview,把这个根视图下的全部button背景设置成红色,手写代码,不许用递归
算法原理:
- Android的view视图是按树形结构分布,所以按树形结构遍历
- 循环判断每一层的ViewGroup元素,将其入栈;否则判断当前view是否是Button类实例,是则改写背景色
- 当前ViewGroup检查childView完成后,判断栈是否非空,取出栈顶元素ViewGroup重复步骤2直至栈为空。
void changeAllBtnBGColor(View view, int color) {
if (view == null || !(view instanceof ViewGroup))
return;
Stack m = new Stack<>();
while (view != null) {
ViewGroup tmpGroup = (ViewGroup) view;
int count = tmpGroup.getChildCount();
for (int i = 0; i < count; i++) { View child = tmpGroup.getChildAt(i);
if (child instanceof ViewGroup) m.add(child);
else if (child instanceof Button) { child.setBackgroundColor(color);
} }
if (m.isEmpty()) break;
else view = m.pop();
}
}
41. 谈谈 HTTP 中Get 和 Post 方法的区别
- GET - 从指定的服务器中获取数据,明文发送内容
- POST - 提交数据给指定的服务器处理
- POST请求不能被缓存下来
- POST请求不会保存在浏览器浏览记录中
- 以POST请求的URL无法保存为浏览器书签
- POST请求没有长度限制
42. 推送心跳包是TCP包还是UDP包或者HTTP包
心跳包的实现是调用了socket.sendUrgentData(0xFF)这句代码实现的,所以,当然是TCP包。
43. JAVA 中堆和栈的区别
- 基本数据类型比变量和对象的引用都是在栈分配的
- 堆内存用来存放由new创建的对象和数组
- 类变量(static修饰的变量),程序在一加载的时候就在堆中为类变量分配内存,堆中的内存地址存放在栈中
- 实例变量:当你使用java关键字new的时候,系统在堆中开辟并不一定是连续的空间分配给变量,是根据零散的堆内存地址,通过哈希算法换算为一长串数字以表征这个变量在堆中的"物理位置”,实例变量的生命周期--当实例变量的引用丢失后,将被GC(垃圾回收器)列入可回收“名单”中,但并不是马上就释放堆中内存
- 局部变量: 由声明在某方法,或某代码段里(比如for循环),执行到它的时候在栈中开辟内存,当局部变量一但脱离作用域,内存立即释放
数据结构与算法部分:
- 给最外层的rootview,把这个根视图下的全部button背景设置成红色,手写代码,不许用递归
- 给一串字符串比如abbbcccd,输出a1b3c3d1,手写代码(注意有个别字符可能会出现十次以上的情况)
- 一个序列,它的形式是12349678,9是最高峰,经历了一个上升又下降的过程,找出里面的最大值的位置,要求效率尽可能高
- 二叉查找树的删除操作,手写代码
- 反转链表,手写代码
- 二分查找,手写代码
- 有海量条 url,其中不重复的有300万条,现在希望挑选出重复出现次数最高的 url,要求效率尽可能的高
- 一篇英语文章,去掉字符只留下k个,如何去掉才能使这k个字符字典序最小
- 弗洛伊德算法和 Dijkstra算法的区别?复杂度是多少?讲讲 Dijkstra算法的具体过程
- 反转字符串,要求手写代码,优化速度、优化空间
- 给出两个无向图,找出这2个无向图中相同的环路。手写代码
- 单例模式,手写代码
- 生产者与消费者,手写代码
- 二叉树镜像,手写代码
- 最长不重复子串(最长重复子串),手写代码
上一篇: php use的用法是什么
下一篇: 如何利用PHP发送GET请求