Repository: pengMaster/BestNote Branch: master Commit: 941811623440 Files: 740 Total size: 4.7 MB Directory structure: gitextract_bgwjn77o/ ├── LICENSE ├── README.md └── docs/ ├── .nojekyll ├── android/ │ ├── Android-Interview/ │ │ ├── .gitignore │ │ ├── Activity/ │ │ │ ├── Activity Task和Process.md │ │ │ ├── App优雅退出.md │ │ │ ├── README.md │ │ │ ├── onCreate源码分析.md │ │ │ ├── onSaveInstanceState源码内核分析.md │ │ │ ├── 为什么service里面startActivity抛异常.md │ │ │ ├── 深入理解Activity启动流程.md │ │ │ ├── 深刻剖析activity启动模式-1.md │ │ │ ├── 深刻剖析activity启动模式-2.md │ │ │ └── 深刻剖析activity启动模式-3.md │ │ ├── Android/ │ │ │ ├── Android基础面试核心内容.md │ │ │ ├── Android视频教程.md │ │ │ ├── Android面试精华题目总结.md │ │ │ ├── Android面试重点.md │ │ │ ├── Android面试题-1.md │ │ │ ├── Android面试题-2.md │ │ │ ├── Android高级面试10大开源框架源码解析.md │ │ │ ├── BAT大咖助力全面升级Android面试.md │ │ │ ├── README.md │ │ │ ├── 平台架构.md │ │ │ └── 接口安全.md │ │ ├── HR/ │ │ │ ├── README.md │ │ │ ├── 人事面试宝典.md │ │ │ ├── 人事面试宝典一之自我介绍.md │ │ │ └── 人事面试宝典二之离职.md │ │ ├── Java/ │ │ │ ├── 115个Java面试题及回答.md │ │ │ ├── 66道经典的Java基础面试题集锦.md │ │ │ ├── J2SE基础面试核心内容.md │ │ │ ├── J2SE高级面试核心内容.md │ │ │ ├── Java面试题-1.md │ │ │ ├── Java面试题-2.md │ │ │ ├── Java高级软件工程师面试考纲.md │ │ │ ├── README.md │ │ │ ├── 数据库求差.md │ │ │ └── 深拷贝浅拷贝.md │ │ ├── README.md │ │ ├── SUMMARY.md │ │ ├── Service/ │ │ │ ├── Android面试题-Service.md │ │ │ ├── Android面试题-Service不死之身.md │ │ │ ├── IntentService源码分析.md │ │ │ └── README.md │ │ ├── book.json │ │ ├── 开发遇到的问题/ │ │ │ ├── Context原理分析.md │ │ │ ├── README.md │ │ │ ├── ViewPager和Fragment使用过程中会遇到哪些问题.md │ │ │ ├── 手把手教你如何解决as jar包冲突.md │ │ │ ├── 机型适配之痛.md │ │ │ ├── 终极解决ViewPager.setCurrentItem中间页面过多解决方案.md │ │ │ ├── 解决字体适配.md │ │ │ ├── 软键盘顶出去解决方案.md │ │ │ └── 迭代开发的时候如何向前兼容新旧接口?.md │ │ ├── 性能优化/ │ │ │ ├── Android应用UI性能分析.md │ │ │ ├── App应用启动分析与优化.md │ │ │ ├── README.md │ │ │ ├── 与IPC机制相关面试题.md │ │ │ ├── 与性能优化相关试题一.md │ │ │ ├── 与性能优化相关试题三.md │ │ │ ├── 与性能优化相关试题二.md │ │ │ └── 内存泄漏监测.md │ │ ├── 源码分析/ │ │ │ ├── Android源码编译实现静默安装和静默偷拍.md │ │ │ ├── README.md │ │ │ ├── Volley源码剖析.md │ │ │ ├── okhttp内核剖析.md │ │ │ └── 注解框架内部实现原理.md │ │ ├── 登陆注册/ │ │ │ ├── Oauth的实现原理.md │ │ │ ├── README.md │ │ │ ├── Token的实际意义.md │ │ │ └── 微信扫码登录内部实现原理.md │ │ ├── 经验分享/ │ │ │ ├── 2016年4月某公司面试题及面试流程.md │ │ │ ├── 2017届实习生招聘面经.md │ │ │ ├── Andorid-15k+的面试题.md │ │ │ ├── Android 暑期实习生面试经验谈.md │ │ │ ├── Android 曲折的求职之路.md │ │ │ ├── BAT无线工程师面试流程详细解析.md │ │ │ ├── README.md │ │ │ ├── 一个五年Android开发者百度、阿里、聚美、映客的面试心经.md │ │ │ ├── 一个程序员的血泪史.md │ │ │ ├── 互联网公司面试经验总结.md │ │ │ ├── 互联网巨头BAT3内部员工的真实状况.md │ │ │ ├── 史上最全 Android 面试资料集合.md │ │ │ ├── 国内一线互联网公司内部面试题库.md │ │ │ ├── 工作三年后,我选择离开腾讯.md │ │ │ ├── 我为什么离开锤子科技?.md │ │ │ ├── 我为什么要离开华为?.md │ │ │ ├── 扫清Android面试障碍.md │ │ │ ├── 技术硬碰硬—阳哥带你玩转上海Android招聘市场.md │ │ │ ├── 杭州找 Android 工作的点点滴滴.md │ │ │ ├── 给培训班出来的一点不成熟的小建议.md │ │ │ ├── 腾讯公司程序员面试题及答案详解.md │ │ │ ├── 阿里+百度+CVTE面经合集.md │ │ │ └── 面试心得与总结:BAT、网易、蘑菇街 .md │ │ ├── 网络编程/ │ │ │ ├── Android客户端和服务端如何使用Token和Session.md │ │ │ ├── README.md │ │ │ ├── 推送原理.md │ │ │ ├── 简单阐述一下及时推送原理?.md │ │ │ └── 阐述一下对XMPP协议理解以及优缺点?.md │ │ └── 面试技巧/ │ │ ├── README.md │ │ ├── 我在面试中最喜欢问开发者的问题,和回答思路.md │ │ ├── 程序员面试宝典.md │ │ └── 罗永浩新东方万字求职信.md │ ├── AndroidNote/ │ │ ├── AdavancedPart/ │ │ │ ├── 1.热修复实现(一).md │ │ │ ├── 2.热修复实现(二).md │ │ │ ├── 3.热修复_addAssetPath不同版本区别原因(三).md │ │ │ ├── ART与Dalvik.md │ │ │ ├── Android WorkManager.md │ │ │ ├── Android6.0权限系统.md │ │ │ ├── Android卸载反馈.md │ │ │ ├── Android启动模式详解.md │ │ │ ├── Android开发不申请权限来使用对应功能.md │ │ │ ├── Android开发中的MVP模式详解.md │ │ │ ├── ApplicationId vs PackageName.md │ │ │ ├── BroadcastReceiver安全问题.md │ │ │ ├── ConstraintLaayout简介.md │ │ │ ├── Handler导致内存泄露分析.md │ │ │ ├── Library项目中资源id使用case时报错.md │ │ │ ├── Mac下配置adb及Android命令.md │ │ │ ├── MaterialDesign使用.md │ │ │ ├── RecyclerView专题.md │ │ │ ├── 如何让Service常驻内存.md │ │ │ ├── 屏幕适配之百分比方案详解.md │ │ │ ├── 布局优化.md │ │ │ ├── 性能优化.md │ │ │ ├── 注解使用.md │ │ │ └── 通过Hardware Layer提高动画性能.md │ │ ├── AndroidStudioCourse/ │ │ │ ├── Android Studio你可能不知道的操作.md │ │ │ ├── AndroidStudio中进行ndk开发.md │ │ │ ├── AndroidStudio使用教程(第一弹).md │ │ │ ├── AndroidStudio使用教程(第七弹).md │ │ │ ├── AndroidStudio使用教程(第三弹).md │ │ │ ├── AndroidStudio使用教程(第二弹).md │ │ │ ├── AndroidStudio使用教程(第五弹).md │ │ │ ├── AndroidStudio使用教程(第六弹).md │ │ │ ├── AndroidStudio使用教程(第四弹).md │ │ │ └── AndroidStudio提高Build速度.md │ │ ├── Android基础/ │ │ │ ├── Activity详细解析.md │ │ │ ├── Android-SQLite的基本使用.md │ │ │ ├── Android中相机与相册的详细使用.md │ │ │ ├── Android异步任务机制之AsycTask.md │ │ │ ├── Android数据存储的五种方式.md │ │ │ ├── Android获取SHA1.md │ │ │ ├── Android跟随手指移动的view.md │ │ │ ├── BroadcastReceiver详细解析.md │ │ │ ├── ContentProvider实例详解.md │ │ │ ├── Handler,Looper,MessageQueue关系.md │ │ │ ├── IntentService详细解析.md │ │ │ ├── RecyclerView的简介.md │ │ │ ├── Service详细解析.md │ │ │ ├── tablayout记录.md │ │ │ ├── test.kt │ │ │ └── 图片缓存原理.md │ │ ├── Android开源框架相关/ │ │ │ ├── Android当下最流行的开源框架总结.md │ │ │ ├── Android黑科技——ButterKnifeZelezny.md │ │ │ ├── Picasso-android-load-image-layout.md │ │ │ ├── RxJava+retrofit2实现安卓中网络操作.md │ │ │ ├── 一款Android的Log、Toast的库.md │ │ │ └── 动态申请权限库:easypermissions使用与源码解析.md │ │ ├── Android性能优化相关/ │ │ │ └── LeakCanary工作过程以及原理.md │ │ ├── Android打包相关/ │ │ │ ├── Android发布sdk到jcenter.md │ │ │ └── Android将library打包成jar文件或aar文件.md │ │ ├── Android报错记录/ │ │ │ ├── Android报错-Manifest merger failed with multiple errors, see logs.md │ │ │ └── Android报错2.md │ │ ├── Android编译器相关/ │ │ │ ├── AndroidStudio使用教程(第一弹).md │ │ │ ├── AndroidStudio使用教程(第七弹).md │ │ │ ├── AndroidStudio使用教程(第三弹).md │ │ │ ├── AndroidStudio使用教程(第二弹).md │ │ │ ├── AndroidStudio使用教程(第五弹).md │ │ │ ├── AndroidStudio使用教程(第六弹).md │ │ │ └── AndroidStudio使用教程(第四弹).md │ │ ├── Android自定义View/ │ │ │ ├── Android事件分发机制.md │ │ │ ├── PathMeasure.md │ │ │ ├── 三阶贝塞尔曲线.md │ │ │ ├── 二阶贝塞尔曲线.md │ │ │ ├── 自定义ViewGroup入门.md │ │ │ ├── 自定义View——CameraView.md │ │ │ ├── 自定义View——CheckView.md │ │ │ ├── 自定义View——CircleView.md │ │ │ ├── 自定义View——FlowLayout.md │ │ │ ├── 自定义View——PieView.md │ │ │ ├── 自定义View入门.md │ │ │ └── 自定义view——sideslipListView.md │ │ ├── Android进阶/ │ │ │ ├── AndroidStudio导入工程一直在Building的解决方案.md │ │ │ ├── Android中的动画.md │ │ │ ├── Android内存泄漏总结.md │ │ │ ├── Android性能优化.md │ │ │ ├── Android项目总结.md │ │ │ ├── Android项目总结2.md │ │ │ ├── Android项目总结3.md │ │ │ ├── Handler引起的内存泄漏以及分析.md │ │ │ ├── MVP+RxJava+Retrofit2+Dagger实战.md │ │ │ ├── Recyclerview和Listview的异同.md │ │ │ ├── iterm2+vim打造完美终端.md │ │ │ ├── jvm-serializers.md │ │ │ ├── 基于OTP算法的双向认证.md │ │ │ ├── 检查app是否有推送权限.md │ │ │ ├── 深入了解MVXX模式.md │ │ │ └── 自定义RadioGroup.md │ │ ├── Android面试相关/ │ │ │ ├── Android5.0-6.0-7.0新特性.md │ │ │ ├── Android中常见面试题.md │ │ │ ├── Android中弱引用与软引用.md │ │ │ ├── Android图片三级缓存.md │ │ │ ├── Android推送实现原理.md │ │ │ ├── Asset目录与res目录的区别.md │ │ │ ├── JSON的定义.md │ │ │ ├── Java中Error和Exception.md │ │ │ ├── ListView性能优化.md │ │ │ ├── Service保活.md │ │ │ ├── 如何实现Activity切换的动画.md │ │ │ ├── 如何提高Activity启动速度.md │ │ │ ├── 如何终止App的运行.md │ │ │ └── 面试题.md │ │ ├── AppPublish/ │ │ │ ├── Android应用发布.md │ │ │ ├── Zipalign优化.md │ │ │ └── 使用Jenkins实现自动化打包.md │ │ ├── ArchitectureComponents/ │ │ │ ├── 1.简介(一).md │ │ │ ├── 2.集成(二).md │ │ │ ├── 3.Lifecycle(三).md │ │ │ ├── 4.LiveData(四).md │ │ │ ├── 5.ViewModel(五).md │ │ │ ├── 6.Room(六).md │ │ │ └── 7.PagingLibrary(七).md │ │ ├── BasicKnowledge/ │ │ │ ├── Android入门介绍.md │ │ │ ├── Android动画.md │ │ │ ├── Android四大组件之ContentProvider.md │ │ │ ├── Android四大组件之Service.md │ │ │ ├── Android基础面试题.md │ │ │ ├── Android编码规范.md │ │ │ ├── Ant打包.md │ │ │ ├── Bitmap优化.md │ │ │ ├── Fragment专题.md │ │ │ ├── Home键监听.md │ │ │ ├── HttpClient执行Get和Post请求.md │ │ │ ├── JNI_C语言基础.md │ │ │ ├── JNI基础.md │ │ │ ├── ListView专题.md │ │ │ ├── Parcelable及Serializable.md │ │ │ ├── PopupWindow细节.md │ │ │ ├── SDK Manager无法更新的问题.md │ │ │ ├── Scroller简介.md │ │ │ ├── ScrollingTabs.md │ │ │ ├── Selector使用.md │ │ │ ├── SlidingMenu.md │ │ │ ├── String格式化.md │ │ │ ├── TextView跑马灯效果.md │ │ │ ├── WebView总结.md │ │ │ ├── Widget(窗口小部件).md │ │ │ ├── Wifi状态监听.md │ │ │ ├── XmlPullParser.md │ │ │ ├── adb logcat使用简介.md │ │ │ ├── 下拉刷新ListView.md │ │ │ ├── 代码混淆.md │ │ │ ├── 任务管理器(ActivityManager).md │ │ │ ├── 修改系统组件样式.md │ │ │ ├── 内存泄漏.md │ │ │ ├── 多线程断点下载.md │ │ │ ├── 安全退出应用程序.md │ │ │ ├── 屏幕适配.md │ │ │ ├── 应用后台唤醒后数据的刷新.md │ │ │ ├── 应用安装.md │ │ │ ├── 开发中Log的管理.md │ │ │ ├── 开发中异常的处理.md │ │ │ ├── 快捷方式工具类.md │ │ │ ├── 手机摇晃.md │ │ │ ├── 搜索框.md │ │ │ ├── 数据存储.md │ │ │ ├── 文件上传.md │ │ │ ├── 来电号码归属地提示框.md │ │ │ ├── 来电监听及录音.md │ │ │ ├── 横向ListView.md │ │ │ ├── 滑动切换Activity(GestureDetector).md │ │ │ ├── 病毒.md │ │ │ ├── 知识大杂烩.md │ │ │ ├── 短信广播接收者.md │ │ │ ├── 程序的启动、卸载和分享.md │ │ │ ├── 竖着的Seekbar.md │ │ │ ├── 自定义Toast.md │ │ │ ├── 自定义控件.md │ │ │ ├── 自定义状态栏通知.md │ │ │ ├── 自定义背景.md │ │ │ ├── 获取位置(LocationManager).md │ │ │ ├── 获取应用程序缓存及一键清理.md │ │ │ ├── 获取手机中所有安装的程序.md │ │ │ ├── 获取手机及SD卡可用存储空间.md │ │ │ ├── 获取联系人.md │ │ │ ├── 读取用户logcat日志.md │ │ │ ├── 资源文件拷贝的三种方式.md │ │ │ ├── 超级管理员(DevicePoliceManager).md │ │ │ ├── 锁屏以及解锁监听.md │ │ │ ├── 零权限上传数据.md │ │ │ ├── 音量及屏幕亮度调节.md │ │ │ └── 黑名单挂断电话及删除电话记录.md │ │ ├── Dagger2/ │ │ │ ├── 1.Dagger2简介(一).md │ │ │ ├── 2.Dagger2入门demo(二).md │ │ │ ├── 3.Dagger2入门demo扩展(三).md │ │ │ ├── 4.Dagger2单例(四).md │ │ │ ├── 5.Dagger2Lay和Provider(五).md │ │ │ ├── 6.Dagger2Android示例代码(六).md │ │ │ ├── 7.Dagger2之dagger-android(七).md │ │ │ ├── 8.Dagger2与MVP(八).md │ │ │ └── 9.Dagger2原理分析(九).md │ │ ├── Git/ │ │ │ └── git详细教程.md │ │ ├── Go/ │ │ │ └── Go的练习代码.md │ │ ├── Gradle&Maven/ │ │ │ ├── Gradle专题.md │ │ │ └── 发布library到Maven仓库.md │ │ ├── IOSNote/ │ │ │ └── Ios上架app需要的图标尺寸.md │ │ ├── IPC.md │ │ ├── ImageLoaderLibrary/ │ │ │ ├── Glide简介(上).md │ │ │ ├── Glide简介(下).md │ │ │ └── 图片加载库比较.md │ │ ├── JavaKnowledge/ │ │ │ ├── Base64加密.md │ │ │ ├── Git简介.md │ │ │ ├── HashMap实现原理分析.md │ │ │ ├── Http与Https的区别.md │ │ │ ├── JVM垃圾回收机制.md │ │ │ ├── Java基础面试题.md │ │ │ ├── MD5加密.md │ │ │ ├── MVC与MVP及MVVM.md │ │ │ ├── RMB大小写转换.md │ │ │ ├── Top-K问题.md │ │ │ ├── Vim使用教程.md │ │ │ ├── hashCode与equals.md │ │ │ ├── volatile和Synchronized区别.md │ │ │ ├── 八种排序算法.md │ │ │ ├── 剑指Offer(上).md │ │ │ ├── 剑指Offer(下).md │ │ │ ├── 动态代理.md │ │ │ ├── 单例的最佳实现方式.md │ │ │ ├── 原子性、可见性以及有序性.md │ │ │ ├── 常用命令行大全.md │ │ │ ├── 常见算法.md │ │ │ ├── 强引用、软引用、弱引用、虚引用.md │ │ │ ├── 数据加密及解密.md │ │ │ ├── 数据结构.md │ │ │ ├── 死锁.md │ │ │ ├── 生产者消费者.md │ │ │ ├── 算法的复杂度.md │ │ │ ├── 线程池简介.md │ │ │ ├── 网络请求相关内容总结.md │ │ │ ├── 获取今后多少天后的日期.md │ │ │ └── 设计模式.md │ │ ├── JavaNote/ │ │ │ ├── Javaee/ │ │ │ │ └── Spring-boot入门.md │ │ │ ├── Java相关/ │ │ │ │ ├── ArrayList、LinkedList、Vector的异同.md │ │ │ │ ├── Des加密算法.md │ │ │ │ ├── HashTable和HashMap的异同.md │ │ │ │ ├── JVM类加载器.md │ │ │ │ ├── JVM虚拟机基础知识.md │ │ │ │ ├── Java中Error和Exception.md │ │ │ │ ├── Java利用ExecutorService实现同步执行大量线程.md │ │ │ │ ├── Java利用listener实现回调,即观察者模式.md │ │ │ │ ├── Java回调的原理与实现.md │ │ │ │ ├── Java基础知识.md │ │ │ │ ├── Java注解的编写与Java的反射机制.md │ │ │ │ ├── 发布jar包到Maven中央仓库.md │ │ │ │ └── 面向对象的六大原则以及常见的十七种设计模式.md │ │ │ └── 设计模式相关/ │ │ │ ├── 单例模式.md │ │ │ ├── 单例模式的四种实现方式.md │ │ │ ├── 观察者模式.md │ │ │ └── 设计模式概括.md │ │ ├── KotlinCourse/ │ │ │ ├── .idea/ │ │ │ │ ├── KotlinCourse.iml │ │ │ │ ├── misc.xml │ │ │ │ ├── modules.xml │ │ │ │ └── workspace.xml │ │ │ ├── Kotlin学习教程(一).md │ │ │ ├── Kotlin学习教程(七).md │ │ │ ├── Kotlin学习教程(三).md │ │ │ ├── Kotlin学习教程(九).md │ │ │ ├── Kotlin学习教程(二).md │ │ │ ├── Kotlin学习教程(五).md │ │ │ ├── Kotlin学习教程(八).md │ │ │ ├── Kotlin学习教程(六).md │ │ │ ├── Kotlin学习教程(十).md │ │ │ └── Kotlin学习教程(四).md │ │ ├── Kotlin相关/ │ │ │ └── Kotlin-for-android.md │ │ ├── Linux/ │ │ │ └── Android-GitLabCi配置.md │ │ ├── MacNote/ │ │ │ ├── Mac平台重新设置MySQL的root密码.md │ │ │ ├── SSH原理与应用.md │ │ │ ├── mac上常用命令.md │ │ │ ├── mac本地生成ssh-key.md │ │ │ ├── mac终端与服务器保持连接.md │ │ │ ├── nodejs与npm的更新.md │ │ │ ├── paw-for-mac.md │ │ │ ├── 一些mac上面安装环境的指令.md │ │ │ ├── 如何在mac上安装java1-8.md │ │ │ └── 项目中遇到的单词.md │ │ ├── READMENote.md │ │ ├── ReactNative相关/ │ │ │ ├── React Native 的ES5 ES6写法对照表.md │ │ │ ├── ReactNative入门.md │ │ │ ├── ReactNative利用CodePush实现热更新.md │ │ │ ├── ReactNative报错记录.md │ │ │ ├── ReactNative调试心得.md │ │ │ ├── Touchable系列组建讲解.md │ │ │ └── 短信验证码倒计时控件.md │ │ ├── RxJavaPart/ │ │ │ ├── 1.RxJava详解(一).md │ │ │ ├── 2.RxJava详解(二).md │ │ │ ├── 3.RxJava详解(三).md │ │ │ ├── 4.RxJava详解之执行原理(四).md │ │ │ ├── 5.RxJava详解之操作符执行原理(五).md │ │ │ ├── 6.RxJava详解之线程调度原理(六).md │ │ │ └── 7.RxJava系列全家桶.md │ │ ├── ScriptNote/ │ │ │ ├── GitHub基础操作.md │ │ │ ├── 一篇文章学懂Shell脚本.md │ │ │ ├── 封装一些GitHub常用命令.md │ │ │ └── 简单的Shell脚本.md │ │ ├── SourceAnalysis/ │ │ │ ├── Activity启动过程.md │ │ │ ├── Activity界面绘制过程详解.md │ │ │ ├── Android Touch事件分发详解.md │ │ │ ├── AsyncTask详解.md │ │ │ ├── InstantRun详解.md │ │ │ ├── ListView源码分析.md │ │ │ ├── Netowork/ │ │ │ │ ├── HttpURLConnection与HttpClient.md │ │ │ │ ├── HttpURLConnection详解.md │ │ │ │ ├── Retrofit详解(上).md │ │ │ │ ├── Retrofit详解(下).md │ │ │ │ ├── Volley源码分析.md │ │ │ │ └── volley-retrofit-okhttp之我们该如何选择网路框架.md │ │ │ ├── VideoView源码分析.md │ │ │ ├── View绘制过程详解.md │ │ │ ├── butterknife源码详解.md │ │ │ └── 自定义View详解.md │ │ ├── Tools&Library/ │ │ │ ├── Android开发工具及类库.md │ │ │ ├── Github个人主页绑定域名.md │ │ │ ├── MAT内存分析.md │ │ │ ├── Markdown学习手册.md │ │ │ ├── 性能优化相关工具.md │ │ │ ├── 目前流行的开发组合.md │ │ │ └── 调试平台Flipper.md │ │ ├── VideoDevelopment/ │ │ │ ├── Android WebRTC简介.md │ │ │ ├── Android音视频开发.md │ │ │ ├── DLNA简介.md │ │ │ ├── 搭建nginx+rtmp服务器.md │ │ │ ├── 视频播放相关内容总结.md │ │ │ ├── 视频解码之软解与硬解.md │ │ │ └── 音视频基础知识.md │ │ ├── WebNote/ │ │ │ ├── MySQL相关/ │ │ │ │ ├── ERROR-1045-(28000)--Access-denied-for-user-'debian-sys-maint'@'localho.md │ │ │ │ ├── Error--ER_TRUNCATED_WRONG_VALUE_FOR_FIELD.md │ │ │ │ ├── Mysql导出数据库、表(有无数据).md │ │ │ │ ├── mysql基础操作.md │ │ │ │ └── 云服务器linux下安装MySQL.md │ │ │ └── NodeJS相关/ │ │ │ ├── koa框架对post内容读取并解析.md │ │ │ ├── nodejs查询数据库后将值返回前端.md │ │ │ ├── nodejs项目在云服务器的部署.md │ │ │ ├── test.html │ │ │ └── 淘宝cnpm.md │ │ ├── webRTC相关/ │ │ │ ├── WebRTC-Android源码解析.md │ │ │ ├── WebRTC——Android入门.md │ │ │ ├── WebRTC——AudioRenderer解析.md │ │ │ ├── WebRTC——AudioSource、VideoSource解析.md │ │ │ ├── WebRTC——AudioTrack-VideoTrack解析.md │ │ │ ├── WebRTC——IceCandidate、SdpObserver、CameraSession解析.md │ │ │ ├── WebRTC——MediaSource-java解析.md │ │ │ ├── WebRTC——MeidaStreamTrack解析.md │ │ │ ├── WebRTC——PeerConnection-java解析.md │ │ │ ├── WebRTC——PeerConnectionFactory-java解析.md │ │ │ ├── WebRTC——VideoFileRenderer解析.md │ │ │ └── WebRTC——VideoRenderer解析.md │ │ ├── 内存性能.md │ │ └── 网络协议/ │ │ ├── SSH原理与应用.md │ │ ├── 浅析Hessian协议.md │ │ ├── 浅析RPC协议.md │ │ ├── 浅析dubbo服务.md │ │ └── 浅析socket.md │ ├── interview/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── android/ │ │ │ ├── README.md │ │ │ ├── SUMMARY.md │ │ │ ├── activity-view-window.md │ │ │ ├── arch.md │ │ │ ├── binder.md │ │ │ ├── broadcast.md │ │ │ ├── canvas.md │ │ │ ├── draw.md │ │ │ ├── event.md │ │ │ ├── eventbus.md │ │ │ ├── handler.md │ │ │ ├── intent.md │ │ │ ├── keep-live.md │ │ │ ├── launchmod.md │ │ │ ├── lifecicle.md │ │ │ ├── listview.md │ │ │ ├── okhttp.md │ │ │ ├── optimize.md │ │ │ ├── push.md │ │ │ ├── questions.md │ │ │ └── version.md │ │ ├── architecture/ │ │ │ ├── 1-cap.md │ │ │ ├── README.md │ │ │ ├── concurrent/ │ │ │ │ ├── 1-flow_control.md │ │ │ │ └── README.md │ │ │ ├── design/ │ │ │ │ ├── 1-tinyURL.md │ │ │ │ └── README.md │ │ │ └── distributed/ │ │ │ ├── 1-session.md │ │ │ ├── 2-cache.md │ │ │ ├── 3-lock.md │ │ │ ├── 4-transaction.md │ │ │ ├── 5-mq.md │ │ │ ├── 6-zk.md │ │ │ ├── 7-kafka.md │ │ │ ├── 8-rpc.md │ │ │ ├── 9-dubbo.md │ │ │ └── README.md │ │ ├── basic/ │ │ │ ├── README.md │ │ │ ├── algo/ │ │ │ │ ├── README.md │ │ │ │ ├── algo.md │ │ │ │ ├── hash.md │ │ │ │ ├── kmp.md │ │ │ │ ├── mst.md │ │ │ │ ├── path.md │ │ │ │ ├── questions.md │ │ │ │ ├── search.md │ │ │ │ ├── skip_list.md │ │ │ │ ├── sort.md │ │ │ │ └── tree.md │ │ │ ├── cryptology.md │ │ │ ├── database/ │ │ │ │ ├── README.md │ │ │ │ ├── concurrent_control.md │ │ │ │ ├── index.md │ │ │ │ ├── innodb.md │ │ │ │ ├── join.md │ │ │ │ ├── mysql.md │ │ │ │ ├── questions.md │ │ │ │ ├── redis.md │ │ │ │ ├── sql.md │ │ │ │ └── transaction.md │ │ │ ├── net/ │ │ │ │ ├── README.md │ │ │ │ ├── base_protocol.md │ │ │ │ ├── http.md │ │ │ │ ├── https.md │ │ │ │ ├── ip.md │ │ │ │ ├── osi.md │ │ │ │ ├── questions.md │ │ │ │ └── tcp.md │ │ │ └── op/ │ │ │ ├── README.md │ │ │ ├── arch.md │ │ │ ├── concurrency.md │ │ │ ├── device.md │ │ │ ├── disk.md │ │ │ ├── interrupt.md │ │ │ ├── io.md │ │ │ ├── linux.md │ │ │ ├── memory.md │ │ │ ├── os.md │ │ │ └── questions.md │ │ ├── fromwork/ │ │ │ ├── README.md │ │ │ ├── mybatis/ │ │ │ │ ├── 1-question.md │ │ │ │ ├── 2-cache.md │ │ │ │ └── 3-proxy.md │ │ │ └── spring/ │ │ │ ├── 1-ioc.md │ │ │ ├── 2-design-partten.md │ │ │ ├── 3-aop.md │ │ │ └── README.md │ │ └── java/ │ │ ├── 1-oop.md │ │ ├── 17-questions.md │ │ ├── 2-operator.md │ │ ├── 3-exception.md │ │ ├── 4-generics.md │ │ ├── 5-object.md │ │ ├── 6-StringBuilder.md │ │ ├── 7-proxy.md │ │ ├── README.md │ │ ├── collection/ │ │ │ ├── 1-collection.md │ │ │ ├── 2-HashMap.md │ │ │ ├── 3-Concurrenthashmap.md │ │ │ └── 4-BlockQueue.md │ │ ├── concurrent/ │ │ │ ├── 1-thread.md │ │ │ ├── 2-volatile.md │ │ │ ├── 3-synchronized.md │ │ │ ├── 4-AQS.md │ │ │ ├── 5-threadlocal.md │ │ │ ├── 6-interrupt.md │ │ │ └── 7-CountDownLatch.md │ │ ├── gc/ │ │ │ ├── 11-jvm-gc.md │ │ │ └── 12-jvm-object-life-cycle.md │ │ └── jvm/ │ │ ├── 1-jvm-class-load-init.md │ │ ├── 2-jvm-class-loader.md │ │ ├── 3-dispatcher.md │ │ └── 4-jvm-architecture.md │ ├── self.md │ └── sources/ │ ├── JavaGarbageCollection.md │ ├── JavaMemoryMode.md │ ├── activity_onnewIntent.md │ ├── adsl.md │ ├── app_start_step.md │ ├── application.md │ ├── application_service.md │ ├── asynctask.md │ ├── binder.md │ ├── butterknife.md │ ├── davik_art.md │ ├── design_v28.md │ ├── eventbus.md │ ├── fifo.md │ ├── foreach.md │ ├── fragment_lazy_load.md │ ├── frame.md │ ├── glide.md │ ├── handle_leak.md │ ├── hash_confict.md │ ├── hotfix.md │ ├── imagedownload.md │ ├── iterationAndroidrecursion.md │ ├── java8.md │ ├── javaCopy.md │ ├── javabasic.md │ ├── javacollection.md │ ├── killprocess_system_exit.md │ ├── kotlin/ │ │ └── builderModel.kt │ ├── livedata.md │ ├── lru.md │ ├── media_player.md │ ├── netsafe.md │ ├── okhttp.md │ ├── onMeasure.md │ ├── recyclerView_listview.md │ ├── reference.md │ ├── requestlayout_invalidate_postInvalidate.md │ ├── retrofit.md │ ├── rxjavademo.md │ ├── seven_design_principles.md │ ├── singleInstance.md │ ├── synchronized_lock.md │ ├── thread_principle.md │ ├── tomcat_cache.java │ ├── tomcat_lru_cache.java │ ├── transactiontoolargeexception.md │ ├── tree.md │ ├── tu.md │ ├── v4_v7_v8_v13.md │ ├── view_root.md │ ├── viewmodel.md │ ├── volatile.md │ ├── volley_algorithm.md │ ├── wait_sleep.md │ ├── weakHashMap.md │ ├── workManager.md │ └── yield_join.md ├── data/ │ └── java-recommended-books.md ├── dataStructures-algorithms/ │ ├── Backtracking-NQueens.md │ ├── 公司真题.md │ ├── 几道常见的子符串算法题.md │ ├── 几道常见的链表算法题.md │ ├── 剑指offer部分编程题.md │ ├── 数据结构.md │ └── 算法学习资源推荐.md ├── database/ │ ├── MySQL Index.md │ ├── MySQL.md │ ├── MySQL高性能优化规范建议.md │ ├── Redis/ │ │ ├── Redis.md │ │ ├── Redis持久化.md │ │ ├── Redlock分布式锁.md │ │ └── 如何做可靠的分布式锁,Redlock真的可行么.md │ ├── 一千行MySQL命令.md │ ├── 一条sql语句在mysql中如何执行的.md │ └── 事务隔离级别(图文详解).md ├── essential-content-for-interview/ │ ├── BATJrealInterviewExperience/ │ │ └── 5面阿里,终获offer.md │ ├── MostCommonJavaInterviewQuestions/ │ │ ├── 第一周(2018-8-7).md │ │ ├── 第二周(2018-8-13).md │ │ └── 第四周(2018-8-30).md │ ├── PreparingForInterview/ │ │ ├── JavaInterviewLibrary.md │ │ ├── JavaProgrammerNeedKnow.md │ │ ├── interviewPrepare.md │ │ ├── 如果面试官问你“你有什么问题问我吗?”时,你该如何回答.md │ │ ├── 程序员的简历之道.md │ │ └── 美团面试常见问题总结.md │ ├── 手把手教你用Markdown写一份高质量的简历.md │ ├── 简历模板.md │ └── 面试必备之乐观锁与悲观锁.md ├── github-trending/ │ ├── 2018-12.md │ ├── 2019-1.md │ ├── 2019-2.md │ ├── 2019-3.md │ └── JavaGithubTrending.md ├── java/ │ ├── ArrayList-Grow.md │ ├── ArrayList.md │ ├── BIO-NIO-AIO.md │ ├── Basis/ │ │ ├── Arrays,CollectionsCommonMethods.md │ │ └── final、static、this、super.md │ ├── HashMap.md │ ├── J2EE基础知识.md │ ├── Java IO与NIO.md │ ├── Java基础知识.md │ ├── Java编程规范.md │ ├── Java虚拟机(jvm).md │ ├── Java集合框架常见面试题总结.md │ ├── LinkedList.md │ ├── Multithread/ │ │ ├── AQS.md │ │ ├── Atomic.md │ │ ├── BATJ都爱问的多线程面试题.md │ │ ├── ConcurrentProgramming1-并发编程基础知识.md │ │ └── 并发容器总结.md │ ├── What's New in JDK8/ │ │ ├── JDK8接口规范-静态、默认方法.md │ │ ├── Java8Tutorial.md │ │ ├── Java8教程推荐.md │ │ ├── Lambda表达式.md │ │ ├── README.md │ │ ├── Stream.md │ │ ├── 改进的类型推断.md │ │ └── 通过反射获得方法的参数信息.md │ ├── synchronized.md │ ├── 可能是把Java内存区域讲的最清楚的一篇文章.md │ ├── 多线程系列.md │ ├── 搞定JVM垃圾回收就是这么简单.md │ └── 这几道Java集合框架面试题几乎必问.md ├── network/ │ ├── HTTPS中的TLS.md │ ├── 干货:计算机网络知识总结.md │ └── 计算机网络.md ├── notes/ │ ├── Leetcode-Database 题解.md │ ├── SQL.md │ ├── 攻击技术.md │ ├── 数据库系统原理.md │ ├── 构建工具.md │ ├── 正则表达式.md │ ├── 计算机操作系统 - 内存管理.md │ ├── 计算机操作系统 - 概述.md │ ├── 计算机操作系统 - 死锁.md │ ├── 计算机操作系统 - 目录.md │ ├── 计算机操作系统 - 目录1.md │ ├── 计算机操作系统 - 设备管理.md │ ├── 计算机操作系统 - 进程管理.md │ └── 计算机操作系统 - 链接.md ├── operating-system/ │ ├── Shell.md │ └── 后端程序员必备的Linux基础知识.md ├── system-design/ │ ├── data-communication/ │ │ ├── dubbo.md │ │ ├── message-queue.md │ │ ├── rabbitmq.md │ │ └── 数据通信(RESTful、RPC、消息队列).md │ ├── framework/ │ │ ├── SpringBean.md │ │ ├── SpringMVC 工作原理详解.md │ │ ├── Spring学习与面试.md │ │ ├── ZooKeeper.md │ │ └── ZooKeeper数据模型和常见命令.md │ ├── website-architecture/ │ │ ├── 8 张图读懂大型网站技术架构.md │ │ ├── 【面试精选】关于大型网站系统架构你不得不懂的10个问题.md │ │ └── 分布式.md │ └── 设计模式.md └── tools/ ├── Docker-Image.md ├── Docker.md └── Git.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 pengMaster Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Java Android学习/面试指南

| Flutter| Android | Java | Kotlin |  面试  | 网络 | 系统 | 系统设计 |  工具  | 数据库 | 算法 |TODO | | :--------:| :--------: | :---------: | :---------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :------:|:------:| | [ :iphone:](#Flutter)| [:pencil2:](#Android) | [:coffee:](#Java)|[:unlock:](#Kotlin) | [:memo:](#面试指南) |[:cloud:](#网络)| [:computer:](#操作系统)| [:bulb:](#系统设计)| [:wrench:](#工具)| [:floppy_disk:](#数据库)| [:pencil2:](#数据结构与算法) | [:page_facing_up:](#TODO学习清单) |
## 目录 - [Android](#Android) - [基础](#基础知识) - [进阶](#进阶) - [Gradle相关](#Gradle相关) - [自定义View](#自定义View) - [插件化相关](#插件化相关) - [热修复相关](#热修复相关) - [编译器相关](#编译器相关) - [框架源码分析](#框架源码分析) - [性能优化](#性能优化) - [Android常见设计模式](#Android常见设计模式) - [音视频开发](#音视频开发) - [开源框架](#开源框架) - [应用发布](#应用发布) - [打包](#打包) - [原生功能讲解](docs/android/AndroidNote/READMENote.md) - [Java](#java) - [基础](#基础) - [容器](#容器) - [并发](#并发) - [JVM](#jvm) - [I/O](#io) - [Java 8](#java-8) - [编程规范](#编程规范) - [TODO学习清单](#TODO学习清单) - [Kotlin学习](#Kotlin) - [Flutter学习](#Flutter) - [面试指南](#面试指南) - [备战面试](#备战面试) - [常见面试题总结](#常见面试题总结) - [面经](#面经) - [Android面试专场](docs/android/Android-Interview/README.md) - [网络协议](#网络) - [操作系统](#操作系统) - [Linux相关](#linux相关) - [计算机操作系统](#计算机操作系统) - [数据结构与算法](#数据结构与算法) - [数据结构](#数据结构) - [算法](#算法) - [数据库](#数据库) - [MySQL](#mysql) - [Redis](#redis) - [数据库系统原理](docs/notes/数据库系统原理.md) - [SQL](docs/notes/SQL.md) - [Leetcode-Database 题解](docs/notes/Leetcode-Database%20题解.md) - [系统设计](#系统设计) - [设计模式](#设计模式) - [常用框架](#常用框架) - [数据通信](#数据通信) - [网站架构](#网站架构) - [攻击技术](docs/notes/攻击技术.md) - [工具](#工具) - [Git](#git) - [Docker](#Docker) - [构建工具](docs/notes/构建工具.md) - [正则表达式](docs/notes/正则表达式.md) - [常见问题](docs/android/interview/README.md) ## Android ### 基础知识 * [Activity详细解析](docs/android/AndroidNote/Android基础/Activity详细解析.md) * [Service详细解析](docs/android/AndroidNote/Android基础/Service详细解析.md) * [IntentService详细解析](docs/android/AndroidNote/Android基础/IntentService详细解析.md) * [IntentService原理解析文章](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=401611665&idx=1&sn=9b6b1f2924d4adfe4e89a322ab53df9c&scene=21#wechat_redirect) * [ContentProvider实例详解](docs/android/AndroidNote/Android基础/ContentProvider实例详解.md) * [BroadcastReceiver详细解析](docs/android/AndroidNote/Android基础/BroadcastReceiver详细解析.md) * [Android异步任务机制之AsycTask](docs/android/AndroidNote/Android基础/Android异步任务机制之AsycTask.md) * [Handler,Looper,MessageQueue关系](docs/android/AndroidNote/Android基础/Handler,Looper,MessageQueue关系.md) * [Android-SQLite的基本使用](docs/android/AndroidNote/Android基础/Android-SQLite的基本使用.md) * [Android系统相机与相册的使用](docs/android/AndroidNote/Android基础/Android中相机与相册的详细使用.md) * [图片缓存原理](docs/android/AndroidNote/Android基础/图片缓存原理.md) * [Android数据存储的五种方式](docs/android/AndroidNote/Android基础/Android数据存储的五种方式.md) * [Android跟随手指移动的View](docs/android/AndroidNote/Android基础/Android跟随手指移动的view.md) * [RecyclerView的使用](docs/android/AndroidNote/Android基础/RecyclerView的简介.md) * [Android获取SHA1](docs/android/AndroidNote/Android基础/Android获取SHA1.md) * [Recyclerview和Listview的异同.md](docs/android/AndroidNote/Android进阶/Recyclerview和Listview的异同.md) * [初识ConstraintLayout](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649548068&idx=1&sn=f750ae79c9458f89c3cf85f7573ba579&scene=21#wechat_redirect) * [TabLayout记录](docs/android/AndroidNote/Android基础/tablayout记录.md) * [用SpannableString打造绚丽多彩的文本显示效果](http://www.jianshu.com/p/84067ad289d2) * [解析ConstraintLayout的性能优势](https://mp.weixin.qq.com/s/gGR2itbY7hh9fo61SxaMQQ) * [Android新特性介绍,ConstraintLayout完全解析](https://blog.csdn.net/guolin_blog/article/details/53122387) * [Android新特性介绍,ConstraintLayout完全解析](https://blog.csdn.net/guolin_blog/article/details/53122387) * [Android 一个无限循环滚动的卡片式ViewPager](https://blog.csdn.net/qq_30552993/article/details/76208535) * [Android 中获取控件宽和高的方法(详细解析)](https://blog.csdn.net/CodeIsPoisonous/article/details/54316025) ### 进阶 * [Android 学习笔记核心篇](https://juejin.im/post/5c46db4ae51d4503834d8227) * [Android内存泄漏性能优化总结](docs/android/AndroidNote/内存性能.md) * [进程间通信详解](docs/android/AndroidNote/IPC.md) * [Android中的动画](docs/android/AndroidNote/Android进阶/Android中的动画.md) * [深入了解MVXX模式](docs/android/AndroidNote/Android进阶/深入了解MVXX模式.md) * [Android项目总结](docs/android/AndroidNote/Android进阶/Android项目总结.md) * [Android项目总结2](docs/android/AndroidNote/Android进阶/Android项目总结2.md) * [自定义RadioGroup](docs/android/AndroidNote/Android进阶/自定义RadioGroup.md) * [Android导入项目一直在Building的解决方案](docs/android/AndroidNote/Android进阶/AndroidStudio导入工程一直在Building的解决方案.md) * [基于TOTP的双向认证算法](docs/android/AndroidNote/Android进阶/基于OTP算法的双向认证.md) * [基于TOTP的双向认证算法](docs/android/AndroidNote/Android进阶/基于OTP算法的双向认证.md) * [Android 触控事件解析 - Mastering The Android Touch System 笔记](https://www.jianshu.com/p/c65da5e81afd) * [《Android 高性能编程》—— @IntDef 注解,减缓枚举的使用](https://blog.csdn.net/OneDeveloper/article/details/79973205) * [Android官网建议代码规范](https://source.android.com/source/code-style#java-language-rules) * [30多年编码经验总结成10条最佳实践](https://mp.weixin.qq.com/s?__biz=MzIyMjQ0MTU0NA==&mid=2247484524&idx=1&sn=5b2759e6d89f01e61d021545ca7556b9&chksm=e82c3d4bdf5bb45dd77227982931ede8229ee6910829253a57bb905e810c89bd3f0a162786e8&mpshare=1&scene=23&srcid=1023FjKcLWtRlcDpwEeeJnCN#rd) * [Android中利用异步来优化处理速度](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=401555104&idx=1&sn=501e6158e6eb26b4e86467be01fd290e&scene=21#wechat_redirect) * [三大图片缓存框架的对比](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649547344&idx=2&sn=e3fa99b52055a37202634fe61a62d439&scene=21#wechat_redirect) * [SVG图片在Android中的应用](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649548366&idx=1&sn=6cbdf8652ec139859d9be01444e1ad3b&chksm=f1180d33c66f8425a286de4fd5f03aa89308add3593529a91356439cb8c2f8542305561034c8&scene=21#wechat_redirect) * [携程App的网络性能优化实践](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649547359&idx=1&sn=9f069a28f5dbe73fb6c241cfa1049571&scene=21#wechat_redirect) * [途牛插件化原理](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649547401&idx=1&sn=e615735d600f987a7f769f7e278d0840&scene=21#wechat_redirect) * [Android分包原理](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649547390&idx=1&sn=1fae14b1753e437a032640be81c475b8&scene=21#wechat_redirect) * [插件化实现的思想](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649547660&idx=1&sn=d2764b282fdf1c1fdb629f9c2ca9b10f&scene=21#wechat_redirect) * [Android 7.0新特性总结](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649548427&idx=1&sn=df9956d131a6da5f29292cd05a61b16e&chksm=f1180df6c66f84e0097eea33bba6abb125b6bcd6847720a7c481a85001a52ae2e4b1941690eb&scene=21#wechat_redirect) * [RecyclerView局部刷新的坑](http://blog.csdn.net/jdsjlzx/article/details/52893469) * [Android单元测试](https://tech.meituan.com/Android_unit_test.html) * [gradle 详解——你真的了解Gradle吗?](http://blog.csdn.net/u013132758/article/details/52355915) * [AndroidStudio-Gradle多渠道打包](http://stormzhang.com/devtools/2015/01/15/android-studio-tutorial6/) * [Android基础入门教程——8.1.1 Android中的13种Drawable小结 Part 1](http://blog.csdn.net/coder_pig/article/details/49006217) * [Android基础入门教程——8.1.2 Android中的13种Drawable小结 Part 2](http://blog.csdn.net/coder_pig/article/details/49008397) * [Android-Drawable高级用法](http://blog.csdn.net/lmj623565791/article/details/43752383) * [安卓开踩过的坑:你的 Bitmap 究竟占多大内存?](http://dev.qq.com/topic/591d61f56793d26660901b4e) * [Android 4.4 中 WebView 使用注意事项](https://github.com/cundong/blog/blob/master/Android%204.4%20%E4%B8%AD%20WebView%20%E4%BD%BF%E7%94%A8%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9.md) * [Android图像处理 - 高斯模糊的原理及实现](https://mp.weixin.qq.com/s?__biz=MzI2MTU3MTE4NQ==&mid=2247483896&idx=1&sn=50c61e2c78aa610a1944be6a89bd75e5&chksm=ea5916e6dd2e9ff0a62af64c7f345ffb5c6dafdb65847b757b99afcc6fed8e1270e915dbcb25&mpshare=1&scene=23&srcid=1001DxwdQpiMwea74mczpSw8#rd) * [Android实战——GreenDao3.2的使用,爱不释手](https://mp.weixin.qq.com/s/4Nx2DacsK65O5LanPZUszA) * [Realm for Android详细教程](http://www.jianshu.com/p/28912c2f31db#) * [给 Android 开发者的 RxJava 详解](http://gank.io/post/560e15be2dca930e00da1083) * [Android 谈谈自动化测试](https://mp.weixin.qq.com/s/-0e1wd2iveQPMWgGFcmOwQ) * [检查app是否具有通知栏权限](docs/android/AndroidNote/Android进阶/检查app是否有推送权限.md) * [Android中图片压缩分析(上)](https://mp.weixin.qq.com/s/QZ-XTsO7WnNvpnbr3DWQmg) * [Android Studio3.0更新之路(遇坑必入)](http://www.jianshu.com/p/15afb8234d19) * [Android Studio3.0正式版填坑路](http://www.jianshu.com/p/9b25087a5d7d) * [Android混合编程:WebView实践](https://juejin.im/post/59f17a7051882546d71e91a7) * [runOnUiThread 、Handler.post、View.post之间的区别](https://blog.csdn.net/dengpeng_/article/details/78804404) * [理解 Activity.runOnUiThread](https://www.jianshu.com/p/e39449026f21) * [说说 getMainLooper](http://www.icodeyou.com/2015/10/11/2015-10-11-getMainLooper/) * [Android 探究 LayoutInflater setFactory](https://blog.csdn.net/lmj623565791/article/details/51503977) * [巧用ViewPager 打造不一样的广告轮播切换效果](https://blog.csdn.net/lmj623565791/article/details/51339751) * [为RecyclerView打造通用Adapter 让RecyclerView更加好用](https://blog.csdn.net/lmj623565791/article/details/51118836) * [MNCrashMonitor 监听程序崩溃日志,直接页面展示崩溃日志列表](http://www.wanandroid.com/blog/show/2207) * [『进阶之路』—— 线程池](http://www.wanandroid.com/blog/show/2264) * [从json文件到炫酷动画-Lottie实现思路和源码分析](https://www.jianshu.com/p/81be1bf9600c) * [Lottie动画库 Android 端源码浅析](http://chenhaohui.com/2017/03/13/sd/) ### Gradle相关 * [如何理解 Transform API](https://juejin.im/entry/59776f2bf265da6c4741db2b) * [Gradle自定义插件详解](https://www.jianshu.com/p/03eb55536298) * [Android 突破 DEX 文件的 64k方法数限制](http://yifeng.studio/2016/10/26/android-64k-methods-count/) * [Android Dex分包之旅](http://yydcdut.com/2016/03/20/split-dex/) * [美团Android DEX自动拆包及动态加载简介](https://tech.meituan.com/mt-android-auto-split-dex.html) * [gradle简单入门系列](http://www.cnblogs.com/davenkin/p/gradle-learning-1.html) * [Gradle简单配置](https://mp.weixin.qq.com/s/1UHcYOudViMhpUYeREZzGA) * [Android 如何编写基于编译时注解的项目](https://blog.csdn.net/lmj623565791/article/details/51931859) * [Gradle 完整指南(Android)](https://www.jianshu.com/p/9df3c3b6067a) * [NDK-JNI开发入门教程项目](https://github.com/pengMaster/NDKJniDemo) * [深入理解Android之Gradle Groovy](https://blog.csdn.net/innost/article/details/48228651) * [Groovy 闭包](https://www.jianshu.com/p/6dc2074480b8) * [要点提炼| Gradle指南](https://www.jianshu.com/p/1274c1f1b6a4) * [Gradle专题][39] * [发布library到Maven仓库][40] ### 自定义View * [自定义View入门](docs/android/AndroidNote/Android自定义View/自定义View入门.md) * [自定义view详细教程](https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649547668&idx=1&sn=b2667c46188c6674c90aa72c2fba4719&scene=21#wechat_redirect) * [自定义ViewGroup入门](docs/android/AndroidNote/Android自定义View/自定义ViewGroup入门.md) * [Android事件分发机制](docs/android/AndroidNote/Android自定义View/Android事件分发机制.md) * [CameraView](docs/android/AndroidNote/Android自定义View/自定义View——CameraView.md) * [CheckView](docs/android/AndroidNote/Android自定义View/自定义View——CheckView.md) * [CircleView](docs/android/AndroidNote/Android自定义View/自定义View——CircleView.md) * [FlowLayout](docs/android/AndroidNote/Android自定义View/自定义View——FlowLayout.md) * [PieView](docs/android/AndroidNote/Android自定义View/自定义View——PieView.md) * [SlideslipListView](docs/android/AndroidNote/Android自定义View/自定义view——sideslipListView.md) * [二阶贝塞尔曲线](docs/android/AndroidNote/Android自定义View/二阶贝塞尔曲线.md) * [三阶贝塞尔曲线](docs/android/AndroidNote/Android自定义View/三阶贝塞尔曲线.md) * [贝塞尔曲线Demo](https://github.com/linsir6/mCustomView/tree/master/BezierDemo) * [具有弹性的小球](https://github.com/linsir6/mCustomView/tree/master/MagicCircle) * [PathMeasure](docs/android/AndroidNote/Android自定义View/PathMeasure.md) ## Android常见设计模式 * **Android常见设计模式** * [观察者模式](https://blog.csdn.net/chengyuqiang/article/details/79222294) * [策略模式](https://github.com/pengMaster/strategyMode) * [建造者模式](https://www.jianshu.com/p/154948d5adc6) * [适配器模式](https://blog.csdn.net/u012583459/article/details/47079529) * [代理模式](https://blog.csdn.net/u012583459/article/details/47079529) * [工厂模式](https://blog.csdn.net/u012583459/article/details/47079549) * [单例模式](https://blog.csdn.net/u012583459/article/details/47079549) * [命令模式](https://blog.csdn.net/u012583459/article/details/47079549) ### 音视频开发 - [音视频开发][44] - [搭建nginx+rtmp服务器][18] - [视频播放相关内容总结][19] - [视频解码之软解与硬解][20] - [音视频基础知识][21] - [Android WebRTC简介][22] - [Android音视频开发知识(未完)][23] - [DLNA简介][24] ### 热修复相关 * [Android 热修复 Tinker Gradle Plugin解析](https://blog.csdn.net/lmj623565791/article/details/72667669) * [Android 热修复 Tinker接入及源码浅析](https://blog.csdn.net/lmj623565791/article/details/54882693) * [Android 热修复 Tinker 源码分析之DexDiff / DexPatch](https://blog.csdn.net/lmj623565791/article/details/60874334) ### 插件化相关 * [滴滴插件化方案 VirtualApk 源码解析](https://blog.csdn.net/lmj623565791/article/details/75000580) ### 编译器相关 * [Android Studio 3.0 新功能解析和旧项目适配](https://mp.weixin.qq.com/s/met0fke7rKumb7Nlb5hxpA) * [Android-studio使用教程1](docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第一弹).md) * [Android-studio使用教程2](docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第二弹).md) * [Android-studio使用教程3](docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第三弹).md) * [Android-studio使用教程4](docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第四弹).md) * [Android-studio使用教程5](docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第五弹).md) * [Android-studio使用教程6](docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第六弹).md) * [Android-studio使用教程7](docs/android/AndroidNote/Android编译器相关/AndroidStudio使用教程(第七弹).md) ### 性能优化 * [Android开发性能优化总结(一)](http://blog.csdn.net/gs12software/article/details/51173392) * [Android开发性能优化总结(二)](http://blog.csdn.net/gs12software/article/details/51234454) ### 开源框架 * [当下流行开源框架总览](docs/android/AndroidNote/Android开源框架相关/Android当下最流行的开源框架总结.md) * [easypermission](docs/android/AndroidNote/Android开源框架相关/动态申请权限库:easypermissions使用与源码解析.md) * [ButterKnifeZelezny](docs/android/AndroidNote/Android开源框架相关/Android黑科技——ButterKnifeZelezny.md) * [RxJava+retrofit2](docs/android/AndroidNote/Android开源框架相关/RxJava+retrofit2实现安卓中网络操作.md) * [LinLog](docs/android/AndroidNote/Android开源框架相关/一款Android的Log、Toast的库.md) * [Retrofit 2.0 使用教程](http://www.jianshu.com/p/a3e162261ab6) * [retrofit 2.0 源码解析](http://www.jianshu.com/p/0c055ad46b6c) * [关于 RxJava 背压](https://juejin.im/entry/58e704cbac502e4957b230eb) * [RxJava 2.0中backpressure(背压)概念的理解](https://blog.csdn.net/jdsjlzx/article/details/52717636) * [Retrofit2 完全解析 探索与okhttp之间的关系](https://blog.csdn.net/lmj623565791/article/details/51304204) *[Dagger2][199] - [1.Dagger2简介(一).md][200] - [2.Dagger2入门demo(二).md][201] - [3.Dagger2入门demo扩展(三).md][202] - [4.Dagger2单例(四).md][203] - [5.Dagger2Lay和Provider(五).md][204] - [6.Dagger2Android示例代码(六).md][205] - [7.Dagger2之dagger-android(七).md][206] - [8.Dagger2与MVP(八).md][207] - [9.Dagger2原理分析(九).md][212] * [图片加载][45] - [Glide简介(上)][25] - [Glide简介(下)][26] - [图片加载库比较][27] * [RxJava][46] - [RxJava详解(一)][28] - [RxJava详解(二)][29] - [RxJava详解(三)][30] - [RxJava详解之执行原理(四)][209] - [RxJava详解之操作符执行原理(五)][210] - [RxJava详解之线程调度原理(六)][211] - [RxJava系列全家桶][31] ### 应用发布 - [应用发布][50] - [使用Jenkins实现自动化打包][198] - [Android应用发布][41] - [Zipalign优化][42] ### 打包 * [打包jar包或aar包](docs/android/AndroidNote/Android打包相关/Android将library打包成jar文件或aar文件.md) * [发布sdk到jcenter](docs/android/AndroidNote/Android打包相关/Android发布sdk到jcenter.md) ##### 框架源码分析 - [EventBus源码分析](docs/android/sources/eventbus.md) - [Bufferknife源码分析](docs/android/sources/butterknife.md) - [Glide 源码分析](docs/android/sources/glide.md) - [OKHttp 源码分析](docs/android/sources/okhttp.md) - [Retrofit 源码分析](docs/android/sources/retrofit.md) - [ViewModel 源码分析](docs/android/sources/viewmodel.md) - [自定义View详解][1] - [Activity界面绘制过程详解][2] - [Activity启动过程][3] - [Android Touch事件分发详解][4] - [AsyncTask详解][5] - [butterknife源码详解][6] - [InstantRun详解][7] - [ListView源码分析][8] - [VideoView源码分析][9] - [View绘制过程详解][10] - [网络部分][11] - [HttpURLConnection详解][12] - [HttpURLConnection与HttpClient][13] - [volley-retrofit-okhttp之我们该如何选择网路框架][14] - [Volley源码分析][15] - [Retrofit详解(上)][16] - [Retrofit详解(下)][17] ## Kotlin - [Kotlin学习][48] - [Kotlin学习教程(一)][180] - [Kotlin学习教程(二)][181] - [Kotlin学习教程(三)][182] - [Kotlin学习教程(四)][183] - [Kotlin学习教程(五)][184] - [Kotlin学习教程(六)][185] - [Kotlin学习教程(七)][186] - [Kotlin学习教程(八)][187] - [Kotlin学习教程(九)][188] - [Kotlin学习教程(十)][197] - [集合之常用操作符汇总](https://www.cnblogs.com/Jetictors/p/9241867.html) ## Flutter * **Flutter学习:** * [flutter脚手架封装](https://github.com/pengMaster/flutter_app) * [flutter中文学习网](https://book.flutterchina.club/chapter2/) * [flutter常用库总结](https://www.cnblogs.com/yangyxd/p/9232308.html) * [flutter开源项目](https://flutterchina.club/opensource.html) * [flutter基础语法](https://www.jianshu.com/p/3d927a7bf020) * [Flutter常用工具类](https://juejin.im/post/5d0f4c54f265da1bb31c426c?utm_source=gold_browser_extension) * [Flutter-learning](https://github.com/AweiLoveAndroid/Flutter-learning) * [Flutter-UI框架](https://bruno.ke.com/) ## TODO学习清单 - [TODO学习清单](docs/android/self.md) ## Java ### 基础 * [Java 基础知识回顾](docs/java/Java基础知识.md) * [J2EE 基础知识回顾](docs/java/J2EE基础知识.md) * [Collections 工具类和 Arrays 工具类常见方法](docs/java/Basis/Arrays%2CCollectionsCommonMethods.md) * [Java常见关键字总结:static、final、this、super](docs/java/Basis/final、static、this、super.md) * [Java常见关键字总结:static、final、this、super](docs/java/Basis/final、static、this、super.md) ### 容器 * **常见问题总结:** * [这几道Java集合框架面试题几乎必问](docs/java/这几道Java集合框架面试题几乎必问.md) * [Java 集合框架常见面试题总结](docs/java/Java集合框架常见面试题总结.md) * **源码分析:** * [ArrayList 源码学习](docs/java/ArrayList.md) * [【面试必备】透过源码角度一步一步带你分析 ArrayList 扩容机制](docs/java/ArrayList-Grow.md) * [LinkedList 源码学习](docs/java/LinkedList.md) * [HashMap(JDK1.8)源码学习](docs/java/HashMap.md) ### 并发 * [并发编程面试必备:synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](docs/java/synchronized.md) * [并发编程面试必备:乐观锁与悲观锁](docs/essential-content-for-interview/面试必备之乐观锁与悲观锁.md) * [并发编程面试必备:JUC 中的 Atomic 原子类总结](docs/java/Multithread/Atomic.md) * [并发编程面试必备:AQS 原理以及 AQS 同步组件总结](docs/java/Multithread/AQS.md) * [BATJ都爱问的多线程面试题](docs/java/Multithread/BATJ都爱问的多线程面试题.md) * [并发容器总结](docs/java/Multithread/并发容器总结.md) ### JVM * [可能是把Java内存区域讲的最清楚的一篇文章](docs/java/可能是把Java内存区域讲的最清楚的一篇文章.md) * [搞定JVM垃圾回收就是这么简单](docs/java/搞定JVM垃圾回收就是这么简单.md) * [《深入理解Java虚拟机》第2版学习笔记](docs/java/Java虚拟机(jvm).md) ### I/O * [BIO,NIO,AIO 总结 ](docs/java/BIO-NIO-AIO.md) * [Java IO 与 NIO系列文章](docs/java/Java%20IO与NIO.md) ### Java 8 * [Java 8 新特性总结](docs/java/What's%20New%20in%20JDK8/Java8Tutorial.md) * [Java 8 学习资源推荐](docs/java/What's%20New%20in%20JDK8/Java8教程推荐.md) ### 编程规范 - [Java 编程规范](docs/java/Java编程规范.md) ## 网络 * [浅析socket](docs/android/AndroidNote/网络协议/浅析socket.md) * [浅析Hessian](docs/android/AndroidNote/网络协议/浅析Hessian协议.md) * [浅析RPC协议](docs/android/AndroidNote/网络协议/浅析RPC协议.md) * [浅析dubbo服务](docs/android/AndroidNote/网络协议/浅析dubbo服务.md) * [SSH原理与应用](docs/android/AndroidNote/网络协议/SSH原理与应用.md) * [理解OAuth 2.0](http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html) * [OAuth 2和JWT - 如何设计安全的API?](http://blog.csdn.net/ljinddlj/article/details/53108261) * [计算机网络常见面试题](docs/network/计算机网络.md) * [计算机网络基础知识总结](docs/network/干货:计算机网络知识总结.md) * [HTTPS中的TLS](docs/network/HTTPS中的TLS.md) ## 操作系统 ### Linux相关 * [后端程序员必备的 Linux 基础知识](docs/operating-system/后端程序员必备的Linux基础知识.md) * [Shell 编程入门](docs/operating-system/Shell.md) ### 计算机操作系统 - [计算机操作系统](docs/notes/计算机操作系统%20-%20目录.md) ## 数据结构与算法 ### 数据结构 - [数据结构知识学习与面试](docs/dataStructures-algorithms/数据结构.md) ### 算法 - [算法学习资源推荐](docs/dataStructures-algorithms/算法学习资源推荐.md) - [算法总结——几道常见的子符串算法题 ](docs/dataStructures-algorithms/几道常见的子符串算法题.md) - [算法总结——几道常见的链表算法题 ](docs/dataStructures-algorithms/几道常见的链表算法题.md) - [剑指offer部分编程题](docs/dataStructures-algorithms/剑指offer部分编程题.md) - [公司真题](docs/dataStructures-algorithms/公司真题.md) - [回溯算法经典案例之N皇后问题](docs/dataStructures-algorithms/Backtracking-NQueens.md) - [算法设计常用思想](docs/dataStructures-algorithms/Backtracking-NQueens.md) ## 数据库 ### MySQL * [MySQL 学习与面试](docs/database/MySQL.md) * [一千行MySQL学习笔记](docs/database/一千行MySQL命令.md) * [MySQL高性能优化规范建议](docs/database/MySQL高性能优化规范建议.md) * [搞定数据库索引就是这么简单](docs/database/MySQL%20Index.md) * [事务隔离级别(图文详解)](docs/database/事务隔离级别(图文详解).md) * [一条SQL语句在MySQL中如何执行的](docs/database/一条sql语句在mysql中如何执行的.md) * [linux下安装MySQL](docs/android/AndroidNote/WebNote/MySQL相关/云服务器linux下安装MySQL.md) * [MySQL基础操作](docs/android/AndroidNote/WebNote/MySQL相关/mysql基础操作.md) * [MySQL导出数据库、表](docs/android/AndroidNote/WebNote/MySQL相关/Mysql导出数据库、表(有无数据).md) * [Error-ER_TRUNCATED_WRONG_VALUE_FOR_FIELD](docs/android/AndroidNote/WebNote/MySQL相关/Error--ER_TRUNCATED_WRONG_VALUE_FOR_FIELD.md) * [ERROR-1045-(28000)--Access-denied-for-user-'debian-sys-maint'@'localhost](docs/android/AndroidNote/WebNote/MySQL相关/ERROR-1045-(28000)--Access-denied-for-user-'debian-sys-maint'@'localho.md) * [mysql设置远程链接权限](https://www.cnblogs.com/gdsblog/p/7349551.html) * [关于初次安装mysql8.01遇到的问题解决](https://blog.csdn.net/l569746927/article/details/80025364) ### Redis * [Redis 总结](docs/database/Redis/Redis.md) * [Redlock分布式锁](docs/database/Redis/Redlock分布式锁.md) * [如何做可靠的分布式锁,Redlock真的可行么](docs/database/Redis/如何做可靠的分布式锁,Redlock真的可行么.md) ### 数据库系统原理 - [数据库系统原理](docs/notes/数据库系统原理.md) ### SQL - [SQL](docs/notes/SQL.md) ### Leetcode-Database 题解 - [Leetcode-Database 题解](docs/notes/Leetcode-Database%20题解.md) ## 系统设计 ### 设计模式 - [设计模式系列文章](docs/system-design/设计模式.md) ### 常用框架 #### Spring - [Spring 学习与面试](docs/system-design/framework/Spring学习与面试.md) - [Spring中bean的作用域与生命周期](docs/system-design/framework/SpringBean.md) - [SpringMVC 工作原理详解](docs/system-design/framework/SpringMVC%20%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3.md) #### ZooKeeper - [可能是把 ZooKeeper 概念讲的最清楚的一篇文章](docs/system-design/framework/ZooKeeper.md) - [ZooKeeper 数据模型和常见命令了解一下,速度收藏!](docs/system-design/framework/ZooKeeper数据模型和常见命令.md) ### 数据通信 - [数据通信(RESTful、RPC、消息队列)相关知识点总结](docs/system-design/data-communication/数据通信(RESTful、RPC、消息队列).md) - [Dubbo 总结:关于 Dubbo 的重要知识点](docs/system-design/data-communication/dubbo.md) - [消息队列总结:新手也能看懂,消息队列其实很简单](docs/system-design/data-communication/message-queue.md) - [一文搞懂 RabbitMQ 的重要概念以及安装](docs/system-design/data-communication/rabbitmq.md) ### 网站架构 - [一文读懂分布式应该学什么](docs/system-design/website-architecture/分布式.md) - [8 张图读懂大型网站技术架构](docs/system-design/website-architecture/8%20张图读懂大型网站技术架构.md) - [【面试精选】关于大型网站系统架构你不得不懂的10个问题](docs/system-design/website-architecture/【面试精选】关于大型网站系统架构你不得不懂的10个问题.md) ### 攻击技术 - [攻击技术](docs/notes/攻击技术.md) ## 面试指南 ### 备战面试 * [【备战面试1】程序员的简历就该这样写](docs/essential-content-for-interview/PreparingForInterview/程序员的简历之道.md) * [【备战面试2】初出茅庐的程序员该如何准备面试?](docs/essential-content-for-interview/PreparingForInterview/interviewPrepare.md) * [【备战面试3】7个大部分程序员在面试前很关心的问题](docs/essential-content-for-interview/PreparingForInterview/JavaProgrammerNeedKnow.md) * [【备战面试4】Github上开源的Java面试/学习相关的仓库推荐](docs/essential-content-for-interview/PreparingForInterview/JavaInterviewLibrary.md) * [【备战面试5】如果面试官问你“你有什么问题问我吗?”时,你该如何回答](docs/essential-content-for-interview/PreparingForInterview/如果面试官问你“你有什么问题问我吗?”时,你该如何回答.md) * [【备战面试6】美团面试常见问题总结(附详解答案)](docs/essential-content-for-interview/PreparingForInterview/美团面试常见问题总结.md) ### 常见面试题总结 * [第一周(2018-8-7)](docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第一周(2018-8-7).md) (为什么 Java 中只有值传递、==与equals、 hashCode与equals) * [第二周(2018-8-13)](docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第二周(2018-8-13).md)(String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的?、什么是反射机制?反射机制的应用场景有哪些?......) * [第三周(2018-08-22)](docs/java/这几道Java集合框架面试题几乎必问.md) (Arraylist 与 LinkedList 异同、ArrayList 与 Vector 区别、HashMap的底层实现、HashMap 和 Hashtable 的区别、HashMap 的长度为什么是2的幂次方、HashSet 和 HashMap 区别、ConcurrentHashMap 和 Hashtable 的区别、ConcurrentHashMap线程安全的具体实现方式/底层具体实现、集合框架底层数据结构总结) * [第四周(2018-8-30).md](docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第四周(2018-8-30).md) (主要内容是几道面试常问的多线程基础题。) ### 面经 - [5面阿里,终获offer(2018年秋招)](docs/essential-content-for-interview/BATJrealInterviewExperience/5面阿里,终获offer.md) ### Android面试专场 - [Android面试专场](docs/android/Android-Interview/README.md) ## 工具 ### Git * [Git入门](docs/tools/Git.md) ### Docker * [Docker 入门](docs/tools/Docker.md) * [一文搞懂 Docker 镜像的常用操作!](docs/tools/Docker-Image.md) ### 构建工具 * [构建工具](docs/notes/构建工具.md) ### 正则表达式 * [正则表达式](docs/notes/正则表达式.md) ## 致谢 本文并非原创,通过各位博主综合而得,以便供自己方便学习,在此感谢各位前辈,并在下面注明出处 - [Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide) - [UCodeUStory/DataStructure](https://github.com/UCodeUStory/DataStructure) - [JackChan1999/Android-Interview](https://github.com/JackChan1999/Android-Interview) - [linsir6/AndroidNote](https://github.com/linsir6/AndroidNote) - [CharonChui/AndroidNote](https://github.com/CharonChui/AndroidNote) - [CS-Notes](https://github.com/CyC2018/CS-Notes) License === MIT License Copyright (c) 2019 pengMaster Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. [1]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/%E8%87%AA%E5%AE%9A%E4%B9%89View%E8%AF%A6%E8%A7%A3.md "自定义View详解" [2]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Activity%E7%95%8C%E9%9D%A2%E7%BB%98%E5%88%B6%E8%BF%87%E7%A8%8B%E8%AF%A6%E8%A7%A3.md "Activity界面绘制过程详解" [3]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Activity%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B.md "Activity启动过程" [4]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Android%20Touch%E4%BA%8B%E4%BB%B6%E5%88%86%E5%8F%91%E8%AF%A6%E8%A7%A3.md "Android Touch事件分发详解" [5]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/AsyncTask%E8%AF%A6%E8%A7%A3.md "AsyncTask详解" [6]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/butterknife%E6%BA%90%E7%A0%81%E8%AF%A6%E8%A7%A3.md "butterknife源码详解" [7]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/InstantRun%E8%AF%A6%E8%A7%A3.md "InstantRun详解" [8]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/ListView源码分析.md "ListView源码分析" [9]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/VideoView%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md "VideoView源码分析" [10]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/View%E7%BB%98%E5%88%B6%E8%BF%87%E7%A8%8B%E8%AF%A6%E8%A7%A3.md "View绘制过程详解" [11]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//SourceAnalysis/Netowork "网络部分" [12]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Netowork/HttpURLConnection%E8%AF%A6%E8%A7%A3.md "HttpURLConnection详解" [13]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Netowork/HttpURLConnection%E4%B8%8EHttpClient.md "HttpURLConnection与HttpClient" [14]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Netowork/volley-retrofit-okhttp%E4%B9%8B%E6%88%91%E4%BB%AC%E8%AF%A5%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9%E7%BD%91%E8%B7%AF%E6%A1%86%E6%9E%B6.md "volley-retrofit-okhttp之我们该如何选择网路框架" [15]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Netowork/Volley%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md "Volley源码分析" [16]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Netowork/Retrofit%E8%AF%A6%E8%A7%A3(%E4%B8%8A).md "Retrofit详解(上)" [17]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/SourceAnalysis/Netowork/Retrofit%E8%AF%A6%E8%A7%A3(%E4%B8%8B).md "Retrofit详解(下)" [18]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/%E6%90%AD%E5%BB%BAnginx%2Brtmp%E6%9C%8D%E5%8A%A1%E5%99%A8.md "搭建nginx+rtmp服务器" [19]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/%E8%A7%86%E9%A2%91%E6%92%AD%E6%94%BE%E7%9B%B8%E5%85%B3%E5%86%85%E5%AE%B9%E6%80%BB%E7%BB%93.md "视频播放相关内容总结" [20]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/%E8%A7%86%E9%A2%91%E8%A7%A3%E7%A0%81%E4%B9%8B%E8%BD%AF%E8%A7%A3%E4%B8%8E%E7%A1%AC%E8%A7%A3.md "视频解码之软解与硬解" [21]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/%E9%9F%B3%E8%A7%86%E9%A2%91%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md "音视频基础知识" [22]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/Android%20WebRTC%E7%AE%80%E4%BB%8B.md "Android WebRTC简介" [23]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/Android%E9%9F%B3%E8%A7%86%E9%A2%91%E5%BC%80%E5%8F%91.md "Android音视频开发知识" [24]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/VideoDevelopment/DLNA%E7%AE%80%E4%BB%8B.md "DLNA简介" [25]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/ImageLoaderLibrary/Glide%E7%AE%80%E4%BB%8B(%E4%B8%8A).md "Glide简介(上)" [26]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/ImageLoaderLibrary/Glide%E7%AE%80%E4%BB%8B(%E4%B8%8B).md "Glide简介(下)" [27]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/ImageLoaderLibrary/%E5%9B%BE%E7%89%87%E5%8A%A0%E8%BD%BD%E5%BA%93%E6%AF%94%E8%BE%83.md "图片加载库比较" [28]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/1.RxJava%E8%AF%A6%E8%A7%A3(%E4%B8%80).md "RxJava详解(一)" [29]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/2.RxJava%E8%AF%A6%E8%A7%A3(%E4%BA%8C).md "RxJava详解(二)" [30]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/3.RxJava%E8%AF%A6%E8%A7%A3(%E4%B8%89).md "RxJava详解(三)" [31]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/7.RxJava%E7%B3%BB%E5%88%97%E5%85%A8%E5%AE%B6%E6%A1%B6.md "RxJava系列全家桶" [32]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/%E7%9B%AE%E5%89%8D%E6%B5%81%E8%A1%8C%E7%9A%84%E5%BC%80%E5%8F%91%E7%BB%84%E5%90%88.md "目前流行的开发组合" [33]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E7%9B%B8%E5%85%B3%E5%B7%A5%E5%85%B7.md "性能优化相关工具" [34]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/Android%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7%E5%8F%8A%E7%B1%BB%E5%BA%93.md "Android开发工具及类库" [35]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/Github%E4%B8%AA%E4%BA%BA%E4%B8%BB%E9%A1%B5%E7%BB%91%E5%AE%9A%E5%9F%9F%E5%90%8D.md "Github个人主页绑定域名" [36]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/Markdown%E5%AD%A6%E4%B9%A0%E6%89%8B%E5%86%8C.md "Markdown学习手册" [37]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/MAT%E5%86%85%E5%AD%98%E5%88%86%E6%9E%90.md "MAT内存分析" [38]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%B8%80).md "Kotlin学习教程(一)(未完)" [39]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Gradle%26Maven/Gradle%E4%B8%93%E9%A2%98.md "Gradle专题" [40]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Gradle%26Maven/%E5%8F%91%E5%B8%83library%E5%88%B0Maven%E4%BB%93%E5%BA%93.md "发布library到Maven仓库" [41]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AppPublish/Android%E5%BA%94%E7%94%A8%E5%8F%91%E5%B8%83.md "Android应用发布" [42]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AppPublish/Zipalign%E4%BC%98%E5%8C%96.md "Zipalign优化" [43]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//SourceAnalysis "源码解析" [44]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//VideoDevelopment "音视频开发" [45]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//ImageLoaderLibrary "图片加载" [46]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//RxJavaPart "RxJava" [47]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//Tools%26Library "开发工具" [48]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//KotlinCourse "Kotlin学习" [49]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//Gradle%26Maven "Gradle&Maven" [50]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//AppPublish "应用发布" [51]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//AndroidStudioCourse "Android Studio使用教程" [52]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//AdavancedPart "进阶部分" [53]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//JavaKnowledge "Java基础及算法" [54]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//BasicKnowledge "基础部分" [55]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E4%B8%80%E5%BC%B9).md "AndroidStudio使用教程(第一弹)" [56]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E4%BA%8C%E5%BC%B9).md "AndroidStudio使用教程(第二弹)" [57]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E4%B8%89%E5%BC%B9).md "AndroidStudio使用教程(第三弹)" [58]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E5%9B%9B%E5%BC%B9).md "AndroidStudio使用教程(第四弹)" [59]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E4%BA%94%E5%BC%B9).md "AndroidStudio使用教程(第五弹)" [60]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E5%85%AD%E5%BC%B9).md "AndroidStudio使用教程(第六弹)" [61]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B(%E7%AC%AC%E4%B8%83%E5%BC%B9).md "AndroidStudio使用教程(第七弹)" [62]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/Android%20Studio%E4%BD%A0%E5%8F%AF%E8%83%BD%E4%B8%8D%E7%9F%A5%E9%81%93%E7%9A%84%E6%93%8D%E4%BD%9C.md "Android Studio你可能不知道的操作" [63]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E6%8F%90%E9%AB%98Build%E9%80%9F%E5%BA%A6.md "AndroidStudio提高Build速度" [64]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AndroidStudioCourse/AndroidStudio%E4%B8%AD%E8%BF%9B%E8%A1%8Cndk%E5%BC%80%E5%8F%91.md "AndroidStudio中进行ndk开发" [65]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E5%B8%83%E5%B1%80%E4%BC%98%E5%8C%96.md "布局优化" [66]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E5%B1%8F%E5%B9%95%E9%80%82%E9%85%8D%E4%B9%8B%E7%99%BE%E5%88%86%E6%AF%94%E6%96%B9%E6%A1%88%E8%AF%A6%E8%A7%A3.md "屏幕适配之百分比方案详解" [67]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E7%83%AD%E4%BF%AE%E5%A4%8D%E5%AE%9E%E7%8E%B0.md "热修复实现" [68]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E5%A6%82%E4%BD%95%E8%AE%A9Service%E5%B8%B8%E9%A9%BB%E5%86%85%E5%AD%98.md "如何让Service常驻内存" [69]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E9%80%9A%E8%BF%87Hardware%20Layer%E6%8F%90%E9%AB%98%E5%8A%A8%E7%94%BB%E6%80%A7%E8%83%BD.md "通过Hardware Layer提高动画性能" [70]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96.md "性能优化" [71]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/%E6%B3%A8%E8%A7%A3%E4%BD%BF%E7%94%A8.md "注解使用" [72]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Android6.0%E6%9D%83%E9%99%90%E7%B3%BB%E7%BB%9F.md "Android6.0权限系统" [73]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Android%E5%BC%80%E5%8F%91%E4%B8%8D%E7%94%B3%E8%AF%B7%E6%9D%83%E9%99%90%E6%9D%A5%E4%BD%BF%E7%94%A8%E5%AF%B9%E5%BA%94%E5%8A%9F%E8%83%BD.md "Android开发不申请权限来使用对应功能" [74]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Android%E5%BC%80%E5%8F%91%E4%B8%AD%E7%9A%84MVP%E6%A8%A1%E5%BC%8F%E8%AF%A6%E8%A7%A3.md "Android开发中的MVP模式详解" [75]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Android%E5%90%AF%E5%8A%A8%E6%A8%A1%E5%BC%8F%E8%AF%A6%E8%A7%A3.md "Android启动模式详解" [76]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Android%E5%8D%B8%E8%BD%BD%E5%8F%8D%E9%A6%88.md "Android卸载反馈" [77]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/ApplicationId%20vs%20PackageName.md "ApplicationId vs PackageName" [78]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/ART%E4%B8%8EDalvik.md "ART与Dalvik" [79]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/BroadcastReceiver%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98.md "BroadcastReceiver安全问题" [80]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Handler%E5%AF%BC%E8%87%B4%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2%E5%88%86%E6%9E%90.md "Handler导致内存泄露分析" [81]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Library%E9%A1%B9%E7%9B%AE%E4%B8%AD%E8%B5%84%E6%BA%90id%E4%BD%BF%E7%94%A8case%E6%97%B6%E6%8A%A5%E9%94%99.md "Library项目中资源id使用case时报错" [82]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Mac%E4%B8%8B%E9%85%8D%E7%BD%AEadb%E5%8F%8AAndroid%E5%91%BD%E4%BB%A4.md "Mac下配置adb及Android命令" [83]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/MaterialDesign%E4%BD%BF%E7%94%A8.md "MaterialDesign使用" [84]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/RecyclerView%E4%B8%93%E9%A2%98.md "RecyclerView专题" [85]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%A4%A7%E5%85%A8.md "常用命令行大全" [86]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%8D%95%E4%BE%8B%E7%9A%84%E6%9C%80%E4%BD%B3%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F.md "单例的最佳实现方式" [87]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.md "数据结构" [88]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E8%8E%B7%E5%8F%96%E4%BB%8A%E5%90%8E%E5%A4%9A%E5%B0%91%E5%A4%A9%E5%90%8E%E7%9A%84%E6%97%A5%E6%9C%9F.md "获取今后多少天后的日期" [89]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%89%91%E6%8C%87Offer(%E4%B8%8A).md "剑指Offer(上)" [90]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/剑指Offer(下).md "剑指Offer(下)" [91]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%BC%BA%E5%BC%95%E7%94%A8%E3%80%81%E8%BD%AF%E5%BC%95%E7%94%A8%E3%80%81%E5%BC%B1%E5%BC%95%E7%94%A8%E3%80%81%E8%99%9A%E5%BC%95%E7%94%A8.md "强引用、软引用、弱引用、虚引用" [92]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85.md "生产者消费者" [93]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E6%95%B0%E6%8D%AE%E5%8A%A0%E5%AF%86%E5%8F%8A%E8%A7%A3%E5%AF%86.md "数据加密及解密" [94]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E6%AD%BB%E9%94%81.md "死锁" [95]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95.md "算法" [96]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E7%BD%91%E7%BB%9C%E8%AF%B7%E6%B1%82%E7%9B%B8%E5%85%B3%E5%86%85%E5%AE%B9%E6%80%BB%E7%BB%93.md "网络请求相关内容总结" [97]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%9A%84%E5%8E%9F%E7%90%86.md "线程池的原理" [98]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%8E%9F%E5%AD%90%E6%80%A7%E3%80%81%E5%8F%AF%E8%A7%81%E6%80%A7%E4%BB%A5%E5%8F%8A%E6%9C%89%E5%BA%8F%E6%80%A7.md "原子性、可见性以及有序性" [99]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/Base64%E5%8A%A0%E5%AF%86.md "Base64加密" [100]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/Git%E7%AE%80%E4%BB%8B.md "Git简介" [101]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/hashCode%E4%B8%8Eequals.md "hashCode与equals" [102]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/HashMap%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90.md "HashMap实现原理分析" [103]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/Java%E5%9F%BA%E7%A1%80%E9%9D%A2%E8%AF%95%E9%A2%98.md "Java基础面试题" [104]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/JVM%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6.md "JVM垃圾回收机制" [105]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/MD5%E5%8A%A0%E5%AF%86.md "MD5加密" [106]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/MVC%E4%B8%8EMVP%E5%8F%8AMVVM.md "MVC与MVP及MVVM" [107]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/RMB%E5%A4%A7%E5%B0%8F%E5%86%99%E8%BD%AC%E6%8D%A2.md "RMB大小写转换" [108]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/Vim%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B.md "Vim使用教程" [109]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/volatile%E5%92%8CSynchronized%E5%8C%BA%E5%88%AB.md "volatile和Synchronized区别" [110]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%AE%89%E5%85%A8%E9%80%80%E5%87%BA%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F.md "安全退出应用程序" [111]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E7%97%85%E6%AF%92.md "病毒" [112]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%B6%85%E7%BA%A7%E7%AE%A1%E7%90%86%E5%91%98(DevicePoliceManager).md "超级管理员(DevicePoliceManager)" [113]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E7%A8%8B%E5%BA%8F%E7%9A%84%E5%90%AF%E5%8A%A8%E3%80%81%E5%8D%B8%E8%BD%BD%E5%92%8C%E5%88%86%E4%BA%AB.md "程序的启动、卸载和分享" [114]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E4%BB%A3%E7%A0%81%E6%B7%B7%E6%B7%86.md "代码混淆" [115]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%AF%BB%E5%8F%96%E7%94%A8%E6%88%B7logcat%E6%97%A5%E5%BF%97.md "读取用户logcat日志" [116]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E7%9F%AD%E4%BF%A1%E5%B9%BF%E6%92%AD%E6%8E%A5%E6%94%B6%E8%80%85.md "短信广播接收者" [117]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E6%96%AD%E7%82%B9%E4%B8%8B%E8%BD%BD.md "多线程断点下载" [118]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E9%BB%91%E5%90%8D%E5%8D%95%E6%8C%82%E6%96%AD%E7%94%B5%E8%AF%9D%E5%8F%8A%E5%88%A0%E9%99%A4%E7%94%B5%E8%AF%9D%E8%AE%B0%E5%BD%95.md "黑名单挂断电话及删除电话记录" [119]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%A8%AA%E5%90%91ListView.md "横向ListView" [120]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%BB%91%E5%8A%A8%E5%88%87%E6%8D%A2Activity(GestureDetector).md "滑动切换Activity(GestureDetector)" [121]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%8E%B7%E5%8F%96%E8%81%94%E7%B3%BB%E4%BA%BA.md "获取联系人" [122]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%8E%B7%E5%8F%96%E6%89%8B%E6%9C%BA%E5%8F%8ASD%E5%8D%A1%E5%8F%AF%E7%94%A8%E5%AD%98%E5%82%A8%E7%A9%BA%E9%97%B4.md "获取手机及SD卡可用存储空间" [123]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%8E%B7%E5%8F%96%E6%89%8B%E6%9C%BA%E4%B8%AD%E6%89%80%E6%9C%89%E5%AE%89%E8%A3%85%E7%9A%84%E7%A8%8B%E5%BA%8F.md "获取手机中所有安装的程序" [124]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%8E%B7%E5%8F%96%E4%BD%8D%E7%BD%AE(LocationManager).md "获取位置(LocationManager)" [125]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%8E%B7%E5%8F%96%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E7%BC%93%E5%AD%98%E5%8F%8A%E4%B8%80%E9%94%AE%E6%B8%85%E7%90%86.md "获取应用程序缓存及一键清理" [126]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%BC%80%E5%8F%91%E4%B8%AD%E5%BC%82%E5%B8%B8%E7%9A%84%E5%A4%84%E7%90%86.md "开发中异常的处理" [127]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%BC%80%E5%8F%91%E4%B8%ADLog%E7%9A%84%E7%AE%A1%E7%90%86.md "开发中Log的管理" [128]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%BF%AB%E6%8D%B7%E6%96%B9%E5%BC%8F%E5%B7%A5%E5%85%B7%E7%B1%BB.md "快捷方式工具类" [129]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%9D%A5%E7%94%B5%E5%8F%B7%E7%A0%81%E5%BD%92%E5%B1%9E%E5%9C%B0%E6%8F%90%E7%A4%BA%E6%A1%86.md "来电号码归属地提示框" [130]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%9D%A5%E7%94%B5%E7%9B%91%E5%90%AC%E5%8F%8A%E5%BD%95%E9%9F%B3.md "来电监听及录音" [131]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E9%9B%B6%E6%9D%83%E9%99%90%E4%B8%8A%E4%BC%A0%E6%95%B0%E6%8D%AE.md "零权限上传数据" [132]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F.md "内存泄漏" [133]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%B1%8F%E5%B9%95%E9%80%82%E9%85%8D.md "屏幕适配" [134]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E4%BB%BB%E5%8A%A1%E7%AE%A1%E7%90%86%E5%99%A8(ActivityManager).md "任务管理器(ActivityManager)" [135]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%89%8B%E6%9C%BA%E6%91%87%E6%99%83.md "手机摇晃" [136]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E7%AB%96%E7%9D%80%E7%9A%84Seekbar.md "竖着的Seekbar" [137]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8.md "数据存储" [138]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%90%9C%E7%B4%A2%E6%A1%86.md "搜索框" [139]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E9%94%81%E5%B1%8F%E4%BB%A5%E5%8F%8A%E8%A7%A3%E9%94%81%E7%9B%91%E5%90%AC.md "锁屏以及解锁监听" [140]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0.md "文件上传" [141]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E4%B8%8B%E6%8B%89%E5%88%B7%E6%96%B0ListView.md "下拉刷新ListView" [142]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E4%BF%AE%E6%94%B9%E7%B3%BB%E7%BB%9F%E7%BB%84%E4%BB%B6%E6%A0%B7%E5%BC%8F.md "修改系统组件样式" [143]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E9%9F%B3%E9%87%8F%E5%8F%8A%E5%B1%8F%E5%B9%95%E4%BA%AE%E5%BA%A6%E8%B0%83%E8%8A%82.md "音量及屏幕亮度调节" [144]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%BA%94%E7%94%A8%E5%AE%89%E8%A3%85.md "应用安装" [145]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E5%BA%94%E7%94%A8%E5%90%8E%E5%8F%B0%E5%94%A4%E9%86%92%E5%90%8E%E6%95%B0%E6%8D%AE%E7%9A%84%E5%88%B7%E6%96%B0.md "应用后台唤醒后数据的刷新" [146]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E7%9F%A5%E8%AF%86%E5%A4%A7%E6%9D%82%E7%83%A9.md "知识大杂烩" [147]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%B5%84%E6%BA%90%E6%96%87%E4%BB%B6%E6%8B%B7%E8%B4%9D%E7%9A%84%E4%B8%89%E7%A7%8D%E6%96%B9%E5%BC%8F.md "资源文件拷贝的三种方式" [148]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%87%AA%E5%AE%9A%E4%B9%89%E8%83%8C%E6%99%AF.md "自定义背景" [149]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8E%A7%E4%BB%B6.md "自定义控件" [150]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%87%AA%E5%AE%9A%E4%B9%89%E7%8A%B6%E6%80%81%E6%A0%8F%E9%80%9A%E7%9F%A5.md "自定义状态栏通知" [151]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/%E8%87%AA%E5%AE%9A%E4%B9%89Toast.md "自定义Toast" [152]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/adb%20logcat%E4%BD%BF%E7%94%A8%E7%AE%80%E4%BB%8B.md "adb logcat使用简介" [153]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Android%E7%BC%96%E7%A0%81%E8%A7%84%E8%8C%83.md "Android编码规范" [154]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Android%E5%8A%A8%E7%94%BB.md "Android动画" [155]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Android%E5%9F%BA%E7%A1%80%E9%9D%A2%E8%AF%95%E9%A2%98.md "Android基础面试题" [156]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Android%E5%85%A5%E9%97%A8%E4%BB%8B%E7%BB%8D.md "Android入门介绍" [157]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Android%E5%9B%9B%E5%A4%A7%E7%BB%84%E4%BB%B6%E4%B9%8BContentProvider.md "Android四大组件之ContentProvider" [158]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Android%E5%9B%9B%E5%A4%A7%E7%BB%84%E4%BB%B6%E4%B9%8BService.md "Android四大组件之Service" [159]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Ant%E6%89%93%E5%8C%85.md "Ant打包" [160]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Bitmap%E4%BC%98%E5%8C%96.md "Bitmap优化" [161]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Fragment%E4%B8%93%E9%A2%98.md "Fragment专题" [162]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Home%E9%94%AE%E7%9B%91%E5%90%AC.md "Home键监听" [163]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/HttpClient%E6%89%A7%E8%A1%8CGet%E5%92%8CPost%E8%AF%B7%E6%B1%82.md "HttpClient执行Get和Post请求" [164]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/JNI_C%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80.md "JNI_C语言基础" [165]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/JNI%E5%9F%BA%E7%A1%80.md "JNI基础" [166]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/ListView%E4%B8%93%E9%A2%98.md "ListView专题" [167]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Parcelable%E5%8F%8ASerializable.md "Parcelable及Serializable" [168]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/PopupWindow%E7%BB%86%E8%8A%82.md "PopupWindow细节" [169]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Scroller%E7%AE%80%E4%BB%8B.md "Scroller简介" [170]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/ScrollingTabs.md "ScrollingTabs" [171]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/SDK%20Manager%E6%97%A0%E6%B3%95%E6%9B%B4%E6%96%B0%E7%9A%84%E9%97%AE%E9%A2%98.md "SDK Manager无法更新的问题" [172]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Selector%E4%BD%BF%E7%94%A8.md "Selector使用" [173]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/SlidingMenu.md "SlidingMenu" [174]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/String%E6%A0%BC%E5%BC%8F%E5%8C%96.md "String格式化" [175]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/TextView%E8%B7%91%E9%A9%AC%E7%81%AF%E6%95%88%E6%9E%9C.md "TextView跑马灯效果" [176]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/WebView%E6%80%BB%E7%BB%93.md "WebView总结" [177]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Widget(%E7%AA%97%E5%8F%A3%E5%B0%8F%E9%83%A8%E4%BB%B6).md "Widget(窗口小部件)" [178]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/Wifi%E7%8A%B6%E6%80%81%E7%9B%91%E5%90%AC.md "Wifi状态监听" [179]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/BasicKnowledge/XmlPullParser.md "XmlPullParser" [180]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%B8%80).md "Kotlin学习教程(一)" [181]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%BA%8C).md "Kotlin学习教程(二)" [182]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%B8%89).md "Kotlin学习教程(三)" [183]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E5%9B%9B).md "Kotlin学习教程(四)" [184]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%BA%94).md "Kotlin学习教程(五)" [185]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E5%85%AD).md "Kotlin学习教程(六)" [186]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%B8%83).md "Kotlin学习教程(七)" [187]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E5%85%AB).md "Kotlin学习教程(八)" [188]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E4%B9%9D).md "Kotlin学习教程(九)" [189]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%85%AB%E7%A7%8D%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.md "八种排序算法" [190]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%AE%80%E4%BB%8B.md "线程池简介" [191]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md "设计模式" [192]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E7%AE%97%E6%B3%95%E7%9A%84%E5%A4%8D%E6%9D%82%E5%BA%A6.md "算法复杂度" [193]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86.md "动态代理" [194]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/ConstraintLaayout%E7%AE%80%E4%BB%8B.md "ConstraintLaayout简介" [195]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/Http%E4%B8%8EHttps%E7%9A%84%E5%8C%BA%E5%88%AB.md "Http与Https的区别" [196]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/JavaKnowledge/Top-K%E9%97%AE%E9%A2%98.md "Top-K问题" [197]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/KotlinCourse/Kotlin%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B(%E5%8D%81).md "Kotlin学习教程(十)" [198]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AppPublish/%E4%BD%BF%E7%94%A8Jenkins%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E5%8C%96%E6%89%93%E5%8C%85.md "使用Jenkins实现自动化打包" [199]: https://github.com/pengMaster/BestNote/tree/master/docs/android/AndroidNote//Dagger2 "Dagger2" [200]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/1.Dagger2%E7%AE%80%E4%BB%8B(%E4%B8%80).md "1.Dagger2简介(一).md" [201]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/2.Dagger2%E5%85%A5%E9%97%A8demo(%E4%BA%8C).md "2.Dagger2入门demo(二).md" [202]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/3.Dagger2%E5%85%A5%E9%97%A8demo%E6%89%A9%E5%B1%95(%E4%B8%89).md "3.Dagger2入门demo扩展(三).md" [203]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/4.Dagger2%E5%8D%95%E4%BE%8B(%E5%9B%9B).md "4.Dagger2单例(四).md" [204]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/5.Dagger2Lay%E5%92%8CProvider(%E4%BA%94).md "5.Dagger2Lay和Provider(五).md" [205]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/6.Dagger2Android%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81(%E5%85%AD).md "6.Dagger2Android示例代码(六).md" [206]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/7.Dagger2%E4%B9%8Bdagger-android(%E4%B8%83).md "7.Dagger2之dagger-android(七).md" [207]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/8.Dagger2%E4%B8%8EMVP(%E5%85%AB).md "8.Dagger2与MVP(八).md" [208]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/AdavancedPart/Android%20WorkManager.md "Android WorkManager.md" [209]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/4.RxJava%E8%AF%A6%E8%A7%A3%E4%B9%8B%E6%89%A7%E8%A1%8C%E5%8E%9F%E7%90%86(%E5%9B%9B).md "4.RxJava详解之执行原理(四)" [210]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/5.RxJava%E8%AF%A6%E8%A7%A3%E4%B9%8B%E6%93%8D%E4%BD%9C%E7%AC%A6%E6%89%A7%E8%A1%8C%E5%8E%9F%E7%90%86(%E4%BA%94).md "5.RxJava详解之操作符执行原理(五)" [211]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/RxJavaPart/6.RxJava%E8%AF%A6%E8%A7%A3%E4%B9%8B%E7%BA%BF%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%8E%9F%E7%90%86(%E5%85%AD).md "6.RxJava详解之线程调度原理(六)" [212]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Dagger2/9.Dagger2%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90(%E4%B9%9D).md "9.Dagger2原理分析(九)" [213]: https://github.com/pengMaster/BestNote/blob/master/docs/android/AndroidNote/Tools%26Library/%E8%B0%83%E8%AF%95%E5%B9%B3%E5%8F%B0Sonar.md "调试平台Sonar" ================================================ FILE: docs/.nojekyll ================================================ ================================================ FILE: docs/android/Android-Interview/.gitignore ================================================ # Node rules: ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt ## Dependency directory ## Commenting this out is preferred by some people, see ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git node_modules # Book build output _book # eBook build output *.epub *.mobi *.pdf ================================================ FILE: docs/android/Android-Interview/Activity/Activity Task和Process.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) - [深刻剖析activity启动模式(一)](http://www.jianshu.com/p/b33fd8c550bf) - [深刻剖析activity启动模式(二)](http://www.jianshu.com/p/e1ea9e542112) - [深刻剖析activity启动模式(三)](http://www.jianshu.com/p/d13e3d552d4b) - [Activity Task和Process之间的关系](http://www.jianshu.com/p/d13e3d552d4b) - [service里面startActivity抛异常?activity不会](http://www.jianshu.com/p/16e880ceb3a4) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) - [字体适配](http://www.jianshu.com/p/33d499170e25) - [软键盘顶出去解决方案](http://www.jianshu.com/p/640bac6f58ab)与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) AMS提供了一个ArrayList mHistory来管理所有的activity,activity在AMS中的形式是ActivityRecord,task在AMS中的形式为TaskRecord,进程在AMS中的管理形式为ProcessRecord。如下图所示 ![img](http://upload-images.jianshu.io/upload_images/4037105-1a0f2628f74ca65e?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 从图中我们可以看出如下几点规则: 1) 所有的ActivityRecord会被存储在mHistory管理; 2) 每个ActivityRecord会对应到一个TaskRecord,并且有着相同TaskRecord的ActivityRecord在mHistory中会处在连续的位置; 3) 同一个TaskRecord的Activity可能分别处于不同的进程中,每个Activity所处的进程跟task没有关系; Activity启动时ActivityManagerService会为其生成对应的ActivityRecord记录,并将其加入到回退栈(back stack)中,另外也会将ActivityRecord记录加入到某个Task中。请记住,ActivityRecord,backstack,Task都是ActivityManagerService的对象,由ActivityManagerService进程负责维护,而不是由应用进程维护。 在回退栈里属于同一个task的ActivityRecord会放在一起,也会形成栈的结构,也就是说后启动的Activity对应的ActivityRecord会放在task的栈顶 执行adb shell dumpsys activity命令,发现有以下输出: ``` Task id #30 TaskRecord{7f2f34a #30 A=com.maweiqi.second U=0 sz=2} Intent { flg=0x10000000 cmp=com.open.android.task1/.SecondActivity } Hist #1: ActivityRecord{a0f9ded u0 com.open.android.task3/.OtherActivity t30} Intent { flg=0x10400000 cmp=com.open.android.task3/.OtherActivity } ProcessRecord{12090b5 27543:com.open.android.task3/u0a62} Hist #0: ActivityRecord{1048af6 u0 com.open.android.task1/.SecondActivity t30} Intent { flg=0x10000000 cmp=com.open.android.task1/.SecondActivity } ProcessRecord{5bc013e 26035:com.open.android.task1/u0a59} Task id #31 TaskRecord{dce52bb #31 A=com.open.android.task3 U=0 sz=1} Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task3/.MainActivity } Hist #0: ActivityRecord{f9e58c5 u0 com.open.android.task3/.MainActivity t31} Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task3/.MainActivity } ProcessRecord{12090b5 27543:com.open.android.task3/u0a62} Task id #29 TaskRecord{5b063d8 #29 A=com.open.android.task1 U=0 sz=1} Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task1/.MainActivity } Hist #0: ActivityRecord{689947d u0 com.open.android.task1/.MainActivity t29} Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task1/.MainActivity } ProcessRecord{5bc013e 26035:com.open.android.task1/u0a59} Running activities (most recent first): TaskRecord{7f2f34a #30 A=com.maweiqi.second U=0 sz=2} Run #3: ActivityRecord{a0f9ded u0 com.open.android.task3/.OtherActivity t30} TaskRecord{dce52bb #31 A=com.open.android.task3 U=0 sz=1} Run #2: ActivityRecord{f9e58c5 u0 com.open.android.task3/.MainActivity t31} TaskRecord{7f2f34a #30 A=com.maweiqi.second U=0 sz=2} Run #1: ActivityRecord{1048af6 u0 com.open.android.task1/.SecondActivity t30} TaskRecord{5b063d8 #29 A=com.open.android.task1 U=0 sz=1} Run #0: ActivityRecord{689947d u0 com.open.android.task1/.MainActivity t29} mResumedActivity: ActivityRecord{a0f9ded u0 com.open.android.task3/.OtherActivity t30} ``` 从图对应文字在对应adb输出,这几个基本概念大家估计就清楚了。 - 欢迎关注微信公众号,长期推荐技术文章和技术视频 - 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/Activity/App优雅退出.md ================================================ ### **1. RxBus优雅式** 首先,在基类BaseActivity里,注册RxBus监听: ```java public class BaseActivity3 extends AppCompatActivity { Subscription mSubscription; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); initRxBus(); } //接收退出的指令,关闭所有activity private void initRxBus() { mSubscription = RxBus.getInstance().toObserverable(NormalEvent.class) .subscribe(new Action1() { @Override public void call(NormalEvent userEvent) { if (userEvent.getType() == -1) { finish(); } } }, new Action1() { @Override public void call(Throwable throwable) { } }); } @Override protected void onDestroy() { super.onDestroy(); if (!mSubscription.isUnsubscribed()) { mSubscription.unsubscribe(); } } } ``` 这是事件实体NormalEvent: ```java public class NormalEvent { private int type; public NormalEvent(int type) { this.type = type; } public int getType() { return type; } public void setType(int type) { this.type = type; } } ``` 新建RxBus类 ```java public class RxBus { private static volatile RxBus mInstance; private final Subject bus; public RxBus() { bus = new SerializedSubject<>(PublishSubject.create()); } /** * 单例模式RxBus * * @return */ public static RxBus getInstance() { RxBus rxBus2 = mInstance; if (mInstance == null) { synchronized (RxBus.class) { rxBus2 = mInstance; if (mInstance == null) { rxBus2 = new RxBus(); mInstance = rxBus2; } } } return rxBus2; } /** * 发送消息 * * @param object */ public void post(Object object) { bus.onNext(object); } /** * 接收消息 * * @param eventType * @param * @return */ public Observable toObserverable(Class eventType) { return bus.ofType(eventType); } } ``` 最后,在需要退出的地方调用: ```java RxBus.getInstance().post(new NormalEvent(-1));//发送退出指令 ``` ### **2. 容器式:** 建立一个全局容器,把所有的Activity存储起来,退出时循环遍历finish所有Activity ```java public class BaseActivity extends AppCompatActivity { @Override public void onCreate(@Nullable Bundle savedInstanceState ) { super.onCreate(savedInstanceState); ActivityManager.getActivityManager().addActivity(this); } @Override protected void onDestroy() { super.onDestroy(); // 结束Activity&从栈中移除该Activity ActivityManager.getActivityManager().finishActivity(); } } public class ActivityManager { // Activity栈 private static Stack activityStack; // 单例模式 private static ActivityManager instance; private ActivityManager() { } /** * 单一实例 */ public static ActivityManager getActivityManager() { if (instance == null) { instance = new ActivityManager(); } return instance; } /** * 添加Activity到堆栈 */ public void addActivity(Activity activity) { if (activityStack == null) { activityStack = new Stack(); } activityStack.add(activity); } /** * 获取当前Activity(堆栈中最后一个压入的) */ public Activity currentActivity() { Activity activity = activityStack.lastElement(); return activity; } /** * 结束当前Activity(堆栈中最后一个压入的) */ public void finishActivity() { Activity activity = activityStack.lastElement(); finishActivity(activity); } /** * 结束指定的Activity */ public void finishActivity(Activity activity) { if (activity != null) { activityStack.remove(activity); activity.finish(); activity = null; } } /** * 结束指定类名的Activity */ public void finishActivity(Class cls) { for (Activity activity : activityStack) { if (activity.getClass().equals(cls)) { finishActivity(activity); } } } /** * 结束所有Activity */ public void finishAllActivity() { for (int i = 0; i < activityStack.size(); i++) { if (null != activityStack.get(i)) { activityStack.get(i).finish(); } } activityStack.clear(); } /** * 退出应用程序 */ public void AppExit(Context context) { try { finishAllActivity(); //根据进程ID,杀死该进程 android.os.Process.killProcess(android.os.Process.myPid()); //退出真个应用程序 System.exit(0); } catch (Exception e) { } } } ``` ### **3. 广播式** 通过在BaseActivity中注册一个广播,当退出时发送一个广播,finish退出 ```java public class BaseActivity2 extends AppCompatActivity { private static final String EXITACTION = "action.exit"; private ExitReceiver exitReceiver = new ExitReceiver(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); IntentFilter filter = new IntentFilter(); filter.addAction(EXITACTION); registerReceiver(exitReceiver, filter); } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(exitReceiver); } class ExitReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { BaseActivity2.this.finish(); } } } ``` ### **4. SingleTask** 1、设置MainActivity的加载模式为singleTask ```xml android:launchMode="singleTask" ``` 2、将退出出口放置在MainActivity ```java private boolean mIsExit; @Override /** * 双击返回键退出 */ public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { if (mIsExit) { this.finish(); } else { Toast.makeText(this, "再按一次退出", Toast.LENGTH_SHORT).show(); mIsExit = true; new Handler().postDelayed(new Runnable() { @Override public void run() { mIsExit = false; } }, 2000); } return true; } return super.onKeyDown(keyCode, event); } ``` ### **5. SingleTask改版式** 第一步设置MainActivity的加载模式为singleTask ```xml android:launchMode="singleTask" ``` 第二步重写onNewIntent()方法 ```java private static final String TAG_EXIT = "exit"; @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); if (intent != null) { boolean isExit = intent.getBooleanExtra(TAG_EXIT, false); if (isExit) { this.finish(); } } } ``` 第三步 退出 ```java Intent intent = new Intent(this,MainActivity.class); intent.putExtra(MainActivity.TAG_EXIT, true); startActivity(intent); ``` ================================================ FILE: docs/android/Android-Interview/Activity/README.md ================================================ ## Activity相关面试题 - [onSaveInstanceState源码内核分析](onSaveInstanceState源码内核分析.md) - [深刻剖析activity启动模式-1](深刻剖析activity启动模式-1.md) - [深刻剖析activity启动模式-2](深刻剖析activity启动模式-2.md) - [深刻剖析activity启动模式-3](深刻剖析activity启动模式-3.md) - [Activity Task和Process之间的关系](Activity Task和Process.md) - [为什么service里面startActivity抛异常](为什么service里面startActivity抛异常.md) - [App优雅退出](Android面试题-app优雅退出.md) - [onCreate源码分析](onCreate源码分析.md) ================================================ FILE: docs/android/Android-Interview/Activity/onCreate源码分析.md ================================================ Activity扮演了一个界面展示的角色,堪称四大组件之首,onCreate是Activity的执行入口,都不知道入口到底干了嘛,还学什么android,所以本文会从源码的角度对其进行分析。 熟悉源码的会发现,真正启动Activity的实现都在ActivityThread,前面的调用过程略过 ActivityThread的方法performLaunchActivity中调用了Instrumentation类中的方法callActivityOnCreate方法,继而调用了TargetActivity中的onCreate方法。 ```java private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ...... Activity activity = null; activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); ...... if (r.isPersistable()) { mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); } else { mInstrumentation.callActivityOnCreate(activity, r.state); } ...... } ``` #### **源码可知:** 1)通过反射的机制创建的Activity 2)这里的mInstrumentation是类Instrumentation 3)Instrumentation类中的方法callActivityOnCreate方法源码如下: ```java public void callActivityOnCreate(Activity activity, Bundle icicle) { prePerformCreate(activity); activity.performCreate(icicle); postPerformCreate(activity); } ``` #### **源码可知:** 1)activity.performCreate(icicle),其中的方法是通过activity,这个activity,形如:Activity activity = 子Activity的对象 2)在Activity类中的方法performCreate(icicle),源码如下: ```java final void performCreate(Bundle icicle) { onCreate(icicle); mActivityTransitionState.readState(icicle); performCreateCommon(); } ``` #### **源码可知:** 1)原来onCreate的生命周期方法是在这里回调的 2)在performCreate方法中调用的onCreate方法是MainActivity中的onCreate方法,那么到此MainActivity中的方法onCreate方法中的参数Bundle savedInstanceState也就知道来源了,此时,MainActivity中的方法也就被调用了。 再次看MainActivity中的方法onCreate: ```java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ``` super.onCreate(savedInstanceState),其实这条语句放在子类中的onCreate方法中的任何位置都可,问题只是super.onCreate(savedInstanceState)必须要被执行,所以,最好也就是放在第一行,看起来比较明确,至于为什么,参考[onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) 至此onCreate源码分析完毕。 ================================================ FILE: docs/android/Android-Interview/Activity/onSaveInstanceState源码内核分析.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) 经常有人问,后台的activity被系统自动回收的话,怎么回到界面的时候恢复数据,通过一个真实案例给大家讲讲如何保存状态,然后带着大家分析onSaveInstanceState的源码。 ![img](https://upload-images.jianshu.io/upload_images/4037105-79dd4bae8a99c6df?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### **当前页面侧滑菜单指向专题,用户做了如下操作:** 1)当用户按下HOME键时。 2)长按HOME键,选择运行其他的程序时。 3)按下电源按键(关闭屏幕显示)时。 4)从activity A中启动一个新的activity时。 5)屏幕方向切换时,例如从竖屏切换到横屏时。 失去焦点,activity很可能被进程终止!被KILL掉了,这时候就需要能保存当前的状态,不然下次用户再次进来看到的还是新闻,这样用户体验就不够好,代码有删减,我自己项目就这样使用的,解决方案如下: ```java @Override public void onSaveInstanceState(Bundle outState) { outState.putInt("newsCenter_position", newsCenterPosition); outState.putInt("smartService_position", smartServicePosition); outState.putInt("govAffairs_position", govAffairsPosition); super.onSaveInstanceState(outState); } ``` #### 如上代码可知: 1)界面被回收之后调用onSaveInstanceState方法保存当前的状态,每个侧滑菜单选项都有一个位置。 ```java @Override public void onCreate(Bundle savedInstanceState) { if (savedInstanceState != null && savedInstanceState.containsKey("newsCenter_position")) { newsCenterPosition = savedInstanceState .getInt("newsCenter_position"); smartServicePosition = savedInstanceState .getInt("smartService_position"); govAffairsPosition = savedInstanceState .getInt("govAffairs_position"); } super.onCreate(savedInstanceState); } ``` #### **由以上代码可知:** 1)判断当前Bundle 是否有刚刚我们保存的位置,如果不为空,从当前的Bundle取出来,给每一个位置赋值。 ```java public void switchMenu(int type) { switch (type) { case NEWS_CENTER: if (newsCenterAdapter == null) { newsCenterAdapter = new MenuAdapter(ct, newsCenterMenu); newsCenterclassifyLv.setAdapter(newsCenterAdapter); } else { newsCenterAdapter.notifyDataSetChanged(); } newsCenterAdapter.setSelectedPosition(newsCenterPosition); break; case SMART_SERVICE: if (smartServiceAdapter == null) { smartServiceAdapter = new MenuAdapter(ct, smartServiceMenu); smartServiceclassifyLv.setAdapter(smartServiceAdapter); } else { smartServiceAdapter.notifyDataSetChanged(); } smartServiceAdapter.setSelectedPosition(smartServicePosition); break; case GOV_AFFAIRS: if (govAffairsAdapter == null) { govAffairsAdapter = new MenuAdapter(ct, govAffairsMenu); govAffairsclassifyLv.setAdapter(govAffairsAdapter); } else { govAffairsAdapter.notifyDataSetChanged(); } govAffairsAdapter.setSelectedPosition(govAffairsPosition); break; } ``` #### **以上代码可知:** 1)根据当前的位置设置到adapter当中,这样下次用户进来就还是专题了。 #### **总结下savedInstanceState的使用,代码如下:** ```java public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(savedInstanceState != null) System.out.println("onCreate() : " + savedInstanceState.getString("octopus")); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); System.out.println("onRestoreInstanceState() : " + savedInstanceState.getString("octopus")); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString("octopus", "www.baidu.com"); System.out.println("onSaveInstanceState() : save date www.baidu.com"); } } ``` #### **横竖屏切换,打印结果如下:** ``` I/System.out( 8167): onSaveInstanceState() : save date www.baidu.com I/System.out( 8167): onCreate() : www.baidu.com I/System.out( 8167): onRestoreInstanceState() : www.baidu.com ``` 从打印结果可以看出来,当前Activity被系统回收之后,会调用onSaveInstanceState()保存状态,然后在activity判断bundler是否有当前状态,如果只是到这,估计你们就会吐槽没啥含金量,没办法硬着头皮上,接着咱们来分onSaveInstanceState()源码,请看如下代码: ```java @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); } ``` #### **以上代码可知** 1)调用父类Activity源码里面的onSaveInstanceState方法,代码如下: ```java protected void onSaveInstanceState(Bundle outState) { outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState()); Parcelable p = mFragments.saveAllState(); if (p != null) { outState.putParcelable(FRAGMENTS_TAG, p); } ...... } ``` #### **以上代码可知** 1)outState.put一个tag调用了mWindow里面的saveHierarchyState方法,继续分析Window源代码。 2)window是抽象类调用子类PhoneWindow里面的saveHierarchyState方法代码如下: ```java @Override public Bundle saveHierarchyState() { Bundle outState = new Bundle(); if (mContentParent == null) { return outState; } SparseArray states = new SparseArray(); mContentParent.saveHierarchyState(states); outState.putSparseParcelableArray(VIEWS_TAG, states); ...... return outState; } ``` #### **以上代码可知** 1 ) Bundle outState = new Bundle()初始化Bundle对象,Bundle实现了Parcelable接口。 2)states = new SparseArray()并且把自己放到outState当中。 3)mContentParent.saveHierarchyState(states),整个View树的顶层视图保存了层级状态代码如下: ```java public void saveHierarchyState(SparseArray container) { dispatchSaveInstanceState(container); } ``` #### 以上代码可知: 1)调相应的dispatchSaveInstanceState方法,代码如下: ```java protected void dispatchSaveInstanceState(SparseArray container) { if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) { mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED; Parcelable state = onSaveInstanceState(); if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) { throw new IllegalStateException( "Derived class did not call super.onSaveInstanceState()"); } if (state != null) { // Log.i("View", "Freezing #" + Integer.toHexString(mID) // + ": " + state); container.put(mID, state); } } } ``` #### 以上代码可知: 1) mID != NO_ID 判断一个View必须有一个id,也就是说你要么在xml里通过android:id指定要么在代码里通过setId,但是你从如上代码压根是看不出来谷歌想干啥,必须全局搜索NO_ID 和 mID ,一般在源码里面都会有谷歌工程师的注释方便我们理解,搜索NO_ID 可知代码如下: ```java /** * Used to mark a View that has no ID. */ public static final int NO_ID = -1; ``` 原来NO_ID用来标记没有id的View,搜索mID可知原来在如下代码赋值 ```java public void setId(@IdRes int id) { mID = id; if (mID == View.NO_ID && mLabelForId != View.NO_ID) { mID = generateViewId(); } } ``` 经常当我们看不懂谷歌源码的时候,可以通过曲线救国的方式,看看英文注释,看看源码哪个地方用到当前的类或者方法或者变量,这样就好理解了,好了扯远了,继续分析代码; 2)通过if判断,检测子类是否调用父类的onSaveInstanceState()方法,否则会抛异常,突然看到这才明白,还记得刚刚开始学Android的时候,经常一不小心就把代码里面的super.onCreate(savedInstanceState);这行代码删掉,报了错误还看不懂,原来系统在这里检测了,都怪自己曾经太年轻。 3)container.put(mID, state)这行代码,将state放进SparseArray中,以view自身的id为key,并且从注释来看打印mID的Hex值用来保证每页的id必须是唯一的,难怪每当我给view取id的时候,一个页面有重复的id就会报错,谷歌大婶在这里做判断了,腻害了word哥,总是百思不得其姐,凭啥不让我共用id(因为取名字太难了),原来是想把id做为key来使用。 4)走到这onSaveInstanceState(),调用如下代码: ```java @CallSuper protected Parcelable onSaveInstanceState() { mPrivateFlags |= PFLAG_SAVE_STATE_CALLED; ...... return BaseSavedState.EMPTY_STATE; } ``` #### 以上代码可知: 1)设置位标志, 默认不save任何东西,状态为空,这就是为啥我们每次随便写个类继承activity实现onCreate方法的时候可以使用参数savedInstanceState保存状态,因为默认为null,代码如下: ```java protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); savedInstanceState.putString("key","value"); } ``` 至此整个savedInstanceState保存状态源码分析完毕。 - 欢迎关注微信公众号,长期推荐技术文章和技术视频 - 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/Activity/为什么service里面startActivity抛异常.md ================================================ **Android面试题-源码分析为什么service里面startActivity抛异常?activity不会** 我们有时候需要在service里面启动activity,但是会发现报如下异常: ![img](http://upload-images.jianshu.io/upload_images/4037105-8dd39209dfdb4961?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 必须添加FLAG_ACTIVITY_NEW_TASK这个标记就可以了,那么为什么在activity里面不需要呢?接下来通过从源码角度带大家分析。 ### **启动activity有两种形式** 1)直接调用Context类的startActivity方法;这种方式启动的Activity没有Activity栈,因此不能以standard方式启动,必须加上FLAG_ACTIVITY_NEW_TASK这个Flag,服务就是通过Context调用。 2)调用被Activity类重载过的startActivity方法,通常在我们的Activity中直接调用这个方法就是这种形式; ### **Context.startActivity源码分析** 我们查看Context类的startActivity方法,发现这竟然是一个抽象类;查看Context的类继承关系图如下: ![img](http://upload-images.jianshu.io/upload_images/4037105-1a5f12db8f551acf?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 我们看到诸如Activity,Service等并没有直接继承Context,Activity继承了ContextThemeWrapper,Service而是继承了ContextWrapper; #### **现在从源码分析ContextWrapper的实现:** ```java @Override public void startActivity(Intent intent) { mBase.startActivity(intent); } ``` 这个mBase是什么呢?这里我先直接告诉你,它的真正实现是ContextImpl类;至于为什么,有一条思路:在任意mBase打一个断点就能看到实现。 Context.startActivity最终使用了ContextImpl里面的方法,代码如下: ```java @Override public void startActivity(Intent intent, Bundle options) { warnIfCallingFromSystemProcess(); if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { throw new AndroidRuntimeException( "Calling startActivity() from outside of an Activity " + " context requires the FLAG_ACTIVITY_NEW_TASK flag." + " Is this really what you want?"); } mMainThread.getInstrumentation().execStartActivity( getOuterContext(), mMainThread.getApplicationThread(), null, (Activity) null, intent, -1, options); } ``` #### **源码分析:** 1)大家看看抛出来的异常是不是还是熟悉的味道。 2)通过判断可知当前的intent.getFlags是否带有FLAG_ACTIVITY_NEW_TASK这个标记,如果没有抛出异常,因为源码使用了&运算符,只有两个位都是1,结果才是1,所以可知service没有带FLAG_ACTIVITY_NEW_TASK标记,才抛出异常。 3)真正的startActivity使用了Instrumentation类的execStartActivity方法;继续跟踪: ```java public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) { ...... try { ...... int result = ActivityManagerNative.getDefault() .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mEmbeddedID : null, requestCode, 0, null, options); checkStartActivityResult(result, intent); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); } return null; ``` #### **源码分析:** 1)到这里我们发现真正调用的是ActivityManagerNative的startActivity方法; ### **Activity.startActivity源码分析** ```java @Override public void startActivity(Intent intent) { this.startActivity(intent, null); } ``` #### **源码可知:** 1)调用当前类的startActivity方法,代码如下: ```java @Override public void startActivity(Intent intent, @Nullable Bundle options) { if (options != null) { startActivityForResult(intent, -1, options); } else { // Note we want to go through this call for compatibility with // applications that may have overridden the method. startActivityForResult(intent, -1); } } ``` #### **源码可知** 1)调用了startActivityForResult ```java public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) { ...... Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options); ...... ``` #### **源码可知** 1)可以看到,其实通过Activity和ContextImpl类启动Activity并无本质不同,他们都通过Instrumentation这个辅助类调用到了ActivityManagerNative的方法。 2)只是Activity不会去检查标记,所以并不会抛出异常。 至此源码分析完毕。 ================================================ FILE: docs/android/Android-Interview/Activity/深入理解Activity启动流程.md ================================================ > 原文链接:[Cloud Chou](http://weibo.com/muguachou). http://www.cloudchou.com/android/post-788.html ## 概述 Android中启动某个Activity,将先启动Activity所在的应用。应用启动时会启动一个以应用包名为进程名的进程,该进程有一个主线程,叫ActivityThread,也叫做UI线程。 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究。 - [深入理解Activity启动流程(二)--Activity启动相关类的类图](http://www.cloudchou.com/android/post-793.html) - [深入理解Activity启动流程(三)--Activity启动的详细流程1](http://www.cloudchou.com/android/post-805.html) - [深入理解Activity启动流程(三)--Activity启动的详细流程2](http://www.cloudchou.com/android/post-815.html) - [深入理解Activity启动流程(四)--Activity Task的调度算法](http://www.cloudchou.com/android/post-858.html) ## Activity启动时的概要交互流程 用户从Launcher程序点击应用图标可启动应用的入口Activity,Activity启动时需要多个进程之间的交互,Android系统中有一个zygote进程专用于孵化Android框架层和应用层程序的进程。还有一个system_server进程,该进程里运行了很多binder service,例如ActivityManagerService,PackageManagerService,WindowManagerService,这些binder service分别运行在不同的线程中,其中ActivityManagerService负责管理Activity栈,应用进程,task。 Activity启动时的概要交互流程如下图如下所示(点击图片可看[大图](http://www.cloudchou.com/wp-content/uploads/2015/05/activity_start_flow.png)): ![activity_start_flow](../assets/activity_start_flow.png) 用户在Launcher程序里点击应用图标时,会通知ActivityManagerService启动应用的入口Activity,ActivityManagerService发现这个应用还未启动,则会通知Zygote进程孵化出应用进程,然后在这个dalvik应用进程里执行ActivityThread的main方法。应用进程接下来通知ActivityManagerService应用进程已启动,ActivityManagerService保存应用进程的一个代理对象,这样ActivityManagerService可以通过这个代理对象控制应用进程,然后ActivityManagerService通知应用进程创建入口Activity的实例,并执行它的生命周期方法。 ================================================ FILE: docs/android/Android-Interview/Activity/深刻剖析activity启动模式-1.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) - [深刻剖析activity启动模式(一)](http://www.jianshu.com/p/b33fd8c550bf) - [深刻剖析activity启动模式(二)](http://www.jianshu.com/p/e1ea9e542112) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### **Activity的四种启动模式如下:** **standard、singleTop、singleTask、singleInstance** 我们一边讲理论一边结合案例来全面学习这四种启动模式。 为了打印方便,定义一个基础BaseActivity,在其onCreate方法和onNewIntent方法中打印出当前Activity的日志信息,主要包括所属的task,当前类的hashcode,之后我们进行测试的Activity都直接继承该BaseActivity ```java public class BaseActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i("maweiqi", "*****onCreate()方法******"); Log.i("maweiqi", "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode()); dumpTaskAffinity(); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); Log.i("maweiqi", "*****onNewIntent()方法*****"); Log.i("maweiqi", "onNewIntent:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode()); dumpTaskAffinity(); } protected void dumpTaskAffinity(){ try { ActivityInfo info = this.getPackageManager() .getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); Log.i("maweiqi", "taskAffinity:"+info.taskAffinity); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } } } ``` ### **standard-默认模式** 这个模式是默认的启动模式,即标准模式,在不指定启动模式的前提下,系统默认使用该模式启动Activity,每次启动一个Activity都会重写创建一个新的实例,不管这个实例存不存在,这种模式下,谁启动了该模式的Activity,该Activity就属于启动它的Activity的任务栈中。 #### **配置形式:** ```xml ``` #### **使用案例:** 对于standard模式,android:launchMode可以不进行声明,因为默认就是standard。 SecondActivity的代码如下,入口MainActivity中有一个按钮进入该Activity,这个Activity中又有一个按钮启动SecondActivity。 ```java public class MainActivity extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btn_standard= (Button) findViewById(R.id.btn_standard); btn_standard.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, SecondActivity.class); startActivity(intent); } }); Log.i("maweiqi", "*****onCreate()方法******"); Log.i("maweiqi", "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode()); } } ``` ```java public class SecondActivity extends BaseActivity { private Button jump; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); jump= (Button) findViewById(R.id.button3); jump.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(SecondActivity.this, SecondActivity.class); startActivity(intent); } }); } } ``` #### **测试方式:** MainActivity --> SecondActivity --> SecondActivity --> SecondActivity --> SecondActivity #### **输出的日志如下:** ``` MainActivity TaskId: 2 hasCode:1249333352 SecondActivity TaskId: 2 hasCode:1249526392 SecondActivity TaskId: 2 hasCode:1249424816 SecondActivity TaskId: 2 hasCode:1249439692 SecondActivity TaskId: 2 hasCode:1249459968 ``` #### **测试结果:** 1)日志输出了四次SecondActivity的和一次MainActivity的,并且所属的任务栈的id都是2,这也验证了谁启动了该模式的Activity,该Activity就属于启动它的Activity的任务栈中这句话.因为启动SecondActivity的是MainActivity,而MainActivity的taskId是2,因此启动的SecondActivity也应该属于id为2的这个task,后续的3个SecondActivity是被SecondActivity这个对象启动的,因此也应该还是2,所以taskId都是2。 2)并且每一个Activity的hashcode都是不一样的,说明他们是不同的实例,即“每次启动一个Activity都会重写创建一个新的实例” ### **singleTop模式** 这个模式下,如果新的activity已经位于栈顶,那么这个Activity不会被重新创建,同时它的onNewIntent方法会被调用,通过此方法的参数我们可以去除当前请求的信息。如果栈顶不存在该Activity的实例,则情况与standard模式相同。需要注意的是这个Activity它的onCreate(),onStart()方法不会被调用,因为它并没有发生改变。 #### **配置形式:** ```xml ``` #### **使用案例:** ```java public class SingleTopActivity extends BaseActivity { private Button jump, jump2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_singletop); jump = (Button) findViewById(R.id.button); jump2 = (Button) findViewById(R.id.button2); jump.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(SingleTopActivity.this, SingleTopActivity.class); startActivity(intent); } }); jump2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(SingleTopActivity.this, OtherTopActivity.class); startActivity(intent); } }); Log.i("maweiqi", "*****onCreate()方法******"); Log.i("maweiqi", "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode()); } } ``` ```java public class OtherTopActivity extends AppCompatActivity { private Button jump; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_other); jump= (Button) findViewById(R.id.btn_other); jump.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(OtherTopActivity.this, SingleTopActivity.class); startActivity(intent); } }); Log.i("maweiqi", "*****onCreate()方法******"); Log.i("maweiqi", "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode()); } } ``` #### **操作和standard模式类似,直接贴输出日志** ``` onCreate:MainActivity TaskId: 3 hasCode:1249332216 onCreate:SingleTopActivity TaskId: 3 hasCode:1249464444 onNewIntent:SingleTopActivity TaskId: 3 hasCode:1249464444 onNewIntent:SingleTopActivity TaskId: 3 hasCode:1249464444 onNewIntent:SingleTopActivity TaskId: 3 hasCode:1249464444 ``` #### **测试结果:** 1)除了第一次进入SingleTopActivity这个Activity时,输出的是onCreate方法中的日志,后续的都是调用了onNewIntent方法,并没有调用onCreate方法,并且四个日志的hashcode都是一样的。 2)说明栈中只有一个实例。这是因为第一次进入的时候,栈中没有该实例,则创建,后续的三次发现栈顶有这个实例,则直接复用,并且调用onNewIntent方法。 3)那么假设栈中有该实例,但是该实例不在栈顶情况又如何呢? 我们先从MainActivity中进入到SingleTopActivity,然后再跳转到OtherActivity中,再从OtherActivity中跳回SingleTopActivity,再从SingleTopActivity跳到SingleTopActivity中,看看整个过程的日志。 #### **输出的日志如下:** ``` onCreate:SingleTopActivity TaskId: 4 hasCode:1249520904 onCreate:OtherTopActivity TaskId: 4 hasCode:1249420244 onCreate:SingleTopActivity TaskId: 4 hasCode:1249448776 onCreate:SingleTopActivity TaskId: 4 hasCode:1249448776 onNewIntent:SingleTopActivity TaskId: 4 hasCode:1249448776 ``` #### **测试结果:** 1)从MainActivity进入到SingleTopActivity时,新建了一个SingleTopActivity对象。 2)从SingleTopActivity跳到OtherActivity时,新建了一个OtherActivity,此时task中存在三个Activity,从栈底到栈顶依次是MainActivity,SingleTopActivity,OtherActivity。 3)此时如果再跳到SingleTopActivity,即使栈中已经有SingleTopActivity实例了,但是依然会创建一个新的SingleTopActivity实例,这一点从上面的日志的hashCode可以看出,此时栈顶是SingleTopActivity,如果再跳到SingleTopActivity,就会复用栈顶的SingleTopActivity,即会调用SingleTopActivity的onNewIntent方法。这就是上述日志的全过程。 #### **对以上内容进行总结** standard启动模式是默认的启动模式,每次启动一个Activity都会新建一个实例不管栈中是否已有该Activity的实例。 #### **singleTop模式分3种情况** 1)当前栈中已有该Activity的实例并且该实例位于栈顶时,不会新建实例,而是复用栈顶的实例,并且会将Intent对象传入,回调onNewIntent方法 2)当前栈中已有该Activity的实例但是该实例不在栈顶时,其行为和standard启动模式一样,依然会创建一个新的实例 3)当前栈中不存在该Activity的实例时,其行为同standard启动模式standard和singleTop启动模式都是在原任务栈中新建Activity实例,不会启动新的Task ### **singleTask模式** 在这个模式下,如果栈中存在这个Activity的实例就会复用这个Activity,不管它是否位于栈顶,复用时,会将它上面的Activity全部出栈,并且会回调该实例的onNewIntent方法。 #### **配置形式:** ```xml ``` #### **使用案例:** ```java public class SingleTaskActivity extends BaseActivity { private Button jump,jump2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_task); jump = (Button) findViewById(R.id.btn_task); jump2 = (Button) findViewById(R.id.btn_other); jump.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(SingleTaskActivity.this, SingleTaskActivity.class); startActivity(intent); } }); jump2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(SingleTaskActivity.this, OtherTaskActivity.class); startActivity(intent); } }); }} ``` ```java public class OtherTaskActivity extends BaseActivity { private Button jump; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_other_task); jump= (Button) findViewById(R.id.btn_other); jump.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(OtherTaskActivity.this, SingleTaskActivity.class); startActivity(intent); } }); } } ``` #### **日志输出** ``` onCreate:MainActivity TaskId: 5 hasCode:1249321980 onCreate:SingleTaskActivity TaskId: 5 hasCode:1249515136 onCreate:OtherTaskActivity TaskId: 6 hasCode:1249386172 onNewIntent:SingleTaskActivity TaskId: 6 hasCode:1249513244 ``` #### **测试结果:** 1)从MainActiviyty进入到SingleTaskActivity,再进入到OtherActivity后,此时栈中有3个Activity实例,并且SingleTaskActivity不在栈顶。 2)而在OtherActivity跳到SingleTaskActivity时,并没有创建一个新的SingleTaskActivity,而是复用了该实例,并且回调了onNewIntent方法。并且原来的OtherActivity出栈了,具体见下面的信息,使用命令adb shell dumpsys activity activities可进行查看 ``` Running activities (most recent first): TaskRecord{3c727e #11 A=com.maweiqi.task U=0 sz=2} Run #1: ActivityRecord{5a00d1e u0 com.maweiqi.task/.SingleTaskActivity t11} Run #0: ActivityRecord{2dce0b u0 com.maweiqi.task/.MainActivity t11} ``` 可以看到当前栈中只有两个Activity,即原来栈中位于SingleTaskActivity 之上的Activity都出栈了。 ### **singleInstance-全局唯一模式** 该模式具备singleTask模式的所有特性外,与它的区别就是,这种模式下的Activity会单独占用一个Task栈,具有全局唯一性,即整个系统中就这么一个实例,由于栈内复用的特性,后续的请求均不会创建新的Activity实例,除非这个特殊的任务栈被销毁了。以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。 #### **singleInstance示例一配置形式:** ```xml ``` ```java public class MainActivity extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btn_standard= (Button) findViewById(R.id.btn_standard); btn_standard.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, SingleInstanceActivity.class); startActivity(intent); } }); } } ``` ```java public class SingleInstanceActivity extends BaseActivity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_instance); Button button4 = (Button) findViewById(R.id.button4); button4.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(SingleInstanceActivity.this, MainActivity.class); startActivity(intent); } }); } } ``` #### **测试方式:** MainActivity--> SingleInstanceActivity--> MainActivity--> SingleInstanceActivity #### **日志输出** ``` onCreate:MainActivity TaskId: 12 hasCode:201331476 onCreate:SingleInstanceActivity TaskId: 13 hasCode:57987178 onCreate:MainActivity TaskId: 12 hasCode:254253633 onNewIntent:SingleInstanceActivity TaskId: 13 hasCode:57987178 ``` #### **测试结果** 1)两个SingleInstanceActivity是同一个实例。 2) 第一次进入的MainActivity和第一次进入的SingleInstanceActivity位于不同的task中。 3) 两个MainActivity是位于同一个task中的不同实例。 4)这个结论与预期是相同的,即,singleInstance类型的Activity的实例只能有一个,而且它只允许存在于单独的一个task中。 5)至于为什么两个MainActivity是位于同一个task中的不同实例,那是因为它是standard类型的,我们可以将ActivityTest修改为singleTop等其他类型进行测试。 #### **singleInstance示例二配置形式:** MainActivity的模式改为"singleTop",修改后的manifest如下: ```xml ``` MainActivity进入SingleInstanceActivity,在从SingleInstanceActivity 进入MainActivity,在从MainActivity进入SingleInstanceActivity #### **日志输出** ``` onCreate:MainActivity TaskId: 15 hasCode:201331476 onCreate:SingleInstanceActivity TaskId: 16 hasCode:57987178 onNewIntent:MainActivity TaskId: 15 hasCode:201331476 onNewIntent:SingleInstanceActivity TaskId: 16 hasCode:57987178 ``` #### **测试结果** 1)两个SingleInstanceActivity是同一个实例。 2)第一次进入的MainActivity和第一次进入的SingleInstanceActivity位于不同的task中。 3)两个MainActivity是同一个实例。 ### **launchMode模式总结** #### **1. standard** 在该模式下,Activity可以拥有多个实例,并且这些实例既可以位于同一个task,也可以位于不同的task。 #### **2.singleTop** 该模式下,在同一个task中,如果存在该Activity的实例,并且该Activity实例位于栈顶(即,该Activity位于前端),则调用startActivity()时,不再创建该Activity的示例;而仅仅只是调用Activity的onNewIntent()。否则的话,则新建该Activity的实例,并将其置于栈顶。 #### **3. singleTask** 只容许有一个包含该Activity实例的task存在! 总的来说:singleTask的结论与android:taskAffinity相关(下章在讲),以A启动B来说 1) 当A和B的taskAffinity相同时:第一次创建B的实例时,并不会启动新的task,而是直接将B添加到A所在的task;否则,将B所在task中位于B之上的全部Activity都删除,然后跳转到B中。 2) 当A和B的taskAffinity不同时:第一次创建B的实例时,会启动新的task,然后将B添加到新建的task中;否则,将B所在task中位于B之上的全部Activity都删除,然后跳转到B中。 #### **4. singleInstance** 顾名思义,是单一实例的意思,即任意时刻只允许存在唯一的Activity实例,而且该Activity所在的task不能容纳除该Activity之外的其他Activity实例! 它与singleTask有相同之处,也有不同之处。 相同之处:任意时刻,最多只允许存在一个实例。 不同之处: 1) singleTask受android:taskAffinity属性的影响,而singleInstance不受android:taskAffinity的影响。 2) singleTask所在的task中能有其它的Activity,而singleInstance的task中不能有其他Activity。 3) 当跳转到singleTask类型的Activity,并且该Activity实例已经存在时,会删除该Activity所在task中位于该Activity之上的全部Activity实例;而跳转到singleInstance类型的Activity,并且该Activity已经存在时,不需要删除其他Activity,因为它所在的task只有该Activity唯一一个Activity实例。 参考链接:[http://blog.csdn.net/sbsujjbcy/article/details/49360615](http://blog.csdn.net/sbsujjbcy/article/details/49360615) - 欢迎关注微信公众号,长期推荐技术文章和技术视频 - 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/Activity/深刻剖析activity启动模式-2.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) - [深刻剖析activity启动模式(一)](http://www.jianshu.com/p/b33fd8c550bf) - [深刻剖析activity启动模式(二)](http://www.jianshu.com/p/e1ea9e542112) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### **实例验证singleTask启动模式** 上篇文章将activity的四种启动模式就基本介绍完了。为了加深对启动模式的了解,下面会通过一个简单的例子进行验证。由以上的介绍可知,standard和singleTop这两种启动模式行为比较简单,所以在下面的例子中,通过taskAffinity会对singleTask和singleInstance着重介绍。 #### **验证启动singleTask模式的activity时是否会创建新的任务** 这个实例中有三个Activity,分别为:MainActivity,SecondActivity和ThirdActivity。 #### **配置形式:** ```xml ``` #### **说明:** MainActivity和ThirdActivity都是标准的启动模式,而SecondActivity的启动模式为singleTask。 #### **使用案例:** ```java public class MainActivity extends AppCompatActivity { private static final String ACTIVITY_NAME = "MainActivity"; private static final String LOG_TAG = "maweiqi"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, SecondActivity.class); startActivity(intent); } }); int taskId = getTaskId(); Log.i("maweiqi", "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode()); } } ``` ```java public class SecondActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(SecondActivity.this, ThirdActivity.class); startActivity(intent); } }); Log.i("maweiqi", "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode()); } } ``` ```java public class ThirdActivity extends AppCompatActivity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i("maweiqi", "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode()); } } ``` #### **测试方式:** MainActivity -->SecondActivity -->ThirdActivity。 #### **输出的日志如下:** ``` onCreate:MainActivity TaskId: 21 hasCode:199785693 onCreate:SecondActivity TaskId: 21 hasCode:171956091 ``` #### **在命令行中执行以下命令 adb shell dumpsys activity ,有以下输出:** ``` Running activities (most recent first): TaskRecord{838c0b8 #21 A=com.open.android.task1 U=0 sz=2} Run #1: ActivityRecord{abf1b0a u0 com.open.android.task1/.SecondActivity t21} Run #0: ActivityRecord{4e579b u0 com.open.android.task1/.MainActivity t21} ``` #### **测试结果:** 1)MainActivity和SecondActivity是启动在同一个任务中 2)在SecondActivity增加一个taskAffinity属性,如下所示: ```xml ``` #### **测试方式:** MainActivity -->SecondActivity -->ThirdActivity。 #### **输出的日志如下:** ``` onCreate:MainActivity TaskId: 24 hasCode:199785693 onCreate:SecondActivity TaskId: 25 hasCode:171956091 onCreate:ThirdActivity TaskId: 25 hasCode:76684615 ``` #### **在命令行中执行以下命令 adb shell dumpsys activity ,有以下输出:** ``` Running activities (most recent first): TaskRecord{844539b #25 A=com.maweiqi.second U=0 sz=2} Run #2: ActivityRecord{2eb5348 u0 com.open.android.task1/.ThirdActivity t25} Run #1: ActivityRecord{119f0df u0 com.open.android.task1/.SecondActivity t25} TaskRecord{b730338 #24 A=com.open.android.task1 U=0 sz=1} Run #0: ActivityRecord{2e05e2a u0 com.open.android.task1/.MainActivity t24} ``` #### **测试结果:** 1)MainActivity和SecondActivity运行在不同的任务中了 2)ThirdActivity和SecondActivity运行在同一个任务中 在这里便引出了manifest文件中的一个重要属性,taskAffinity。在官方文档中可以得到关于taskAffinity的以下信息 #### **taskAffinity** 1)taskAffinity表示当前activity具有亲和力的一个任务(翻译不是很准确,原句为The task that the activity has an affinity for.),大致可以这样理解,这个 taskAffinity表示一个任务,这个任务就是当前activity所在的任务。 2)在概念上,具有相同的affinity的activity(即设置了相同taskAffinity属性的activity)属于同一个任务。 3) 一个任务的affinity决定于这个任务的根activity(root activity)的taskAffinity。 4) 默认情况下,一个应用中的所有activity具有相同的taskAffinity,即应用程序的包名。我们可以通过设置不同的taskAffinity属性给应用中的activity分组,也可以把不同的 应用中的activity的taskAffinity设置成相同的值。 5)为一个activity的taskAffinity设置一个空字符串,表明这个activity不属于任何task。 #### **结果分析:** 1)这就可以解释上面示例中的现象了,由第4条可知,MainActivity和SecondActivity具有不同的taskAffinity,MainActivity的taskAffinity为com.open.android.task1,SecondActivity的taskAffinity为com.maweiqi.second。 2)当新启动的activity(SecondActivity)是以FLAG_ACTIVITY_NEW_TASK标志启动时(只有singleTask模式才会保证“single in task”,只使用FLAG_ACTIVITY_NEW_TASK并不保证“single in task”,或者说singleTask包含了FLAG_ACTIVITY_NEW_TASK,反之却不成立),framework会检索是否已经存在了一个affinity为com.maweiqi.second的任务(即一个TaskRecord对象) 3)如果存在这样的一个任务,则检查在这个任务中是否已经有了一个SecondActivity的实例。 3-1)如果已经存在一个SecondActivity的实例,则会重用这个任务和任务中的SecondActivity实例,将这个任务调到前台,清除位于SecondActivity上面的所有Activity,显示SecondActivity,并调用SecondActivity的onNewIntent(); 3-2)如果不存在一个SecondActivity的实例,会在这个任务中创建SecondActivity的实例,并调用onCreate()方法 3-3)这是在设置了singleTask模式的情况下会这样,在没有设置singleTask模式的情况下(即默认的standard模式)使用FLAG_ACTIVITY_NEW_TASK,并不会清除位于SecondActivity上面的所有Activity,而是会在task的上面重新创建一个SecondActivity。也就是说此时task中有两个SecondActivity。 4)如果不存在这样的一个任务,会创建一个新的affinity为com.maweiqi.second的任务,并且将SecondActivity启动到这个新的任务中 上面讨论的是设置taskAffinity属性的情况,如果SecondActivity只设置启动模式为singleTask,而不设置taskAffinity,即三个Activity的taskAffinity相同,都为应用的包名,那么SecondActivity是不会开启一个新任务的,framework中的判定过程如下: 1)在MainActivity启动SecondActivity时,发现启动模式为singleTask,那么设定他的启动标志为FLAG_ACTIVITY_NEW_TASK 2)然后获得SecondActivity的taskAffinity,即为包名com.open.android.task1检查是否已经存在一个affinity为com.open.android.task1的任务,这个任务是存在的,就是MainActivity所在的任务,这个任务是在启动MainActivity时开启的 3)既然已经存在这个任务,就检索在这个任务中是否存在一个SecondActivity的实例,发现不存在在这个已有的任务中启动一个SecondActivity的实例 为了作一个清楚的比较,列出SecondActivity启动模式设为singleTask,并且taskAffinity设为com.maweiqi.second时的启动过程 1)在MainActivity启动SecondActivity时,发现启动模式为singleTask,那么设定他的启动标志为FLAG_ACTIVITY_NEW_TASK 2)然后获得SecondActivity的taskAffinity,即com.maweiqi.second检查是否已经存在一个affinity为com.maweiqi.second的任务,这个任务是不存在的创建一个新的affinity为com.maweiqi.second的任务,并且将SecondActivity启动到这个新的任务中 framework中对任务和activity的调度是很复杂的,尤其是把启动模式设为singleTask或者以FLAG_ACTIVITY_NEW_TASK标志启动时.所以,在使用singleTask和FLAG_ACTIVITY_NEW_TASK时,要仔细测试应用程序. #### **实例验证将两个不同app中的不同的singleTask模式的Activity的taskAffinity设成相同** 官方文档中提到,可以把不同的 应用中的activity的taskAffinity设置成相同的值,这样的话这两个activity虽然不在同一应用中,却会在运行时分配到同一任务中,下面对此进行验证,在这里,会使用上面的示例,并创建一个新的示例AndroidTaskTest3。AndroidTaskTest3由两个activity组成,分别为MianActivity和OtherActivity,在MianActivity中点击按钮会启动OtherActivity,两个应用代码和布局一样,仅列出清单文件,两份清单文件如下: ```xml ``` ```xml ``` 可以看到OtherActivity和SecondActivity的启动模式都被设置为singleTask,并且taskAffinity属性被设置为com.maweiqi.second.现在将这两个应用安装在设备上。执行以下操作: #### **测试方式:** 1)MainActivity --> SecondActivity 2)MainActivity --> OtherActivity 启动AndroidTaskTest应用,在它的MianActivity中点击按钮开启SecondActivity,由上面的介绍可知secondActivity是运行在一个新任务中的,这个任务就是com.maweiqi.second。 然后按Home键回到Launcher,启动AndroidTaskTest3,在启动AndroidTaskTest3的入口MianActivity时,会自动启动新的任务,那么现在一共有三个任务,AndroidTaskTest的MianActivity和SecondActivity分别占用一个任务,AndroidTaskTest3的MianActivity也占用一个任务。 在AndroidTaskTest3的MianActivity中点击按钮启动OtherActivity,那么这个OtherActivity是在哪个任务中呢? #### **日志输出** ``` ***AndroidTaskTest应用测试日志输出*** onCreate:MainActivity TaskId: 29 hasCode:193251508 onCreate:SecondActivity TaskId: 30 hasCode:171956091 ***AndroidTaskTest3应用测试日志输出*** onCreate:MainActivity TaskId: 31 hasCode:199785693 onCreate:OtherActivity TaskId: 30 hasCode:171956091 ``` 下面执行adb shell dumpsys activity命令,发现有以下输出: ``` Task id #30 TaskRecord{7f2f34a #30 A=com.maweiqi.second U=0 sz=2} Intent { flg=0x10000000 cmp=com.open.android.task1/.SecondActivity } Hist #1: ActivityRecord{a0f9ded u0 com.open.android.task3/.OtherActivity t30} Intent { flg=0x10400000 cmp=com.open.android.task3/.OtherActivity } ProcessRecord{12090b5 27543:com.open.android.task3/u0a62} Hist #0: ActivityRecord{1048af6 u0 com.open.android.task1/.SecondActivity t30} Intent { flg=0x10000000 cmp=com.open.android.task1/.SecondActivity } ProcessRecord{5bc013e 26035:com.open.android.task1/u0a59} Task id #31 TaskRecord{dce52bb #31 A=com.open.android.task3 U=0 sz=1} Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task3/.MainActivity } Hist #0: ActivityRecord{f9e58c5 u0 com.open.android.task3/.MainActivity t31} Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task3/.MainActivity } ProcessRecord{12090b5 27543:com.open.android.task3/u0a62} Task id #29 TaskRecord{5b063d8 #29 A=com.open.android.task1 U=0 sz=1} Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task1/.MainActivity } Hist #0: ActivityRecord{689947d u0 com.open.android.task1/.MainActivity t29} Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.open.android.task1/.MainActivity } ProcessRecord{5bc013e 26035:com.open.android.task1/u0a59} Running activities (most recent first): TaskRecord{7f2f34a #30 A=com.maweiqi.second U=0 sz=2} Run #3: ActivityRecord{a0f9ded u0 com.open.android.task3/.OtherActivity t30} TaskRecord{dce52bb #31 A=com.open.android.task3 U=0 sz=1} Run #2: ActivityRecord{f9e58c5 u0 com.open.android.task3/.MainActivity t31} TaskRecord{7f2f34a #30 A=com.maweiqi.second U=0 sz=2} Run #1: ActivityRecord{1048af6 u0 com.open.android.task1/.SecondActivity t30} TaskRecord{5b063d8 #29 A=com.open.android.task1 U=0 sz=1} Run #0: ActivityRecord{689947d u0 com.open.android.task1/.MainActivity t29} mResumedActivity: ActivityRecord{a0f9ded u0 com.open.android.task3/.OtherActivity t30} ``` #### **结果分析** 1)所以由此可见,AndroidTaskTest1的SecondActivity和AndroidTaskTest3的OtherActivity是在同一任务中的 2)由上面adb shell dumpsys activity命令的输出结果还可以看出,AndroidTaskTest1和AndroidTaskTest3这两个应用程序会开启两个进程,他们的所有组件分别运行在独立的进程中 3)AndroidTaskTest1所在进程的进程号为u0a59,AndroidTaskTest3所在进程的进程号为u0a62 4)com.maweiqi.second任务中的两个activity属于不同的应用,并且运行在不同的进程中,这也说明了一个问题:任务(Task)不仅可以跨应用(Application),还可以跨进程(Process)。 #### **实例验证singleTask的另一意义:在同一个任务中具有唯一性** singleTask模式只意味着“可以在一个新的任务中开启”,至于是不是真的会在新任务中开启,在framework中还有其他条件的限制。由上面的介绍可知,这个条件为:是否已经存在了一个由他的taskAffinity属性指定的任务。这一点具有迷惑性,我们在看到singleTask这个单词的时候,会直观的想到它的本意:single in task。即,在同一个任务中,只会有一个该activity的实例。现在让我们进行验证: 为了验证这种情况,需要修改一下上面用到的AndroidTaskTest1示例。增加一个FourthActivity,并且MianActivity,SecondActivity,ThirdActivity和FourthActivity这四个activity都不设置taskAffinity属性,并且将SecondActivity启动模式设为singleTask,这样这四个activity会在同一个任务中开启。代码和软件界面就不列出了,只列出清单文件。 ```xml ``` #### **测试方式:** MianActivity --> SecondActivity --> ThirdActivity --> FourthActivity #### **日志输出** ``` onCreate:MainActivity TaskId: 34 hasCode:199785693 onCreate:SecondActivity TaskId: 34 hasCode:171956091 onCreate:ThirdActivity TaskId: 34 hasCode:240438941 onCreate:FourthActivity TaskId: 34 hasCode:168147209 ``` 由此可见这四个activity都是在同一个任务中的。再次执行adb shell dumpsys activity命令加以验证: ``` Running activities (most recent first): TaskRecord{d114530 #34 A=com.open.android.task1 U=0 sz=4} Run #3: ActivityRecord{edae6a3 u0 com.open.android.task1/.FourthActivity t34} Run #2: ActivityRecord{f9339a5 u0 com.open.android.task1/.ThirdActivity t34} Run #1: ActivityRecord{1a30096 u0 com.open.android.task1/.SecondActivity t34} Run #0: ActivityRecord{7e96fa4 u0 com.open.android.task1/.MainActivity t34} ``` #### **测试结果:** 1)同样可以说明目前这四个activity都运行在affinity为com.open.android.task1的任务中,即栈中的状态为MainActivity --> SecondActivity --> ThirdActivity --> FourthActivity。 下面执行在FourthActivity中点击按钮启动SecondActivity的操作,注意,SecondActivity的启动模式为singleTask,那么现在栈中的情况如何呢? #### **日志输出** ``` 没有日志输出,说明没有调用onCreate方法 ``` 再次执行adb shell dumpsys activity命令,有以下输出: ``` Running activities (most recent first): TaskRecord{d114530 #34 A=com.open.android.task1 U=0 sz=2} Run #1: ActivityRecord{1a30096 u0 com.open.android.task1/.SecondActivity t34} Run #0: ActivityRecord{7e96fa4 u0 com.open.android.task1/.MainActivity t34} ``` #### **测试结果:** 1)这时栈中的状态为MainActivity --> SecondActivity。确实确保了在任务中是唯一的,并且清除了同一任务中它上面的所有Activity。 2)那么这个SecondActivity的实例是重用的上次已有的实例还是重新启动了一个实例呢?可以观察系统Log, 发现系统Log没有改变,还是上面的四条Log。打印Log的语句是在各个Activity中的onCreate方法中执行的,没有打印出新的Log,说明SecondActivity的onCreate的方法没有重新执行,也就是说是重用的上次已经启动的实例,而不是销毁重建。 #### **结果分析:** 1)经过上面的验证,可以得出如下的结论:在启动一个singleTask的Activity实例时,如果系统中已经存在这样一个实例,就会将这个实例调度到任务栈的栈顶,并清除它当前所在任务中位于它上面的所有的activity。 ### **实例验证singleInstance的行为** 考谷歌官方文档,singleInstance的特点可以归结为以下三条: 1)以singleInstance模式启动的Activity具有全局唯一性,即整个系统中只会存在一个这样的实例 2)以singleInstance模式启动的Activity具有独占性,即它会独自占用一个任务,被他开启的任何activity都会运行在其他任务中(官方文档上的描述为,singleInstance模式的Activity不允许其他Activity和它共存在一个任务中) 3)被singleInstance模式的Activity开启的其他activity,能够开启一个新任务,但不一定开启新的任务,也可能在已有的一个任务中开启 下面对这三个特点分别验证,所使用的示例同样为AndroidTaskTest1,只不过会进行一些修改,下面列出它的清单文件: ```xml ``` 由上面的清单文件可以知道,该应用包括四个activity,分别为MianActivity,SecondActivity,ThirdActivity,FourthActivity,其中SecondActivity启动模式设置为singleInstance。MianActivity可以开启SecondActivity,SecondActivity可以开启ThirdActivity。 并且为了可以在其他应用中开启SecondActivity,为SecondActivity设置了一个IntentFilter,这样就可以在其他应用中使用隐式Intent开启SecondActivity。 #### **测试方式:** MianActivity --> SecondActivity --> ThirdActivity 为了更好的验证singleInstance的全局唯一性,还需要其他一个应用,新建AndroidTaskTest4。AndroidTaskTest4只需要一个MianActivity,在MainActivity中点击按钮会开启AndroidTaskTest1应用中的SecondActivity。开启AndroidTaskTest1应用中的SecondActivity的代码如下: ```java public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(); intent.setAction("com.maweiqi.ACTION_MY"); startActivity(intent); } }); } } ``` #### **下面开始验证第一个特点:以singleInstance模式启动的Activity具有全局唯一性,即整个系统中只会存在一个这样的实例** 执行如下操作:安装AndroidTaskTest1应用,点击MainActivity中的按钮,开启SecondActivity,可以看到如下log输出: #### **日志输出** ``` onCreate:MainActivity TaskId: 35 hasCode:199785693 onCreate:SecondActivity TaskId: 36 hasCode:171956091 ``` 执行adb shell dumpsys activity命令,有以下输出: ``` Running activities (most recent first): TaskRecord{2b68544 #36 A=com.open.android.task1 U=0 sz=1} Run #1: ActivityRecord{6d9f8c u0 com.open.android.task1/.SecondActivity t36} TaskRecord{ae9a62d #35 A=com.open.android.task1 U=0 sz=1} Run #0: ActivityRecord{d0429f5 u0 com.open.android.task1/.MainActivity t35} ``` 以上可以说明,singleInstance模式的Activity总是会在新的任务中运行(前提是系统中还不存在这样的一个实例) 。 下面验证它的全局唯一性,执行以下操作:安装另一个应用AndroidTaskTest4,在开启的MainActivity中点击按钮开启AndroidTaskTest1应用中的SecondActivity。看到打印出一条新的日志: ``` {act=com.maweiqi.ACTION_MY cmp=com.open.android.task1/.SecondActivity} from uid 10063 on display 0 ``` 执行adb shell dumpsys activity命令,有以下输出: ``` Running activities (most recent first): TaskRecord{2b68544 #36 A=com.open.android.task1 U=0 sz=1} Run #2: ActivityRecord{6d9f8c u0 com.open.android.task1/.SecondActivity t36} TaskRecord{a7e9abd #37 A=com.open.android.task4 U=0 sz=1} Run #1: ActivityRecord{f50eec0 u0 com.open.android.task4/.MainActivity t37} TaskRecord{ae9a62d #35 A=com.open.android.task1 U=0 sz=1} Run #0: ActivityRecord{d0429f5 u0 com.open.android.task1/.MainActivity t35} ``` #### **测试结果:** 1)开启的SecondActivity就是上次创建的编号为6d9f8c的SecondActivity。 2)Log中没有再次输出关于SecondActivity的信息,说明SecondActivity并没有重新创建 #### **结果分析:** 以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。 #### **下面开始验证第二个特点:以singleInstance模式启动的Activity具有独占性,即它会独自占用一个任务,被他开启的任何activity都会运行在其他任务中** 重新安装AndroidTaskTest1应用,点击MainActivity中的按钮,开启SecondActivity,在SecondActivity中点击按钮,开启ThirdActivity。可以看到有如下Log输出: #### **测试方式:** MainActivity --> SecondActivity --> ThirdActivity #### **日志输出:** ``` onCreate:MainActivity TaskId: 42 hasCode:199785693 onCreate:SecondActivity TaskId: 43 hasCode:171956091 onCreate:ThirdActivity TaskId: 42 hasCode:76684615 ``` 执行adb shell dumpsys activity命令,有以下输出: ``` Running activities (most recent first): TaskRecord{ace0710 #42 A=com.open.android.task1 U=0 sz=2} Run #3: ActivityRecord{322319f u0 com.open.android.task1/.ThirdActivity t42} TaskRecord{abc9c0e #43 A=com.open.android.task1 U=0 sz=1} Run #2: ActivityRecord{903a842 u0 com.open.android.task1/.SecondActivity t43} TaskRecord{ace0710 #42 A=com.open.android.task1 U=0 sz=2} Run #1: ActivityRecord{e3c0ddf u0 com.open.android.task1/.MainActivity t42} ``` #### **测试结果:** SecondActivity所在的任务为43,被SecondActivity启动的ThirdActivity所在的任务为42,这就说明以singleInstance模式启动的Activity具有独占性,即它会独自占用一个任务,被他开启的任何activity都会运行在其他任务中 #### **下面开始验证第三个特点:被singleInstance模式的Activity开启的其他activity,能够在新的任务中启动,但不一定开启新的任务,也可能在已有的一个任务中开启** 有上面对第二个特点的验证可以看到,被SecondActivity启动的ThirdActivity并没有运行在一个新开启的任务中,而是和MainActivity运行在了同一个已有的任务中,那么在什么情况下ThirdActivity才会启动一个新的任务呢? 现在对程序的清单文件做以下修改,为ThirdActivity增加一个属性taskAffinity: #### **配置如下:** ``` ``` 重新安装AndroidTaskTest1应用,执行和上一步中同样的操作:点击MainActivity中的按钮,开启SecondActivity,在SecondActivity中点击按钮,开启ThirdActivity。可以看到有如下输出: ``` onCreate:MainActivity TaskId: 44 hasCode:199785693 onCreate:SecondActivity TaskId: 45 hasCode:171956091 onCreate:ThirdActivity TaskId: 46 hasCode:76684615 ``` 执行adb shell dumpsys activity命令,有以下输出: ``` Running activities (most recent first): TaskRecord{3088b56 #46 A=com.maweiqi.second U=0 sz=1} Run #2: ActivityRecord{9eff1ed u0 com.open.android.task1/.ThirdActivity t46} TaskRecord{f9bb7d7 #45 A=com.open.android.task1 U=0 sz=1} Run #1: ActivityRecord{16c7b28 u0 com.open.android.task1/.SecondActivity t45} TaskRecord{e9af8c4 #44 A=com.open.android.task1 U=0 sz=1} Run #0: ActivityRecord{5c2ed47 u0 com.open.android.task1/.MainActivity t44} ``` #### **测试结果:** 1)被SecondActivity启动的ThirdActivity启动在了一个新的任务中,即在启动ThirdActivity时创建了一个新任务。这就说明被singleInstance模式的Activity A在开启另一activity B时,能够开启一个新任务,但是是不是真的开启新任务,还要受其他条件的限制,这个条件是:当前系统中是不是已经有了一个activity B的taskAffinity属性指定的任务。 其实这种行为和singleTask启动时的情况相同。在Activity的启动模式设置为singleTask时,启动时系统会为它加上FLAG_ACTIVITY_NEW_TASK标志,而被singleInstance模式的Activity开启的activity,启动时系统也会为它加上FLAG_ACTIVITY_NEW_TASK标志,所以他们启动时的情况是相同的,上面再验证singleTask时已经阐述过,现在重新说明一下: #### **结果分析:** 由于ThirdActivity是被启动模式为singleInstance类型的Activity(即SecondActivity)启动的,framework会为它它加上FLAG_ACTIVITY_NEW_TASK标志,这时 framework会检索是否已经存在了一个affinity为com.maweiqi.second(即ThirdActivity的taskAffinity属性)的任务 1)如果存在这样的一个任务,则检查在这个任务中是否已经有了一个ThirdActivity的实例. 1-1)如果已经存在一个ThirdActivity的实例,则会重用这个任务和任务中的ThirdActivity实例,将这个任务调到前台,清除位于ThirdActivity上面的所有Activity,显示ThirdActivity,并调用ThirdActivity的onNewIntent()。 1-2)如果不存在一个ThirdActivity的实例,会在这个任务中创建ThirdActivity的实例,并调用onCreate()方法 1-3)需要注意(1-1)有一种特殊情况,MainActivity, SecondActivity, ThirdActivity, FourthActivity 都不设置 taskAffinity.FourthActivity 设置为 singleInstance。测试 MainActivity -> SecondActivity -> ThirdActivity -> FourthActivity -> SecondActivity,从 FourthActivity 跳转到 SecondActivity, 是新开的一个 SecondActivity, 不会销毁 原 SecondActivity 上面的 ThirdActivity。 2)如果不存在这样的一个任务,会创建一个新的affinity为com.maweiqi.second的任务,并且将ThirdActivity启动到这个新的任务中. 如果ThirdActivity不设置taskAffinity,即ThirdActivity和MainActivity的taskAffinity相同,都为应用的包名,那么ThirdActivity是不会开启一个新任务的. #### **framework中的判定过程如下:** 1)在SecondActivity启动ThirdActivity时,因为SecondActivity是singleInstance的,所以设定ThirdActivity的启动标志为FLAG_ACTIVITY_NEW_TASK 2)然后获得ThirdActivity的taskAffinity,即为包名com.open.android.task1 3)检查是否已经存在一个affinity为com.open.android.task1的任务,这个任务是存在的,就是MainActivity所在的任务,这个任务是在启动MainActivity时开启的 4) 既然已经存在这个任务,就检索在这个任务中是否存在一个ThirdActivity的实例,发现不存在 5)在这个已有的任务中启动一个SecondActivity的实例 #### **为了作一个清楚的比较,列出ThirdActivity的taskAffinity属性设为com.maweiqi.second时的启动过程** 1)在SecondActivity启动ThirdActivity时,因为SecondActivity是singleInstance的,那么设定ThirdActivity的启动标志为FLAG_ACTIVITY_NEW_TASK 2)然后获得ThirdActivity的taskAffinity,即为com.maweiqi.second 3)检查是否已经存在一个affinity为com.maweiqi.second的任务,这个任务是不存在的 4) 创建一个新的affinity为com.maweiqi.second的任务,并且将ThirdActivity启动到这个新的任务 到此singleInstance也介绍完了。 ### 小结 由上述可知,Task是Android Framework中的一个概念,Task是由一系列相关的Activity组成的,是一组相关Activity的集合。Task是以栈的形式来管理的。 我们在操作软件的过程中,一定会涉及界面的跳转。其实在对界面进行跳转时,Android Framework既能在同一个任务中对Activity进行调度,也能以Task为单位进行整体调度。在启动模式为standard或singleTop时,一般是在同一个任务中对Activity进行调度,而在启动模式为singleTask或singleInstance是,一般会对Task进行整体调度。 #### **对Task进行整体调度包括以下操作:** 1)按Home键,将之前的任务切换到后台 2)长按Home键,会显示出最近执行过的任务列表 3)在Launcher或HomeScreen点击app图标,开启一个新任务,或者是将已有的任务调度到前台 4)启动singleTask模式的Activity时,会在系统中搜寻是否已经存在一个合适的任务,若存在,则会将这个任务调度到前台以重用这个任务。如果这个任务中已经存在一个要启动的Activity的实例,则清除这个实例之上的所有Activity,将这个实例显示给用户。如果这个已存在的任务中不存在一个要启动的Activity的实例,则在这个任务的顶端启动一个实例。若这个任务不存在,则会启动一个新的任务,在这个新的任务中启动这个singleTask模式的Activity的一个实例。 5)启动singleInstance的Activity时,会在系统中搜寻是否已经存在一个这个Activity的实例,如果存在,会将这个实例所在的任务调度到前台,重用这个Activity的实例(该任务中只有这一个Activity),如果不存在,会开启一个新任务,并在这个新任务中启动这个singleInstance模式的Activity的一个实例。 ================================================ FILE: docs/android/Android-Interview/Activity/深刻剖析activity启动模式-3.md ================================================ 前面介绍了通过launchMode设置Activity的启动模式。本章接着介绍Activity的启动模式相关内容,讲解的内容是Intent与启动模式相关的Flag,以及android:taskAffinity的属性。 ### **Intent与启动模式相关的Flag简介** 这里仅仅对几个常用的与启动模式相关的Flag进行介绍。 #### **FLAG_ACTIVITY_NEW_TASK** 很少单独使用FLAG_ACTIVITY_NEW_TASK,通常与FLAG_ACTIVITY_CLEAR_TASK或FLAG_ACTIVITY_CLEAR_TOP联合使用。因为单独使用该属性会导致奇怪的现象,通常达不到我们想要的效果!尽管如何,后面还是会通过"FLAG_ACTIVITY_NEW_TASK示例一"和"FLAG_ACTIVITY_NEW_TASK示例二"会向你展示单独使用它的效果。 #### **FLAG_ACTIVITY_SINGLE_TOP** 在google的官方文档中介绍,它与launchMode="singleTop"具有相同的行为。实际上,的确如此!单独的使用FLAG_ACTIVITY_SINGLE_TOP,就能达到和launchMode="singleTop"一样的效果。 #### **FLAG_ACTIVITY_CLEAR_TOP** 顾名思义,FLAG_ACTIVITY_CLEAR_TOP的作用清除"包含Activity的task"中位于该Activity实例之上的其他Activity实例。FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_NEW_TASK两者同时使用,就能达到和launchMode="singleTask"一样的效果! #### **FLAG_ACTIVITY_CLEAR_TASK** FLAG_ACTIVITY_CLEAR_TASK的作用包含Activity的task。使用FLAG_ACTIVITY_CLEAR_TASK时,通常会包含FLAG_ACTIVITY_NEW_TASK。这样做的目的是启动Activity时,清除之前已经存在的Activity实例所在的task;这自然也就清除了之前存在的Activity实例! 注意:当同时使用launchMode和上面的FLAG_ACTIVITY_NEW_TASK等标签时,以FLAG_ACTIVITY_NEW_TASK为标准。也就是说,代码的优先级比manifest中配置文件的优先级更高! 下面,通过几个实例加深对这几个标记的理解。 #### **FLAG_ACTIVITY_NEW_TASK标签** #### **配置形式:** ```xml ``` #### **说明:** 在该实例中,有两个MainActivity和SecondActivity。 #### **使用案例:** ```java public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.button). setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(MainActivity.this, SecondActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } }); Log.i("maweiqi", "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode()); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); Log.i("maweiqi", "onNewIntent:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode()); } } ``` 注意,跳转的Intent添加了FLAG_ACTIVITY_NEW_TASK标志。 ```java public class SecondActivity extends AppCompatActivity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); Button button = (Button) findViewById(R.id.button2); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(SecondActivity.this, MainActivity.class); startActivity(intent); } }); Log.i("maweiqi", "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode()); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); Log.i("maweiqi", "onNewIntent:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode()); } } ``` #### **测试方式:** MainActivity--> SecondActivity --> MainActivity--> SecondActivity #### **输出的日志如下:** ``` onCreate:MainActivity TaskId: 73 hasCode:195694529 onCreate:SecondActivity TaskId: 73 hasCode:54326677 onCreate:MainActivity TaskId: 73 hasCode:121740648 onCreate:SecondActivity TaskId: 73 hasCode:5761837 ``` #### **在命令行中执行以下命令 adb shell dumpsys activity ,有以下输出:** ``` Running activities (most recent first): TaskRecord{e3d17e2 #73 A=com.open.android.task5 U=0 sz=4} Run #3: ActivityRecord{a34fa00 u0 com.open.android.task5/.SecondActivity t73} Run #2: ActivityRecord{21172da u0 com.open.android.task5/.MainActivity t73} Run #1: ActivityRecord{b1e1e64 u0 com.open.android.task5/.SecondActivity t73} Run #0: ActivityRecord{f015a6e u0 com.open.android.task5/.MainActivity t73} mResumedActivity: ActivityRecord{a34fa00 u0 com.open.android.task5/.SecondActivity t73} ``` #### **测试结果:** 1)全部在同一个task中 2)全部创建不同的实例 那如果MainActivity跳转去掉FLAG_ACTIVITY_NEW_TASK?在SecondActivity清单文件添加singleTask,代码和流程跟之前一样,只贴出清单文件配置 #### **配置如下:** ```xml ``` #### **测试方式:** MainActivity--> SecondActivity --> MainActivity--> SecondActivity #### **输出的日志如下:** ``` onCreate:MainActivity TaskId: 74 hasCode:195694529 onCreate:SecondActivity TaskId: 74 hasCode:54326677 onCreate:MainActivity TaskId: 74 hasCode:38155814 onNewIntent:SecondActivity TaskId: 74 hasCode:54326677 ``` #### **在命令行中执行以下命令 adb shell dumpsys activity ,有以下输出:** ``` Running activities (most recent first): TaskRecord{46d0de2 #74 A=com.open.android.task5 U=0 sz=2} Run #1: ActivityRecord{b31602f u0 com.open.android.task5/.SecondActivity t74} Run #0: ActivityRecord{f88d9dc u0 com.open.android.task5/.MainActivity t74} mResumedActivity: ActivityRecord{b31602f u0 com.open.android.task5/.SecondActivity t74} ``` #### **测试结果:** 1)FLAG_ACTIVITY_NEW_TASK的作用和singleTask具有不同的效果 那如果两个Activity的android:taskAffinity不相同呢?此时会导致什么效果呢?下面,我们通过示例来看看效果。 #### **配置如下:** ```xml ``` 代码回到最初MainActivity还是添加Flag标记,其他省略,如下: ```java public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.button). setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(MainActivity.this, SecondActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } }); Log.i("maweiqi", "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode()); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); Log.i("maweiqi", "onNewIntent:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode()); } } ``` #### **测试方式:** MainActivity--> SecondActivity --> MainActivity--> SecondActivity #### **输出的日志如下:** ``` onCreate:MainActivity TaskId: 77 hasCode:195694529 onCreate:SecondActivity TaskId: 78 hasCode:54326677 onCreate:MainActivity TaskId: 78 hasCode:38155814 ``` #### **在命令行中执行以下命令 adb shell dumpsys activity ,有以下输出:** ``` Running activities (most recent first): TaskRecord{65dda1d #78 A=com.maweiqi.second U=0 sz=2} Run #2: ActivityRecord{845c9ed u0 com.open.android.task5/.MainActivity t78} Run #1: ActivityRecord{f813328 u0 com.open.android.task5/.SecondActivity t78} TaskRecord{dd8e492 #77 A=com.open.android.task5 U=0 sz=1} Run #0: ActivityRecord{7a85f99 u0 com.open.android.task5/.MainActivity t77} mResumedActivity: ActivityRecord{845c9ed u0 com.open.android.task5/.MainActivity t78} ``` #### **测试结果:** 1)当第二次进入到MainActivity中,再企图从MainActivity中进入到SecondActivity时,没有产生任何效果,仍然停留在MainActivity中!即第二次MainActivity--> SecondActivity压根就没发生! #### **结果分析:** 1)当相互跳转的两个Activity的android:taskAffinity不同时,添加FLAG_ACTIVITY_NEW_TASK:第一次启动SecondActivity时,会新建一个task,并将SecondActivity添加到该task中。这与singleTask产生的效果是一样的!但是,当企图再次从MainActivity进入到SecondActivity时,却什么也没有发生! 2)为什么呢?是因为此时SecondActivity实例已经存在,但是它所在的task的栈顶是MainActivity;而单独的添加FLAG_ACTIVITY_NEW_TASK又不会"删除task中位于SecondActivity之上的Activity实例",所以就没有发生跳转! #### **FLAG_ACTIVITY_CLEAR_TOP。** 修改MainActivity里面的点击事件如下: ```java Intent intent = new Intent(MainActivity.this, SecondActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); ``` ```java ``` #### **测试方式:** MainActivity--> SecondActivity --> MainActivity--> SecondActivity #### **日志输出** ``` onCreate:MainActivity TaskId: 80 hasCode:58656936 onCreate:SecondActivity TaskId: 80 hasCode:212434303 onCreate:MainActivity TaskId: 80 hasCode:121740648 onCreate:SecondActivity TaskId: 80 hasCode:15009890 ``` #### **测试结果:** 1) MainActivity和SecondActivity在同一个task中! 2) 两个SecondActivity是不同的实例。 #### **结果分析:** 这与没有添加FLAG_ACTIVITY_CLEAR_TOP时效果一样!这说明,当相互跳转的两个Activity的android:taskAffinity一样时,不会产生任何效果! 接下来,看看不同android:taskAffinity的情况,代码一致,清单文件修改 #### **文件配置** ```xml ``` #### **测试方式:** MainActivity--> SecondActivity --> MainActivity--> SecondActivity #### **日志输出** ``` onCreate:MainActivity TaskId: 83 hasCode:195694529 onCreate:SecondActivity TaskId: 84 hasCode:54326677 onCreate:MainActivity TaskId: 84 hasCode:190178689 onCreate:SecondActivity TaskId: 84 hasCode:5761837 ``` #### **在命令行中执行以下命令 adb shell dumpsys activity ,有以下输出:** ``` Running activities (most recent first): TaskRecord{12e942 #84 A=com.maweiqi.second U=0 sz=1} Run #1: ActivityRecord{e0674b5 u0 com.open.android.task5/.SecondActivity t84} TaskRecord{62b9d53 #83 A=com.open.android.task5 U=0 sz=1} Run #0: ActivityRecord{71ec08a u0 com.open.android.task5/.MainActivity t83} mResumedActivity: ActivityRecord{e0674b5 u0 com.open.android.task5/.SecondActivity t84} ``` #### **测试结果:** 1)MainActivity和SecondActivity在不同task中! 2) 两个SecondActivity是不同实例。 #### **结果分析:** FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_CLEAR_TOP的使用和android:taskAffinity相关。 在同时使用FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_CLEAR_TOP的情况下,以A启动B来说 1)当A和B的taskAffinity相同时:添加FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_CLEAR_TOP没有任何作用。和没有添加时的效果一样! 2)当A和B的taskAffinity不同时:添加FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_CLEAR_TOP后,如果该activity没有设置启动模式(即默认是standard),或者intent没有设置一个FLAG_ACTIVITY_SINGLE_TOP 的flag,那么这个activity就会销毁,然后重新创建这个实例 ### **总结:** #### **Activity的启动模式** 在AndroidManifest.xml中,可以配置每个activity的启动模式:例如: android:launchMode="standard" #### **(1) standard 标准模式** 此模式,不管有没有已存在的实例,都生成新的实例。每次调用startActivity()启动Activity时都会创建一个新的Activity放在栈顶,每次返回都会销毁实例并出栈,可以重复创建。 #### **(2) singletop 单一顶部模式** 如果任务栈的栈顶存在这个要开启的activity,不会重新创建新的activity,而是复用已存在的activity。保证栈顶如果存在,则不会重复创建,但如果不在栈顶,那么还是会创建新的实例。 应用场景:浏览器的书签 #### **(3) singletask 单一任务模式** 是一个比较严格的模式,在当前任务栈里面只能有一个实例存在,当开启activity的时候,就去检查在任务栈里面是否有实例已经存在,如果有实例存在就复用这个已经存在的activity,并且把这个activity上面的所有的别的activity都清空,复用这个已经存在的activity。 应用场景:BrowserActivity 浏览器界面 如果一个activity的创建需要占用大量的系统资源(cpu,内存)一般配置这个activity为singletask的启动模式。webkit内核(c) 初始化需要大量内存 js解析引擎 html渲染引擎 http解析,下载…减少内存开销,cpu占用。播放器的播放Activity #### **(4) singleInstance** 这种启动模式比较特殊,它会启用一个新的任务栈,activity会运行在自己的任务栈里,这个任务栈里面只有一个实例存在并且保证不再有其他Activity实例进入。在整个手机操作系统里面只有一个实例存在。 应用场景:来电页面。InCallScreenActivity ### **最后的总结:** 实话说,关于任务栈的bug有点多,如果真的添加了启动模式,一定要多多测试,有的地方和官方文档描述完全不相符。 stackoverflow上也有人吐糟。 [http://stackoverflow.com/questions/9772927/flag-activity-new-task-clarification-needed](http://stackoverflow.com/questions/9772927/flag-activity-new-task-clarification-needed) ================================================ FILE: docs/android/Android-Interview/Android/Android基础面试核心内容.md ================================================ ## Android基本常识 ### 1. 写10个简单的linux命令 | 命令 | 作用 | | :------- | :--------------------- | | mkdir | 创建文件夹 | | rmdir | 删除文件夹 | | mv | 移动文件 | | rm | 删除文件 | | cp | 拷贝文件 | | cat | 查看文件 | | tail | 查看文件尾部 | | more | 分页查看文件 | | cd | 切换当前目录 | | ls | 列出文件清单 | | reboot | 重启 | | date | 显示日期 | | cal | 显示日历 | | ps | 查看系统进程相当于windows的任务管理器 | | ifconfig | 配置网络 | ### 2. 书写出android工程的目录结构 ``` │ build.gradle 项目Gradle构建脚本 │ gradle.properties 项目Gradle属性文件 │ gradlew 在没有安装gradle的pc上使用,没用 │ gradlew.bat 在没有安装gradle的pc上使用,没用 │ local.properties 指定sdk所在目录 │ settings.gradle 项目Gradle设置文件 │ ├─.gradle ├─.idea ├─app │ │ .gitignore git忽略文件列表 │ │ app.iml 临时文件,不需要关心 │ │ build.gradle Module Gradle构建脚本 │ │ proguard-rules.pro proguard混淆规则 │ │ │ ├─build 构建目录,相当于Eclipse中默认Java工程的bin目录。编译生成的apk在此目录 │ ├─libs 依赖包 │ └─src │ ├─androidTest 测试相关代码文件夹 │ │ └─java │ │ └─com │ │ └─itheima │ │ └─myapplication │ │ ApplicationTest.java │ │ │ └─main │ │ AndroidManifest.xml 清单文件 │ │ │ ├─assets │ ├─aidl │ ├─java 项目源码 │ │ └─com │ │ └─itheima │ │ └─myapplication │ │ MainActivity.java │ │ │ ├─jni 放置c代码 │ ├─jniLibs 放置so库 │ ├─assets │ └─res 资源文件 │ ├─drawable .9图片只能放到drawable目录下 │ ├─layout │ │ activity_main.xml │ │ │ ├─menu │ │ menu_main.xml │ │ │ ├─mipmap-hdpi 类似drawable-hdpi │ │ ic_launcher.png │ │ │ ├─mipmap-mdpi 类似drawable-mdpi │ │ ic_launcher.png │ │ │ ├─mipmap-xhdpi 类似drawable-xdpi │ │ ic_launcher.png │ │ │ ├─mipmap-xxhdpi 类似drawable-xxdpi │ │ ic_launcher.png │ │ │ ├─values │ │ dimens.xml │ │ strings.xml │ │ styles.xml │ │ │ └─values-w820dp │ dimens.xml │ ├─build └─gradle └─wrapper gradle wrapper可以看作是对gradle的封装,它可以使得在没有安装gradle的电脑上也可以使用Gradle进行构建 gradle-wrapper.jar gradle-wrapper.properties ``` ### 3. 什么是ANR 如何避免它? 在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框。用户可以选择让程序继续运行,但是,他们在使用你的应用程序时,并不希望每次都要处理这个对话框。因此,在程序里对响应性能的设计很重要,这样,系统不会显示ANR给用户。不同的组件发生ANR的时间不一样,主线程(Activity、Service)是5秒,BroadCastReceiver是10秒。 解决方案: 将所有耗时操作,比如访问网络,Socket通信,查询大量SQL语句,复杂逻辑计算等都放在子线程中去,然后通过handler.sendMessage()、runonUITread()、AsyncTask等方式更新UI。无论如何都要确保用户界面操作的流畅度。如果耗时操作需要让用户等待,那么可以在界面上显示进度条。 ### 4. 谈谈Android的优点和不足之处 优点: - 开放性,开源,免费,可定制 - 挣脱运营商束缚 - 丰富的硬件选择 - 不受任何限制的开发商 - 无缝结合的Google应用 缺点: - 安全问题、隐私问题 - 同质化严重 - 运营商对Android手机仍然有影响 - 山寨化严重 - 过分依赖开发商,缺乏标准配置 ### 5.一条最长的短信息约占多少byte? 在国内的三大运营商通常情况下中文70(包括标点),英文160个。对于国外的其他运行商具体多长需要看运营商类型了。 android内部是通过如下代码进行判断具体一个短信多少byte的。 ArrayList<String> android.telephony.SmsManager.divideMessage(String text) ### 6. sim卡的EF文件有何作用? 基本文件EF(Elementary File)是SIM卡文件系统的一部分。 | 文件 | 文件标识符 | 文件缩写 | 中文名称 | 文件作用 | | --------------- | ----- | ------- | ---------------------------------------- | ---------------------------------------- | | MF | 3F00 | 根目录 | 备注:所有非ETSI GSM协议中规定的应用文件由各厂家自行定义在根目录下(如:PIN1,PIN2…) | | | EFICCID | 2FE2 | ICCID | SIM卡唯一的识别号 | 包含运营商、卡商、发卡时间、省市代码等信息 | | DFGSM | 7F20 | GSM目录 | 备注:根据ETSIGSM09.91的规定Phase2(或以上)的SIM卡中应该有7F21并指向7F20,用以兼容Phase1的手机 | | | EFLP语言选择 | 6F05 | LP | 语言选择文件 | 包含一种或多种语言编码 | | EFIMSI | 6F07 | IMSI | 国际移动用户识别符 | 包含SIM卡所对应的号段,比如46000代表135-139号段、46002代表1340-1348 | | EFKC语音加密密钥 | 6F20 | Kc | 计算密钥 | 用于SIM卡的加密、解密 | | EFPLMNsel网络选择表 | 6F30 | PLMNsel | 公共陆地网选择 | 决定SIM卡选择哪种网络,在这里应该选择中国移动的网络 | | EFHPLMN归属地网络选择表 | 6F31 | HPLMN | 两次搜索PLMN的时间间隔 | 两次搜索中国移动的网络的时间间隔 | | EFACMmax最大计费额 | 6F37 | ACMmax | 包含累积呼叫表的最大值 | 全部的ACM数据存在SIM卡中,此处取最大值 | | EFSST SIM卡服务表 | 6F38 | SST | SIM卡服务列表 | 指出SIM卡可以提供服务的种类,哪些业务被激活哪些业务没有开通 | | EFACM累加计费计数器 | 6F39 | ACM | 累计呼叫列表 | 当前的呼叫和以前的呼叫的单位总和 | | EFGID1分组识别1 | 6F3E | GID1 | 1级分组识别文件 | 包含特定的SIM-ME组合的标识符,可以识别一组特定的SIM卡 | | EFGID2分组识别2 | 6F3F | GID2 | 2级分组识别文件 | 包含特定的SIM-ME组合的标识符,可以识别一组特定的SIM卡 | | EFPUCT单位价格/货币表 | 6F41 | PUCT | 呼叫单位的价格和货币表 | PUCT是与计费通知有关的信息,ME用这个信息结合EFACM,以用户选择的货币来计算呼叫费用 | | EFCBMI小区广播识别号 | 6F45 | CBMI | 小区广播信息标识符 | 规定了用户希望MS采纳的小区广播消息内容的类型 | | EFSPN服务提供商 | 6F46 | SPN | 服务提供商名称 | 包含服务提供商的名称和ME显示的相应要求 | | EFCBMID | 6F48 | CBMID | 数据下载的小区广播消息识别符 | 移动台将收到的CBMID传送给SIM卡 | | EFSUME | 6F54 | SUME | 建立菜单单元 | 建立SIM卡中的菜单 | | EFBCCH广播信道 | 6F74 | BCCH | 广播控制信道 | 由于BCCH的存储,在选择小区时,MS可以缩小对BCCH载波的搜索范围 | | EFACC访问控制级别 | 6F78 | ACC | 访问控制级别 | SIM卡有15个级别,10个普通级别,5个高级级别 | | EFFPLMN禁止网络号 | 6F7B | FPLMN | 禁用的PLMN | 禁止选择除中国移动以外的其他运营商,比如中国联通、中国卫通等 | | EFLOCI位置信息 | 6F7E | LOCI | 位置信息 | 存储临时移动用户识别符、位置区信息等内容 | | EFAD管理数据 | 6FAD | AD | 管理数据 | 包括关于不同类型SIM卡操作模式的信息。例如:常规模式(PLMN用户用于GSM网络操作),型号认证模式(允许ME在无线设备的认证期间的特殊应用);小区测试模式(在小区商用之前,进行小区测试),制造商特定模式(允许ME制造商在维护阶段进行特定的性能自动测试) | | EFPHASE阶段 | 6FAE | PHASE | 阶段标识 | 标识SIM卡所处的阶段信息,比如是普通SIM卡还是STK卡等 | | DFTELECOM | 7F10 | 电信目录 | | | | EFADN缩位拨号 | 6F3A | AND | 电话簿 | 用于将电话记录存放在SIM卡中 | | EFFDN固定拨号 | 6F3B | FDN | 固定拨号 | 包括固定拨号(FDN)和/或补充业务控制字串(SSC),还包括相关网络/承载能力的识别符和扩展记录的识别符,以及有关的α识别符 | | EFSMS短消息 | 6F3C | SMS | 短消息 | 用于将短消息记录存放在SIM卡中 | | EFCCP能力配置参数 | 6F3D | CCP | 能力配置参数 | 包括所需要的网络和承载能力的参数,以及当采用一个缩位拨号号码,固定拨号号码,MSISDN、最后拨号号码、服务拨号号码或禁止拨号方式等,建立呼叫时相关的ME配置 | | EFMSISDN电话号码 | 6F40 | MSISDN | 移动基站国际综合业务网号 | 存放用户的手机号 | | EFSMSP短信息参数 | 6F42 | SMSP | 短消息业务参数 | 包括短信中心号码等信息 | | EFSMSS短信息状态 | 6F43 | SMSS | 短消息状态 | 这个标识是用来控制流量的 | | EFLND最后拨号 | 6F44 | LND | 最后拨叫号码 | 存储最后拨叫号码 | | EFExt1扩展文件1 | 6F4A | EXT1 | 扩展文件1 | 包括AND,MSISDN或LND的扩展数据 | | EFExt2扩展文件2 | 6F4B | EXT2 | 扩展文件2 | 包含FDN的扩展数据 | ### 7. 如何判断是否有SD卡? 通过如下方法:`Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)`,如果返回true就是有sdcard,如果返回false则没有。 ### 8. dvm的进程和Linux的进程, 应用程序的进程是否为同一个概念? dvm指dalvik的虚拟机。每一个Android应用程序都拥有一个独立的Dalvik虚拟机实例,应用程序都在它自己的进程中运行。而每一个dvm都是在Linux 中的一个进程,所以说可以近似认为是同一个概念。 什么是android DVM:Dalvik是Google公司自己设计用于Android平台的Java虚拟机,每一个Dalvik 应用作为一个独立的Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。 Dalvik和Java虚拟机的区别 1.Dalvik主要是完成对象生命周期管理,堆栈管理,线程管理,安全和异常管理,以及垃圾回收等等重要功能。    2.Dalvik负责进程隔离和线程管理,每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例,其代码在虚拟机的解释下得以执行。    3.不同于Java虚拟机运行java字节码,Dalvik虚拟机运行的是其专有的文件格式Dex 4.dex文件格式可以减少整体文件尺寸,提高I/O操作的类查找速度。    5.odex是为了在运行过程中进一步提高性能,对dex文件的进一步优化。    6.所有的Android应用的线程都对应一个Linux线程,虚拟机因而可以更多的依赖操作系统的线程调度和管理机制 7.有一个特殊的虚拟机进程Zygote,他是虚拟机实例的孵化器。它在系统启动的时候就会产生,它会完成虚拟机的初始化,库的加载,预制类库和初始化的操作。如果系统需要一个新的虚拟机实例,它会迅速复制自身,以最快的数据提供给系统。对于一些只读的系统库,所有虚拟机实例都和Zygote共享一块内存区域。 ### 9. Android程序与Java程序的区别? Android程序用android sdk开发,java程序用javasdk开发。 Android SDK引用了大部分的Java SDK,少数部分被AndroidSDK抛弃,比如说界面部分,java.awt swing package除了java.awt.font被引用外,其他都被抛弃,在Android平台开发中不能使用。android sdk 添加工具jar httpclient , pull opengl ### 10.启动应用后,改变系统语言,应用的语言会改变么? 这个一般是不会的,一般需要重启应用才能改变应用语言。但是对应应用来说如果做了国际化处理则支持如果没有处理那系统语言再更改也是无用的。 ### 11.请介绍下adb、ddms、aapt的作用 adb是Android Debug Bridge ,Android调试桥的意思,ddms是Dalvik Debug Monitor Service,dalvik调试监视服务。aapt即AndroidAsset Packaging Tool,在SDK的build-tools目录下。该工具可以查看,创建,更新ZIP格式的文档附件(zip, jar, apk)。也可将资源文件编译成二进制文件,尽管我们没有直接使用过该工具,但是开发工具会使用这个工具打包apk文件构成一个Android 应用程序。 Android 的主要调试工具是adb(Android debuging bridge),ddms是一个在adb基础上的一个图形化工具。adb,它是一个命令行工具。而ddms功能与adb相同,只是它有一个图形化界面。对不喜欢命今操作方式的人来说是一个不错的选择。 ### 12.ddms 和traceview的区别 简单的说ddms是一个程序执行查看器,在里面可以看见线程和堆栈等信息,traceView是程序性能分析器。 ### 13.补充知识:TraceView的使用 #### 13.1 TraceView简介 Traceview是Android平台特有的数据采集和分析工具,它主要用于分析Android中应用程序的hotspot(瓶颈)。Traceview本身只是一个数据分析工具,而数据的采集则需要使用Android SDK中的Debug类或者利用DDMS工具。二者的用法如下: 开发者在一些关键代码段开始前调用Android SDK中Debug类的startMethodTracing函数,并在关键代码段结束前调用stopMethodTracing函数。这两个函数运行过程中将采集运行时间内该应用所有线程(注意,只能是Java线程)的函数执行情况,并将采集数据保存到/mnt/sdcard/下的一个文件中。开发者然后需要利用SDK中的Traceview工具来分析这些数据。 借助Android SDK中的DDMS工具。DDMS可采集系统中某个正在运行的进程的函数调用信息。对开发者而言,此方法适用于没有目标应用源代码的情况。DDMS工具中Traceview的使用如下图所示。 ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/204016lpeujohi86i238dx.png.thumb.jpg) 点击上图中所示按钮即可以采集目标进程的数据。当停止采集时,DDMS会自动触发Traceview工具来浏览采集数据。 下面,我们通过一个示例程序向读者介绍Debug类以及Traceview的使用。 实例程序如下图所示:界面有4个按钮,对应四个方法。 ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/204116wfaeyfwa99yx2wbp.png.thumb.jpg) 点击不同的方法会进行不同的耗时操作。 ```java public class MainActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void method1(View view) { int result = jisuan(); System.out.println(result); } private int jisuan() { for (int i = 0; i < 10000; i++) { System.out.println(i); } return 1; } public void method2(View view) { SystemClock.sleep(2000); } public void method3(View view) { int sum = 0; for (int i = 0; i < 1000; i++) { sum += i; } System.out.println("sum=" + sum); } public void method4(View view) { Toast.makeText(this, "" + new Date(), 0).show(); } } ``` 我们分别点击按钮一次,要求找出最耗时的方法。点击前通过DDMS 启动 Start Method Profiling按钮。 ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/204308gttti12apmt9thi3.png.thumb.jpg) 然后依次点击4个按钮,都执行后再次点击上图中红框中按钮,停止收集数据。 接下来我们开始对数据进行分析。 当我们停止收集数据的时候会出现如下分析图表。该图表分为2大部分,上面分不同的行,每一行代表一个线程的执行耗时情况。main线程对应行的的内容非常丰富,而其他线程在这段时间内干得工作则要少得多。图表的下半部分是具体的每个方法执行的时间情况。显示方法执行情况的前提是先选中某个线程。 ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/204355dzirs7fk3ssacgtm.png.thumb.jpg) 我们主要是分析main线程。 上面方法指标参数所代表的意思如下: | 列名 | 描述 | | :--------------------- | :---------------------------------- | | Name | 该线程运行过程中所调用的函数名 | | Incl Cpu Time | 某函数占用的CPU时间,包含内部调用其它函数的CPU时间 | | Excl Cpu Time | 某函数占用的CPU时间,但不含内部调用其它函数所占用的CPU时间 | | Incl Real Time | 某函数运行的真实时间(以毫秒为单位),内含调用其它函数所占用的真实时间 | | Excl Real Time | 某函数运行的真实时间(以毫秒为单位),不含调用其它函数所占用的真实时间 | | Call+Recur Calls/Total | 某函数被调用次数以及递归调用占总调用次数的百分比 | | Cpu Time/Call | 某函数调用CPU时间与调用次数的比。相当于该函数平均执行时间 | | Real Time/Call | 同CPU Time/Call类似,只不过统计单位换成了真实时间 | 我们为了找到最耗时的操作,那么可以通过点击Incl Cpu Time,让其按照时间的倒序排列。我点击后效果如下图: ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/204642ieokexkpse7sxess.png.thumb.jpg) 通过分析发现:method1最耗时,耗时2338毫秒。 ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/204643ojljvruy9j8dy4jr.png.thumb.jpg) 那么有了上面的信息我们可以进入我们的method1方法查看分析我们的代码了。 ## Activity ### 1.什么是Activity? 四大组件之一,通常一个用户交互界面对应一个activity。activity 是Context的子类,同时实现了window.callback和keyevent.callback, 可以处理与窗体用户交互的事件。 常见的Activity类型有FragmentActivitiy,ListActivity,TabAcitivty等。 如果界面有共同的特点或者功能的时候,还会自己定义一个BaseActivity。 ### 2.请描述一下Activity 生命周期 Activity从创建到销毁有多种状态,从一种状态到另一种状态时会激发相应的回调方法,这些回调方法包括:onCreate、onStart、onResume、onPause、onStop、onDestroy 其实这些方法都是两两对应的, onCreate创建与onDestroy销毁; onStart可见与onStop不可见; onResume可编辑(即焦点)与onPause; 这6个方法是相对应的,那么就只剩下一个onRestart方法了,这个方法在什么时候调用呢? 答案就是:在Activity被onStop后,但是没有被onDestroy,在再次启动此Activity时就调用onRestart(而不再调用onCreate)方法;如果被onDestroy了,则是调用onCreate方法。 ### 3.如何保存Activity的状态? Activity的状态通常情况下系统会自动保存的,只有当我们需要保存额外的数据时才需要使用到这样的功能。 一般来说, 调用onPause()和onStop()方法后的activity实例仍然存在于内存中, activity的所有信息和状态数据不会消失, 当activity重新回到前台之后, 所有的改变都会得到保留。 但是当系统内存不足时, 调用onPause()和onStop()方法后的activity可能会被系统摧毁, 此时内存中就不会存有该activity的实例对象了。如果之后这个activity重新回到前台, 之前所作的改变就会消失。为了避免此种情况的发生, 我们可以覆写onSaveInstanceState()方法。onSaveInstanceState()方法接受一个Bundle类型的参数, 开发者可以将状态数据存储到这个Bundle对象中, 这样即使activity被系统摧毁,当用户重新启动这个activity而调用它的onCreate()方法时, 上述的Bundle对象会作为实参传递给onCreate()方法, 开发者可以从Bundle对象中取出保存的数据, 然后利用这些数据将activity恢复到被摧毁之前的状态。 需要注意的是, onSaveInstanceState()方法并不是一定会被调用的, 因为有些场景是不需要保存状态数据的. 比如用户按下BACK键退出activity时, 用户显然想要关闭这个activity, 此时是没有必要保存数据以供下次恢复的, 也就是onSaveInstanceState()方法不会被调用. 如果调用onSaveInstanceState()方法, 调用将发生在onPause()或onStop()方法之前。 ```java @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); } ``` ### 4.两个Activity之间跳转时必然会执行的是哪几个方法? 一般情况下比如说有两个activity,分别叫A,B,当在A里面激活B组件的时候, A会调用 onPause()方法,然后B调用onCreate() ,onStart(), onResume()。这个时候B覆盖了窗体, A会调用onStop()方法. 如果B是个透明的,或者是对话框的样式, 就不会调用A的onStop()方法。 ### 5.横竖屏切换时Activity的生命周期 此时的生命周期跟清单文件里的配置有关系。 1.不设置Activity的`android:configChanges`时,切屏会重新调用各个生命周期,默认首先销毁当前activity,然后重新加载。 2.设置Activity的`android:configChanges="orientation|keyboardHidden|screenSize"`时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法。 通常在游戏开发, 屏幕的朝向都是写死的。 ### 6.如何将一个Activity设置成窗口的样式 只需要给我们的Activity配置如下属性即可。 ```xml android:theme="@android:style/Theme.Dialog" ``` ### 7.如何退出Activity?如何安全退出已调用多个Activity的Application? 1.通常情况用户退出一个Activity只需按返回键,我们写代码想退出activity直接调用finish()方法就行。 2.记录打开的Activity:每打开一个Activity,就记录下来。在需要退出时,关闭每一个Activity即可。 ```java //伪代码 List lists;// 在application 全局的变量里面 lists = new ArrayList(); lists.add(this); for (Activity activity : lists) { activity.finish(); } lists.remove(this); ``` 3.发送特定广播: 在需要结束应用时,发送一个特定的广播,每个Activity收到广播后,关闭即可。 ```java //给某个activity 注册接受接受广播的意图 registerReceiver(receiver, filter) //如果过接受到的是 关闭activity的广播 就调用finish()方法把当前的activity finish()掉 ``` 4.递归退出 在打开新的Activity时使用startActivityForResult,然后自己加标志,在onActivityResult中处理,递归关闭。 5.其实 也可以通过 intent的flag 来实现intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)激活一个新的activity。此时如果该任务栈中已经有该Activity,那么系统会把这个Activity上面的所有Activity干掉。其实相当于给Activity配置的启动模式为SingleTop。 ### 8.请描述一下Activity的启动模式都有哪些以及各自的特点 启动模式(launchMode)在多个Activity跳转的过程中扮演着重要的角色,它可以决定是否生成新的Activity实例,是否重用已存在的Activity实例,是否和其他Activity实例公用一个task里。这里简单介绍一下task的概念,task是一个具有栈结构的对象,一个task可以管理多个Activity,启动一个应用,也就创建一个与之对应的task。 Activity一共有以下四种launchMode: - standard 默认启动模式 - singleTop 栈顶复用模式 - singleTask 栈内复用模式 - singleInstance 单例模式 我们可以在AndroidManifest.xml配置的android:launchMode属性为以上四种之一即可。 下面我们结合实例一一介绍这四种lanchMode: #### 8.1 standard standard模式是默认的启动模式,不用为<activity>配置android:launchMode属性即可,当然也可以指定值为standard。 我们将创建一个Activity,命名为FirstActivity,来演示一下标准的启动模式。FirstActivity代码如下: ```java public class FirstActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.first); TextView textView = (TextView) findViewById(R.id.tv); textView.setText(this.toString()); Button button = (Button) findViewById(R.id.bt); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(FirstActivity.this, FirstActivity.class); startActivity(intent); } }); } } ``` FirstActivity界面中的TextView用于显示当前Activity实例的序列号,Button用于跳转到下一个FirstActivity界面。 然后我们连续点击几次按钮,将会出现下面的现象: ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/205556t5fqfqf9p75um5m5.png.thumb.jpg) 我们注意到都是FirstActivity的实例,但序列号不同,并且我们需要连续按后退键两次,才能回到第一个FirstActivity。standard模式的原理如下图所示: ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/205557cgllglpuuluzgcll.png.thumb.jpg) 如图所示,每次跳转系统都会在task中生成一个新的FirstActivity实例,并且放于栈结构的顶部,当我们按下后退键时,才能看到原来的FirstActivity实例。 这就是standard启动模式,不管有没有已存在的实例,都生成新的实例。 #### 8.2 singleTop 我们在上面的基础上为<activity>指定属性android:launchMode="singleTop",系统就会按照singleTop启动模式处理跳转行为。我们重复上面几个动作,将会出现下面的现象: ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/205728ut1x8w488q84869m.png.thumb.jpg) ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/205728nrtxhzrqdz9xc4bo.png.thumb.jpg) 我们看到这个结果跟standard有所不同,三个序列号是相同的,也就是说使用的都是同一个FirstActivity实例;如果按一下后退键,程序立即退出,说明当前栈结构中只有一个Activity实例。singleTop模式的原理如下图所示: ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/205728uxaz9gcfzwx9n9t5.png.thumb.jpg) 正如上图所示,跳转时系统会先在栈结构中寻找是否有一个FirstActivity实例正位于栈顶,如果有则不再生成新的,而是直接使用。也许朋友们会有疑问,我只看到栈内只有一个Activity,如果是多个Activity怎么办,如果不是在栈顶会如何?我们接下来再通过一个示例来证实一下大家的疑问。 我们再新建一个Activity命名为SecondActivity,如下: ```java public class SecondActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.second); TextView textView = (TextView) findViewById(R.id.tv); textView.setText(this.toString()); Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(SecondActivity.this, FirstActivity.class); startActivity(intent); } }); } } ``` 然后将之前的FirstActivity跳转代码改为: ```java Intent intent = new Intent(FirstActivity.this, SecondActivity.class); startActivity(intent); ``` 这时候,FirstActivity会跳转到SecondActivity,SecondActivity又会跳转到FirstActivity。演示结果如下: ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/210036ua7abect52cq2jp4.png.thumb.jpg) 我们看到,两个FirstActivity的序列号是不同的,证明从SecondActivity跳转到FirstActivity时生成了新的FirstActivity实例。原理图如下: ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/210037gu8wouo3sp8twp2n.png.thumb.jpg) 我们看到,当从SecondActivity跳转到FirstActivity时,系统发现存在有FirstActivity实例,但不是位于栈顶,于是重新生成一个实例。 这就是singleTop启动模式,如果发现有对应的Activity实例正位于栈顶,则重复利用,不再生成新的实例。 #### 8.3 singleTask 在上面的基础上我们修改FirstActivity的属性android:launchMode="singleTask"。演示的结果如下: ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/210241qtiotcrjsb7k88rr.png.thumb.jpg) 我们注意到,在上面的过程中,FirstActivity的序列号是不变的,SecondActivity的序列号却不是唯一的,说明从SecondActivity跳转到FirstActivity时,没有生成新的实例,但是从FirstActivity跳转到SecondActivity时生成了新的实例。singleTask模式的原理图如下图所示: ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/210317f2od966cete6woi4.png.thumb.jpg) 在图中的下半部分是SecondActivity跳转到FirstActivity后的栈结构变化的结果,我们注意到,SecondActivity消失了,没错,在这个跳转过程中系统发现有存在的FirstActivity实例,于是不再生成新的实例,而是将FirstActivity之上的Activity实例统统出栈,将FirstActivity变为栈顶对象,显示到幕前。也许朋友们有疑问,如果将SecondActivity也设置为singleTask模式,那么SecondActivity实例是不是可以唯一呢?在我们这个示例中是不可能的,因为每次从SecondActivity跳转到FirstActivity时,SecondActivity实例都被迫出栈,下次等FirstActivity跳转到SecondActivity时,找不到存在的SecondActivity实例,于是必须生成新的实例。但是如果我们有ThirdActivity,让SecondActivity和ThirdActivity互相跳转,那么SecondActivity实例就可以保证唯一。 这就是singleTask模式,如果发现有对应的Activity实例,则使此Activity实例之上的其他Activity实例统统出栈,使此Activity实例成为栈顶对象,显示到幕前。 #### 8.4 singleInstance 这种启动模式比较特殊,因为它会启用一个新的栈结构,将Activity放置于这个新的栈结构中,并保证不再有其他Activity实例进入。 我们修改FirstActivity的launchMode="standard",SecondActivity的launchMode="singleInstance",由于涉及到了多个栈结构,我们需要在每个Activity中显示当前栈结构的id,所以,我们为每个Activity添加如下代码: ```java TextView taskIdView = (TextView) findViewById(R.id.taskIdView); taskIdView.setText("current task id: " + this.getTaskId()); ``` 然后我们再演示一下这个流程: ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/210717mdrl2lljpgdlgpg0.png.thumb.jpg) 我们发现这两个Activity实例分别被放置在不同的栈结构中,关于singleInstance的原理图如下: ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/210718id29znpnjdi6uznc.png.thumb.jpg) 我们看到从FirstActivity跳转到SecondActivity时,重新启用了一个新的栈结构,来放置SecondActivity实例,然后按下后退键,再次回到原始栈结构;图中下半部分显示的在SecondActivity中再次跳转到FirstActivity,这个时候系统会在原始栈结构中生成一个FirstActivity实例,然后回退两次,注意,并没有退出,而是回到了SecondActivity,为什么呢?是因为从SecondActivity跳转到FirstActivity的时候,我们的起点变成了SecondActivity实例所在的栈结构,这样一来,我们需要“回归”到这个栈结构。 如果我们修改FirstActivity的launchMode值为singleTop、singleTask、singleInstance中的任意一个,流程将会如图所示: ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/210814m8bbhviw22bvvip2.png.thumb.jpg) singleInstance启动模式可能是最复杂的一种模式,为了帮助大家理解,我举一个例子,假如我们有一个share应用,其中的ShareActivity是入口Activity,也是可供其他应用调用的Activity,我们把这个Activity的启动模式设置为singleInstance,然后在其他应用中调用。我们编辑ShareActivity的配置: ```xml ``` 然后我们在其他应用中这样启动该Activity: ```java Intent intent = new Intent("android.intent.action.SINGLE_INSTANCE_SHARE"); startActivity(intent); ``` 当我们打开ShareActivity后再按后退键回到原来界面时,ShareActivity做为一个独立的个体存在,如果这时我们打开share应用,无需创建新的ShareActivity实例即可看到结果,因为系统会自动查找,存在则直接利用。大家可以在ShareActivity中打印一下taskId,看看效果。关于这个过程,原理图如下: ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/211004zlzlj3cf39c53llr.png.thumb.jpg) ## Service ### 1.Service是否在main thread中执行, service里面是否能执行耗时的操作? 默认情况,如果没有显示的指servic所运行的进程, Service和activity是运行在当前app所在进程的main thread(UI主线程)里面。 service里面不能执行耗时的操作(网络请求,拷贝数据库,大文件 )。 特殊情况 ,可以在清单文件配置 service 执行所在的进程,让service在另外的进程中执行。 ```xml ``` ### 2.Activity怎么和Service绑定,怎么在Activity中启动自己对应的Service? Activity通过bindService(Intent service, ServiceConnection conn,int flags)跟Service进行绑定,当绑定成功的时候Service会将代理对象通过回调的形式传给conn,这样我们就拿到了Service提供的服务代理对象。 在Activity中可以通过startService和bindService方法启动Service。一般情况下如果想获取Service的服务对象那么肯定需要通过bindService()方法,比如音乐播放器,第三方支付等。如果仅仅只是为了开启一个后台任务那么可以使用startService()方法。 ### 3.请描述一下Service的生命周期 service有绑定模式和非绑定模式,以及这两种模式的混合使用方式。不同的使用方法生命周期方法也不同。 非绑定模式:当第一次调用startService的时候执行的方法依次为onCreate()、onStartCommand(),当Service关闭的时候调用onDestory方法。 绑定模式:第一次bindService()的时候,执行的方法为onCreate()、onBind()解除绑定的时候会执行onUnbind()、onDestory()。 上面的两种生命周期是在相对单纯的模式下的情形。我们在开发的过程中还必须注意Service实例只会有一个,也就是说如果当前要启动的Service已经存在了那么就不会再次创建该Service当然也不会调用onCreate()方法。 一个Service可以被多个客户进行绑定,只有所有的绑定对象都执行了onBind()方法后该Service才会销毁,不过如果有一个客户执行了onStart()方法,那么这个时候如果所有的bind客户都执行了unBind()该Service也不会销毁。 Service的生命周期图如下所示,帮助大家记忆。 ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/211309fvvjo1kwkpwq7wa1.png.thumb.jpg) ### 4.什么是IntentService?有何优点? 我们通常只会使用Service,可能IntentService对大部分同学来说都是第一次听说。那么看了下面的介绍相信你就不再陌生了。如果你还是不了解那么在面试的时候你就坦诚说没用过或者不了解等。并不是所有的问题都需要回答上来的。 #### IntentService简介 IntentService是Service的子类,比普通的Service增加了额外的功能。先看Service本身存在两个问题: service不会专门启动一条单独的进程,Service与它所在应用位于同一个进程中; service也不是专门一条新线程,因此不应该在Service中直接处理耗时的任务; #### IntentService特征 会创建独立的worker线程来处理所有的Intent请求; 会创建独立的worker线程来处理onHandleIntent()方法实现的代码,无需处理多线程问题; 所有请求处理完成后,IntentService会自动停止,无需调用stopSelf()方法停止Service; 为Service的onBind()提供默认实现,返回null; 为Service的onStartCommand提供默认实现,将请求Intent添加到队列中; #### 使用IntentService 本人写了一个IntentService的使用例子供参考。该例子中一个MainActivity一个MyIntentService,这两个类都是四大组件当然需要在清单文件中注册。这里只给出核心代码: MainActivity.java ```java public void click(View view){ Intent intent = new Intent(this, MyIntentService.class); intent.putExtra("start", "MyIntentService"); startService(intent); } MyIntentService.javapublic class MyIntentService extends IntentService { private String ex = ""; private Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { Toast.makeText(MyIntentService.this, "-e " + ex, Toast.LENGTH_LONG).show(); } }; public MyIntentService(){ super("MyIntentService"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { ex = intent.getStringExtra("start"); return super.onStartCommand(intent, flags, startId); } @Override protected void onHandleIntent(Intent intent) { / * 模拟执行耗时任务 * 该方法是在子线程中执行的,因此需要用到handler跟主线程进行通信 */ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } mHandler.sendEmptyMessage(0); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } ``` 运行后效果如下: ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/211637jzeept0p0mdst2t0.png.thumb.jpg) ### 5.说说Activity、Intent、Service是什么关系 他们都是Android开发中使用频率最高的类。其中Activity和Service都是Android四大组件之一。他俩都是Context类的子类ContextWrapper的子类,因此他俩可以算是兄弟关系吧。不过兄弟俩各有各自的本领,Activity负责用户界面的显示和交互,Service负责后台任务的处理。Activity和Service之间可以通过Intent传递数据,因此可以把Intent看作是通信使者。 ### 6.Service和Activity在同一个线程吗 对于同一app来说默认情况下是在同一个线程中的,mainThread (UI Thread)。 ### 7.Service里面可以弹吐司么 可以的。弹吐司有个条件就是得有一个Context上下文,而Service本身就是Context的子类,因此在Service里面弹吐司是完全可以的。比如我们在Service中完成下载任务后可以弹一个吐司通知用户。 ## BroadCastReceiver ### 1.请描述一下BroadcastReceiver BroadCastReceiver是Android四大组件之一,主要用于接收系统或者app发送的广播事件。广播分两种:有序广播和无序广播。 内部通信实现机制:通过Android系统的Binder机制实现通信。 无序广播:完全异步,逻辑上可以被任何广播接收者接收到。优点是效率较高。缺点是一个接收者不能将处理结果传递给下一个接收者,并无法终止广播intent的传播。 有序广播:按照被接收者的优先级顺序,在被接收者中依次传播。比如有三个广播接收者A,B,C,优先级是A > B> C。那这个消息先传给A,再传给B,最后传给C。每个接收者有权终止广播,比如B终止广播,C就无法接收到。此外A接收到广播后可以对结果对象进行操作,当广播传给B时,B可以从结果对象中取得A存入的数据。在通过Context.sendOrderedBroadcast(intent, receiverPermission,resultReceiver, scheduler, initialCode, initialData, initialExtras)时我们可以指定resultReceiver广播接收者,这个接收者我们可以认为是最终接收者,通常情况下如果比他优先级更高的接收者如果没有终止广播,那么他的onReceive会被执行两次,第一次是正常的按照优先级顺序执行,第二次是作为最终接收者接收。如果比他优先级高的接收者终止了广播,那么他依然能接收到广播。 在我们的项目中经常使用广播接收者接收系统通知,比如开机启动、sd挂载、低电量、外播电话、锁屏等。 如果我们做的是播放器,那么监听到用户锁屏后我们应该将我们的播放之暂停等。 ### 2.在manifest和代码中如何注册和使用BroadcastReceiver 在清单文件中注册广播接收者称为静态注册,在代码中注册称为动态注册。静态注册的广播接收者只要app在系统中运行则一直可以接收到广播消息,动态注册的广播接收者当注册的Activity或者Service销毁了那么就接收不到广播了。 静态注册:在清单文件中进行如下配置 ```xml ``` 动态注册:在代码中进行如下注册 ```java receiver = new BroadcastReceiver(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(CALL_ACTION); context.registerReceiver(receiver, intentFilter); ``` ### 3.BroadCastReceiver的生命周期 a. 广播接收者的生命周期非常短暂的,在接收到广播的时候创建,onReceive()方法结束之后销毁; b. 广播接收者中不要做一些耗时的工作,否则会弹出Application No Response错误对话框; c. 最好也不要在广播接收者中创建子线程做耗时的工作,因为广播接收者被销毁后进程就成为了空进程,很容易被系统杀掉; d. 耗时的较长的工作最好放在服务中完成; ## ContentProvider ### 1.请介绍下ContentProvider是如何实现数据共享的 在Android中如果想将自己应用的数据(一般多为数据库中的数据)提供给第三发应用,那么我们只能通过ContentProvider来实现了。 ContentProvider是应用程序之间共享数据的接口。使用的时候首先自定义一个类继承ContentProvider,然后覆写query、insert、update、delete等方法。因为其是四大组件之一因此必须在AndroidManifest文件中进行注册。 ```xml ``` 第三方可以通过ContentResolver来访问该Provider。 ### 2.请介绍下Android的数据存储方式 - File存储 - SharedPreference存储 - ContentProvider存储 - SQLiteDataBase存储 - 网络存储 ### 3.为什么要用ContentProvider?它和sql的实现上有什么差别? ContentProvider屏蔽了数据存储的细节,内部实现对用户完全透明,用户只需要关心操作数据的uri就可以了,ContentProvider可以实现不同app之间共享。 Sql也有增删改查的方法,但是sql只能查询本应用下的数据库。而ContentProvider 还可以去增删改查本地文件. xml文件的读取等。 ### 4、说说ContentProvider、ContentResolver、ContentObserver之间的关系 - ContentProvider 内容提供者,用于对外提供数据 - ContentResolver.notifyChange(uri)发出消息 - ContentResolver 内容解析者,用于获取内容提供者提供的数据 - ContentObserver 内容监听器,可以监听数据的改变状态 - ContentResolver.registerContentObserver()监听消息 ## Android中的布局 ### 1.Android中常用的布局都有哪些 - FrameLayout - RelativeLayout - LinearLayout - AbsoluteLayout - TableLayout - GridLayout ### 2.谈谈UI中, Padding和Margin有什么区别? android:padding和android:layout_margin的区别,其实概念很简单,padding是站在父view的角度描述问题,它规定它里面的内容必须与这个父view边界的距离。margin则是站在自己的角度描述问题,规定自己和其他(上下左右)的view之间的距离,如果同一级只有一个view,那么它的效果基本上就和padding一样了。 ## ListView ### 1.ListView如何提高其效率? - 复用ConvertView - 自定义静态类ViewHolder - 使用分页加载 - 使用WeakRefrence引用ImageView对象 ### 2.当ListView数据集改变后,如何更新ListView 使用该ListView的adapter的notifyDataSetChanged()方法。该方法会使ListView重新绘制。 ### 3.ListView如何实现分页加载 设置ListView的滚动监听器:`setOnScrollListener(new OnScrollListener{….})`。在监听器中有两个方法: 滚动状态发生变化的方法(onScrollStateChanged)和listView被滚动时调用的方法(onScroll) 在滚动状态发生改变的方法中,有三种状态: - 手指按下移动的状态:SCROLL_STATE_TOUCH_SCROLL: // 触摸滑动 - 惯性滚动(滑翔(flgin)状态):SCROLL_STATE_FLING: // 滑翔 - 静止状态:SCROLL_STATE_IDLE: // 静止 对不同的状态进行处理: 分批加载数据,只关心静止状态:关心最后一个可见的条目,如果最后一个可见条目就是数据适配器(集合)里的最后一个,此时可加载更多的数据。在每次加载的时候,计算出滚动的数量,当滚动的数量大于等于总数量的时候,可以提示用户无更多数据了。 ### 4.ListView可以显示多种类型的条目吗 这个当然可以的,ListView显示的每个条目都是通过baseAdapter的getView(int position, View convertView, ViewGroup parent)来展示的,理论上我们完全可以让每个条目都是不同类型的view,除此之外adapter还提供了getViewTypeCount()和getItemViewType(int position)两个方法。在getView方法中我们可以根据不同的viewtype加载不同的布局文件。 ### 5.ListView如何定位到指定位置 可以通过ListView提供的`lv.setSelection(48);`方法。 ### 6.当在ScrollView中如何嵌入ListView 通常情况下我们不会在ScrollView中嵌套ListView,但是如果面试官非让我嵌套的话也是可以的。 在ScrollView添加一个ListView会导致listview控件显示不全,通常只会显示一条,这是因为两个控件的滚动事件冲突导致。所以需要通过listview中的item数量去计算listview的显示高度,从而使其完整展示,如下提供一个方法供大家参考。 ```java lv = (ListView) findViewById(R.id.lv); adapter = new MyAdapter(); lv.setAdapter(adapter); setListViewHeightBasedOnChildren(lv); public void setListViewHeightBasedOnChildren(ListView listView) { ListAdapter listAdapter = listView.getAdapter(); if (listAdapter == null) { return; } int totalHeight = 0; for (int i = 0; i < listAdapter.getCount(); i++) { View listItem = listAdapter.getView(i, null, listView); listItem.measure(0, 0); totalHeight += listItem.getMeasuredHeight(); } ViewGroup.LayoutParams params = listView.getLayoutParams(); params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); params.height += 5;// if without this statement,the listview will be a little short listView.setLayoutParams(params); } ``` ### 7.ListView中如何优化图片 图片的优化策略比较多。 #### 处理图片的方式: 如果ListView中自定义的Item中有涉及到大量图片的,一定要对图片进行细心的处理,因为图片占的内存是ListView项中最头疼的,处理图片的方法大致有以下几种: - 不要直接拿路径就去循环BitmapFactory.decodeFile;使用Options保存图片大小、不要加载图片到内存去。 - 对图片一定要经过边界压缩尤其是比较大的图片,如果你的图片是后台服务器处理好的那就不需要了 - 在ListView中取图片时也不要直接拿个路径去取图片,而是以WeakReference(使用WeakReference代替强引用。比如可以使用WeakReference mContextRef)、SoftReference、WeakHashMap等的来存储图片信息。 - 在getView中做图片转换时,产生的中间变量一定及时释放 #### 异步加载图片基本思想: - 先从内存缓存中获取图片显示(内存缓冲) - 获取不到的话从SD卡里获取(SD卡缓冲) - 都获取不到的话从网络下载图片并保存到SD卡同时加入内存并显示(视情况看是否要显示) 原理: 优化一:先从内存中加载,没有则开启线程从SD卡或网络中获取,这里注意从SD卡获取图片是放在子线程里执行的,否则快速滑屏的话会不够流畅。 优化二:于此同时,在adapter里有个busy变量,表示listview是否处于滑动状态,如果是滑动状态则仅从内存中获取图片,没有的话无需再开启线程去外存或网络获取图片。 优化三:ImageLoader里的线程使用了线程池,从而避免了过多线程频繁创建和销毁,如果每次总是new一个线程去执行这是非常不可取的,好一点的用的AsyncTask类,其实内部也是用到了线程池。在从网络获取图片时,先是将其保存到sd卡,然后再加载到内存,这么做的好处是在加载到内存时可以做个压缩处理,以减少图片所占内存。 ### 8.ListView中图片错位的问题是如何产生的 图片错位问题的本质源于我们的listview使用了缓存convertView,假设一种场景,一个listview一屏显示九个item,那么在拉出第十个item的时候,事实上该item是重复使用了第一个item,也就是说在第一个item从网络中下载图片并最终要显示的时候,其实该item已经不在当前显示区域内了,此时显示的后果将可能在第十个item上输出图像,这就导致了图片错位的问题。所以解决之道在于可见则显示,不可见则不显示。 ### 9.Java中引用类型都有哪些 Java中对象的引用分为四种级别,这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。 **强引用(StrongReference)** 这个就不多说,我们写代码天天在用的就是强引用。如果一个对象被被人拥有强引用,那么垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。 Java的对象是位于heap中的,heap中对象有强可及对象、软可及对象、弱可及对象、虚可及对象和不可到达对象。应用的强弱顺序是强、软、弱、和虚。对于对象是属于哪种可及的对象,由他的最强的引用决定。如下代码: ```java String abc=new String("abc"); //1 SoftReference softRef=new SoftReference(abc); //2 WeakReference weakRef = new WeakReference(abc); //3 abc=null; //4 softRef.clear();//5 ``` 第一行在heap堆中创建内容为“abc”的对象,并建立abc到该对象的强引用,该对象是强可及的。 第二行和第三行分别建立对heap中对象的软引用和弱引用,此时heap中的abc对象已经有3个引用,显然此时abc对象仍是强可及的。 第四行之后heap中对象不再是强可及的,变成软可及的。 第五行执行之后变成弱可及的。 **软引用(SoftReference)** 如果一个对象只具有软引用,那么如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足之前会清除所有的软引用,这样以来gc就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集 取决于gc的算法和gc运行时可用内存的大小。当gc决定要收集软引用时执行以下过程,以上面的softRef为例: - 首先将softRef的referent(abc)设置为null,不再引用heap中的new String("abc")对象。 - 将heap中的newString("abc")对象设置为可结束的(finalizable)。 - 当heap中的new String("abc")对象的finalize()方法被运行而且该对象占用的内存被释放, softRef被添加到它的ReferenceQueue(如果有的话)中。 注意:对ReferenceQueue软引用和弱引用可以有可无,但是虚引用必须有。被 SoftReference 指到的对象,即使没有任何 Direct Reference,也不会被清除。一直要到 JVM 内存不足且没有Direct Reference 时才会清除,SoftReference 是用来设计 object-cache 之用的。如此一来 SoftReference 不但可以把对象 cache 起来,也不会造成内存不足的错误(OutOfMemoryError)。file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg **弱引用(WeakReference)** 如果一个对象只具有弱引用,那该类就是可有可无的对象,因为只要该对象被gc扫描到了随时都会把它干掉。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。 **虚引用(PhantomReference)** "虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。 虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。 建立虚引用之后通过get方法返回结果始终为null,通过源代码你会发现,虚引用通向会把引用的对象写进referent,只是get方法返回结果为null。先看一下和gc交互的过程再说一下他的作用。 1.不把referent设置为null, 直接把heap中的newString("abc")对象设置为可结束的(finalizable)。 2.与软引用和弱引用不同, 先把PhantomRefrence对象添加到它的ReferenceQueue中.然后在释放虚可及的对象。 ## JNI&NDK 1.在Android中如何调用C语言 当我们的Java需要调用C语言的时候可以通过JNI的方式,Java Native Interface。Android提供了对JNI的支持,因此我们在Android中可以使用JNI调用C语言。在Android开发目录的libs目录下添加xxx.so文件,不过xxx.so文件需要放在对应的CPU架构名目录下,比如armeabi,x86等。 在Java代码需要通过System.*loadLibrary*(libName);加载so文件。同时C语言中的方法在java中必须以native关键字来声明。普通Java方法调用这个native方法接口,虚拟机内部自动调用so文件中对应的方法。 2.请介绍一下NDK 1.NDK 是一系列工具的集合 NDK 提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so 和java 应用一起打包成apk。NDK 集成了交叉编译器,并提供了相应的mk 文件隔离CPU、平台、ABI 等差异,开发人员只需要简单修改mk 文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。 2.NDK 提供了一份稳定、功能有限的API 头文件声明 Google 明确声明该API 是稳定的,在后续所有版本中都稳定支持当前发布的API。从该版本的NDK 中看出,这些API支持的功能非常有限,包含有:C 标准库(libc)、标准数学库(libm)、压缩库(libz)、Log 库(liblog)。 3、JNI调用常用的两个参数 JNIEnv*env, jobject obj 第一个是指向虚拟机对象的指针,是一个二级指针。里面封装了很多方法和中间变量供我们使用。 第二个代表着调用该方法的Java对象的C语言表示。 ## Android中的网络访问 ### 1、Android中如何访问网络 Android提供了org.apache.http.HttpClientConnection和java.net.HttpURLConnection两个连接网络对象。使用哪个都行,具体要看企业领导的要求了。 除此之外一般我比较喜欢使用xUtils中的HttpUtils功能,该模块底层使用的就是org.apache.http.client.HttpClient,使用起来非常方便。 ### 2、如何解析服务器传来的JSON文件 在Android中内置了JSON的解析API,在org.json包中包含了如下几个类:JSONArray、JSONObject、JSONStringer、JSONTokener和一个异常类JSONException。 1、JSON解析步骤 1)读取网络文件数据并转为一个json字符串 ```java InputStreamin = conn.getInputStream(); StringjsonStr = DataUtil.Stream2String(in);//将流转换成字符串的工具类 ``` 2)将字符串传入相应的JSON构造函数中 - 通过构造函数将json字符串转换成json对象 ```java JSONObject jsonObject = new JSONObject(jsonStr); ``` - 通过构造函数将json字符串转换成json数组: JSONArray array = new JSONArray(jsonStr); 3)解析出JSON中的数据信息: - 从json对象中获取你所需要的键所对应的值 ```java JSONObject json=jsonObject.getJSONObject("weatherinfo"); Stringcity = json.getString("city"); Stringtemp = json.getString("temp") ``` - 遍历JSON数组,获取数组中每一个json对象,同时可以获取json对象中键对应的值 ```java for(int i = 0; i < array.length(); i++) { JSONObject obj = array.getJSONObject(i); Stringtitle=obj.getString("title"); Stringdescription=obj.getString("description"); } ``` 2、生成JSON对象和数组 1)生成JSON: 方法1、创建一个map,通过构造方法将map转换成json对象 ```java Map map = new HashMap(); map.put("name","zhangsan"); map.put("age",24); JSONObjectjson = new JSONObject(map); ``` 方法2、创建一个json对象,通过put方法添加数据 ```java JSONObjectjson=new JSONObject(); json.put("name","zhangsan"); json.put("age",24); ``` 2)生成JSON数组: 创建一个list,通过构造方法将list转换成json对象 ```java Map map1 = new HashMap(); map1.put("name","zhangsan"); map1.put("age",24); Map map2 = new HashMap(); map2.put("name","lisi"); map2.put("age",25); List> list=new ArrayList>(); list.add(map1); list.add(map2); JSONArrayarray=new JSONArray(list); System.out.println(array.toString()); ``` ### 3、如何解析服务器传来的XML格式数据 Android为我们提供了原生的XML解析和生成支持。 1、XML解析 - 获取解析器: Xml.newPullParser() - 设置输入流: setInput() - 获取当前事件类型: getEventType() - 解析下一个事件, 获取类型: next() - 获取标签名: getName() - 获取属性值: getAttributeValue() - 获取下一个文本: nextText() - 获取当前文本: getText() 5种事件类型: START_DOCUMENT, END_DOCUMENT, START_TAG,END_TAG, TEXT 示例代码: ```java public ListgetPersons(InuptStream in){ XmlPullParserparser=Xml.newPullParser();//获取解析器 parser.setInput(in,"utf-8"); for(inttype=){ //循环解析 } } ``` 2、XML生成 - 获取生成工具: Xml.newSerializer() - 设置输出流: setOutput() - 开始文档: startDocument() - 结束文档: endDocument() - 开始标签: startTag() - 结束标签: endTag() - 属性: attribute() - 文本: text() 示例代码: ```java XmlSerializer serial=Xml.newSerializer();//获取xml序列化工具 serial.setOuput(put,"utf-8"); serial.startDocument("utf-8",true); serial.startTag(null,"persons"); for(Person p:persons){ serial.startTag(null,"persons"); serial.attribute(null,"id",p.getId().toString()); serial.startTag(null,"name"); serial.attribute(null,"name",p.getName().toString()); serial.endTag(null,"name"); serial.startTag(null,"age"); serial.attribute(null,"age",p.getAge().toString()); serial.endTag(null,"age"); serial.endTag(null,"persons"); } ``` ### 4、如何从网络上加载一个图片显示到界面 可以通过BitmapFactory.decodeStream(inputStream);方法将图片转换为bitmap,然后通过imageView.setImageBitmap(bitmap);将该图片设置到ImageView中。这是原生的方法,还可以使用第三方开源的工具来实现,比如使用SmartImageView作为ImageView控件,然后直接设置一个url地址即可。也可以使用xUtils中的BitmapUtils工具。 ### 5、如何播放网络视频 除了使用Android提供的MediaPlayer和VideoView外通常还可以使用第三方开源万能播放器,VitamioPlayer。该播放器兼容性好,支持几乎所有主流视频格式。 ## Intent ### 1、Intent传递数据时,可以传递哪些类型数据? Intent可以传递的数据类型非常的丰富,java的基本数据类型和String以及他们的数组形式都可以,除此之外还可以传递实现了Serializable和Parcelable接口的对象。 ### 2、Serializable和Parcelable的区别 - 在使用内存的时候,Parcelable 类比Serializable性能高,所以推荐使用Parcelable类。 - Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。 - Parcelable不能使用在要将数据存储在磁盘上的情况。尽管Serializable效率低点,但在这种情况下,还是建议你用Serializable 。 - 实现: 1、Serializable 的实现,只需要继承Serializable 即可。这只是给对象打了一个标记,系统会自动将其序列化。 2、Parcelabel 的实现,需要在类中添加一个静态成员变量 CREATOR,这个变量需要继承 Parcelable.Creator 接口。 ```java public class MyParcelable implements Parcelable { private int mData; public int describeContents() { return 0; } public void writeToParcel(Parcel out, int flags) { out.writeInt(mData); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public MyParcelable createFromParcel(Parcel in) { return new MyParcelable(in); } public MyParcelable[] newArray(int size) { return new MyParcelable[size]; } }; private MyParcelable(Parcel in) { mData = in.readInt(); } } ``` ### 3、请描述一下Intent 和 IntentFilter Android 中通过 Intent 对象来表示一条消息,一个 Intent对象不仅包含有这个消息的目的地,还可以包含消息的内容,这好比一封 Email,其中不仅应该包含收件地址,还可以包含具体的内容。对于一个 Intent 对象,消息“目的地”是必须的,而内容则是可选项。 通过Intent 可以实现各种系统组件的调用与激活。 IntentFilter: 可以理解为邮局或者是一个信笺的分拣系统… 这个分拣系统通过3个参数来识别 - Action: 动作view - Data: 数据uri uri - Category : 而外的附加信息 Action 匹配 Action 是一个用户定义的字符串,用于描述一个 Android 应用程序组件,一个 IntentFilter 可以包含多个 Action。在 AndroidManifest.xml 的 Activity 定义时可以在其 <intent-filter >节点指定一个 Action 列表用于标示 Activity 所能接受的“动作”, 例如: ```xml …… ``` 如果我们在启动一个 Activity 时使用这样的Intent 对象 ``` Intentintent =new Intent(); intent.setAction("cn.itheima.action"); ``` 那么所有的 Action 列表中包含了“cn.itheima”的 Activity 都将会匹配成功。 Android 预定义了一系列的 Action 分别表示特定的系统动作。这些Action 通过常量的方式定义在 android.content. Intent中,以“ACTION_”开头。我们可以在 Android 提供的文档中找到它们的详细说明。 URI 数据匹配 一个 Intent 可以通过 URI 携带外部数据给目标组件。在 <intent-filter >节点中,通过 <data/>节点匹配外部数据。 mimeType 属性指定携带外部数据的数据类型,scheme 指定协议,host、port、path 指定数据的位置、端口、和路径。如下: ```xml ``` 电话的uri tel:12345 自己定义的uri itcast://cn.itcast/person/10 如果在 Intent Filter 中指定了这些属性,那么只有所有的属性都匹配成功时 URI 数据匹配才会成功。 Category 类别匹配 <intent-filter >节点中可以为组件定义一个 Category 类别列表,当 Intent 中包含这个列表的所有项目时 Category 类别匹配才会成功。 ## Fragment1、Fragment跟Activity之间是如何传值的 当Fragment跟Activity绑定之后,在Fragment中可以直接通过getActivity()方法获取到其绑定的Activity对象,这样就可以调用Activity的方法了。在Activity中可以通过如下方法获取到Fragment实例: ```java FragmentManager fragmentManager = getFragmentManager(); Fragment fragment = fragmentManager.findFragmentByTag(tag); Fragment fragment = fragmentManager.findFragmentById(id); ``` 获取到Fragment之后就可以调用Fragment的方法。也就实现了通信功能。 2、描述一下Fragment的生命周期 ![img](http://bbs.itheima.com/data/attachment/forum/201508/09/221025fgoosncfa1cv215l.png.thumb.jpg) ## 能否在子线程中更新UI 可以的,在onCreate()的setContentView()后new一个Thread去更新UI是不会报错的,但是延迟1s后再更新UI就会报错,这是因为在onCreate()的时候ViewRoot的requestLayout()方法没有执行,layout布局文件还没有创建完成。而ViewRoot的requestLayout()方法中才会调用checkThread()方法检查当前是否是主线程,不是的话就抛CalledFromWrongThreadException。Android中的定义是:不建议在子线程中更新UI,否则会产生不可预知的错误 ================================================ FILE: docs/android/Android-Interview/Android/Android视频教程.md ================================================ ## Android视频教程 - 零基础入门Android语法与界面 - Kotlin系统入门与进阶 - 从Java到Kotlin系列精讲 - Kotlin打造完整电商APP 模块化+MVP+主流框架 - Android强化:服务与通信之广播、服务、蓝牙 - Gradle3.0自动化项目构建技术精讲+实战 - React Native技术精讲与高质量上线APP开发 - 双平台真实开发GitHub App React Native技术全面掌握 - Android应用发展趋势必备武器 热修复与插件化 - Android通用框架设计与完整电商APP开发 - 全程MVP手把手 打造IM即时通讯Android APP - RxJava从源码到应用,移动端开发效率秒提速 - Android开发—高级开发专题系列全套课程 - BAT大咖助力 全面升级Android面试 - Android高级面试 10大开源框架源码解析 - BAT大厂APP架构演进实践与优化之路 - Android架构师之路 网络层架构设计与实战 - Android互动直播APP开发 - 从零开发Android视频点播APP - 组件化封装思想实战Android App - 带领新手快速开发Android App - 快速开发轻量级App ================================================ FILE: docs/android/Android-Interview/Android/Android面试精华题目总结.md ================================================ > 原文链接:http://blog.csdn.net/lmj623565791/article/details/24015867/ 下面的题目都是楼主在[Android](http://lib.csdn.net/base/android)交流群大家面试时遇到的,如果大家有好的题目或者好的见解欢迎分享,楼主将长期维护此帖。 ## 某公司高级面试题 1、详述Android系统架构,包括层与层之间调用、binder、jni、底层文件读写方法 android源码之前分为四层,如下图: ![](img/framework.jpg) 新的android源码分为五层([平台架构](平台架构.md)),增加了HAL层,如下图: ![](img/framework.png) - Linux Kernel Linux内核,主要是一些硬件驱动和Binder驱动(IPC进程间通信) - 硬件抽象层 Android系统的硬件抽象层(Hardware Abstract Layer,HAL)运行用户空间中,它向下屏蔽硬件驱动模块的实现细节,向上提供硬件访问服务。通过硬件抽象层,Android系统分两层来支持硬件设备,其中一层实现在用户空间中,另一层实现在内核空间中。传统的Linux系统把对硬件的支持完全实现在内核空间中,即把对硬件的支持完全实现在硬件驱动模块中。 Android系统里封装内核驱动程序的接口层。对上层提供接口,屏蔽底层驱动实现细节. 本来Linux内核可以负责驱动接口定义和驱动实现,但是受限于GNU License(开源感染性),如果厂商选择驱动接口和实现都在内核空间完成,就必须开放自己的驱动源代码。这是不符合厂商利益的(驱动包含核心硬件参数,与其他厂家竞争的法宝)。所以Google将Linux内核中跟底层硬件操作相关的逻辑封装成HAL层接口,厂商基于接口去实现,不直接在内核空间实现驱动。因为Android系统遵循Apache License,不强制开源。 - Libraries and Android Runtime 原生C/C++库,系统运行的核心库,Android运行时环境,包括核心库和dvm - Application FrameWork 框架层 提供的是Java API,主要是各种Manager,如WindowManager,ActivityManager等。 硬件访问服务通过硬件抽象层模块来为应用程序提供硬件读写操作。由于硬件抽象层模块是使用C++语言开发的,而应用程序框架层中的硬件访问服务是使用Java语言开发的,因此,硬件访问服务必须通过Java本地接口(Java Native Interface,JNI)来调用硬件抽象层模块的接口。 在Android应用程序框架层开发硬件访问服务的目的是为了让上层的Android应用程序能够访问对应的硬件设备。 - Applications 应用层 我们使用的系统自带App或者自己安装的App都属于应用层 应用程序框架中的基于Java语言的Binder接口是通过JNI来调用基于C/C++语言的Binder运行库来为Java应用程序提供进程间通信服务的 2、描述自己的一个项目,要求画出结构图,UML图,详细描述项目种的技术点,技术难点以及解决方案 3、一道[算法](http://lib.csdn.net/base/datastructure) 4、谈谈自己项目管理的方法、对[敏捷,即原型开发](http://lib.csdn.net/base/agile)软件开发的理解 ## 基础面试题 **1. 请解释下在单线程模型中Message,Handler,MessageQueue,Looper之间的关系。** 拿主线程来说,主线程启动时会调用Looper.prepare()方法,会初始化一个Looper,放入Threadlocal中,接着调用Looper.loop()不断遍历MessageQueue,Handler的创建依赖与当前线程中的Looper,如果当前线程没有Looper则必须调用Looper.prepare()。Handler , sendMessage到MessageQueue,Looper不断从MessageQueue中取出消息,回调handleMessage方法。 **2. 如果有个100M大的文件,需要上传至服务器中,而服务器form表单最大只能上传2M,可以用什么方法。** 这个问题不是很明确我觉得,首先来说使用http协议上传数据,特别在android下,跟form没什么关系。传统的在web中,在form中写文件上传,其实浏览器所做的就是将我们的数据进行解析组装拼成字符串,以流的方式发送到服务器,且上传文件用的都是POST方式,POST方式对大小没什么限制。 回到题目,可以说假设每次真的只能上传2M,那么可能我们只能把文件截断,然后分别上传了。 **3. 内存溢出和内存泄漏有什么区别?何时会产生内存泄漏?内存优化有哪些方法?** 内存溢出通俗理解就是软件(应用)运行需要的内存,超出了它可用的最大内存。 内存泄漏就是我们对某一内存空间的使用,使用完成后没有释放。 内存优化:Android中容易内存溢出的部分,就是图片的加载,我们可以使用图片的压缩加上使用LruCache缓存的目的来控制图片所能够使用的内存。 还有对于比较耗资源的对象及时的关闭,例如Database Conn , 各种传感器 , Service 等等。 **4. AsyncTask使用在哪些场景?它的缺陷是什么?如何解决?** AsyncTask 运用的场景就是我们需要进行一些耗时的操作,耗时操作完成后更新主线程,或者在操作过程中对主线程的UI进行更新。 缺陷:AsyncTask中维护着一个长度为128的线程池,同时可以执行5个工作线程,还有一个缓冲队列,当线程池中已有128个线程,缓冲队列已满时,如果此时向线程提交任务,将会抛出RejectedExecutionException。 解决:由一个控制线程来处理AsyncTask的调用判断线程池是否满了,如果满了则线程睡眠否则请求AsyncTask继续处理。 **5. Activity用SharedPreferences保存数据,大小有木有限制?** 这个真心查不到 Sp的底层是由xml实现的,操作Sp的过程就是xml的序列化和解析的过程.Xml是储存在磁盘上的,因此考虑到IO速度问题,sp不适宜频繁操作.同时序列化xml就是将内存中的数据写到xml文件中,由于dvm的内存是很有限的,因此单个sp文件不建议太大,具体多大是没有一个明确的要求的,但是我们知道DVM堆内存也就是16M,因此数据大小肯定不能超过这个数字,其实SP设置的目的就是为了保存用户的偏好和配置信息的,因此建议不要保存太多的数据 **6. Activity间通过Intent传递数据大小有没有限制?** 貌似是40K。Intent貌似应该是1m,Intent携带数据太大是会报错,比如携带一张大图片是就会发生异常 **7. assest文件夹里放文件,对于文件的大小有没有限制?** assets目录更像一个附录类型的目录,Android不会为这个目录中的文件生成ID并保存在R类当中,因此它与Android中的一些类和方法兼容度更低。 同时,由于你需要一个字符串路径来获取这个目录下的文件描述符,访问的速度会更慢。但是把一些文件放在这个目录下会使一些操作更加方便,比方说拷贝一个[数据库](http://lib.csdn.net/base/mysql)文件到系统内存中。要注意的是,你无法在Android XML文件中引用到assets目录下的文件,只能通过AssetManager来访问 这些文件。数据库文件和游戏数据等放在这个目录下是比较合适的。另外,网上关于assets和raw的资料都千篇一律了,因此关于这两者中单个文件 大小不能超过1M的**错误**描述也在传播,即如果读取超过1M的文件会报"Data exceeds UNCOMPRESS_DATA_MAX (1314625 vs 1048576)"的IOException,还引申出种种解决方案。个人认为不应该有这样的限制,为了验证这个说法写了个Demo,发现将近5M的压缩包在assets和raw中都能正常访问,因此在这里纠正一下,理论上只要打包不超过Android APK 50M大小的限制都是没有问题的。当然了,不排除是Android很早期的时候因为设备硬件原因aapt在编译的时候对这两个文件夹大小做出了限制,如果是这样,较新版的ADT应该不会出现这种情况。 来自:http://my.eoe.cn/futurexiong/archive/5350.html **8. 启动一个程序,可以主界面点击图标进入,也可以从一个程序中跳转过去,二者有什么区别?** 是因为启动程序(主界面也是一个app),发现了在这个程序中存在一个设置为``的activity,所以这个launcher会把icon提出来,放在主界面上。当用户点击icon的时候,发出一个Intent: ```java Intent intent = mActivity.getPackageManager().getLaunchIntentForPackage(packageName); mActivity.startActivity(intent); ``` 跳过去可以跳到任意允许的页面,如一个程序可以下载,那么真正下载的页面可能不是首页(也有可能是首页),这时还是构造一个Intent,startActivity.这个intent中的action可能有多种view,download都有可能。系统会根据第三方程序向系统注册的功能,为你的Intent选择可以打开的程序或者页面。所以唯一的一点不同的是从icon的点击启动的intent的action是相对单一的,从程序中跳转或者启动可能样式更多一些。本质是相同的。 **9. 程序之间的亲和性的理解** - 默认情况下一个应用的所有Activity都是具有相同的affinity,都是从application中继承,application的affinity默认就是manifest的包名。 - affinity对Activity来说,就像是身份证一样,可以告诉所在的Task,自己属于其中的一员。 - 应用场合: - 根据affinity重新为Activity选择合适的宿主Task - 与allowTaskReparenting属性配合 - 启动Activity使用Intent设置了FLAG_ACTIVITY_NEW_TASK标记 **10. 同一个程序,但不同的Activity是否可以放在不同的Task任务栈中?** 可以放在不同的Task中。需要为不同的activity设置不同的affinity属性,启动activity的Intent需要包含FLAG_ACTIVITY_NEW_TASK标记。 **11. 横竖屏切换时候Activity的生命周期。** - 不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次 - 设置Activity的android:configChanges="orientation"时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次 - 设置Activity的android:configChanges="orientation|keyboardHidden"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法 **12. AIDL的全称是什么?如何工作?** 全称是:Android Interface Define Language 在Android中, 每个应用程序都可以有自己的进程. 在写UI应用的时候, 经常要用到Service. 在不同的进程中, 怎样传递对象呢? 显然, [Java](http://lib.csdn.net/base/javase)中不允许跨进程内存共享. 因此传递对象, 只能把对象拆分成[操作系统](http://lib.csdn.net/base/operatingsystem)能理解的简单形式, 以达到跨界对象访问的目的. 在J2EE中,采用RMI的方式, 可以通过序列化传递对象. 在Android中, 则采用AIDL的方式. 理论上AIDL可以传递Bundle,实际上做起来却比较麻烦。 AIDL(Android接口描述语言)是一种接口描述语言; 编译器可以通过aidl文件生成一段代码,通过预先定义的接口达到两个进程内部通信进程的目的. 如果需要在一个Activity中, 访问另一个Service中的某个对象, 需要先将对象转化成AIDL可识别的参数(可能是多个参数), 然后使用AIDL来传递这些参数, 在消息的接收端, 使用这些参数组装成自己需要的对象.AIDL的IPC的机制和COM或CORBA类似, 是基于接口的,但它是轻量级的。它使用代理类在客户端和实现层间传递值. 如果要使用AIDL, 需要完成2件事情: 1. 引入AIDL的相关类; 2. 调用aidl产生的class. AIDL的创建方法: AIDL语法很简单,可以用来声明一个带一个或多个方法的接口,也可以传递参数和返回值。 由于远程调用的需要, 这些参数和返回值并不是任何类型. 下面是些AIDL支持的数据类型: 1. 不需要import声明的简单Java编程语言类型(int,boolean等) 2. String, CharSequence不需要特殊声明 3. List, Map和Parcelables类型, 这些类型内所包含的数据成员也只能是简单数据类型, String等其他比支持的类型. (另外: 我没尝试Parcelables, 在Eclipse+ADT下编译不过, 或许以后会有所支持 **13. dvm的进程和Linux的进程, 应用程序的进程是否为同一个概念** Dvm的进程是dalivk虚拟机进程,每个android程序都运行在自己的进程里面,每个android程序系统都会给他分配一个单独的liunx uid(user id),每个dvm都是linux里面的一个进程.所以说这两个进程是一个进程. ================================================ FILE: docs/android/Android-Interview/Android/Android面试重点.md ================================================ ## Imageloader ### 常用的图片处理框架 Fresco 正在实现了三级缓存,2级内存缓存,一级本地缓存、Glide、Picasso、UIL-ImageLoader ### 图片占用内存 大小 = 每个像素占用的字节数 * 总像素 Bitmap.Config - RGB_565 - ARGB_8888 内存溢出 ### 图片乱序错位问题 imageview.setTag(url) 弱引用双向关联 使用volley的NetImageView ### 四种引用类型 - 强引用 - 弱引用 - 软引用 - 虚引用 ### 三级缓存 1、DiskLrucache 本地/磁盘缓存 2、Lrucache 内存缓存 原理 LinkedHashMap 双向循环链表 - 参数1:初始大小 - 参数2:装载因子 - 参数3:true 按访问顺序排序,false 按插入顺序排序 内存大小 ```java int memory = (int) (Runtime.getRuntime().maxMemory()/8); LruCache cache = new LruCache(memory){ @Override protected int sizeOf(String key, Bitmap value) { return value.getByteCount(); } }; ``` Lrucache .get(key) --> LinkedHashMap.get(key) : 命中,移动到双向循环链表的的尾部 Lrucache .put(key) --> LinkedHashMap.put(key) : 如果size > maxSize,删除链表header节点直到size < maxSize 3、 网络缓存 4、缓存路径 - getExternalCacheDir() - getCacheDir() ### 图片压缩 BitmapFactory.Options - inJustDecodeBounds - inSampleSize - outWidth - outHeight - inBitmap ```java /** * Decode and sample down a bitmap from resources to the requested width and height. * * @param res The resources object containing the image data * @param resId The resource id of the image data * @param reqWidth The requested width of the resulting bitmap * @param reqHeight The requested height of the resulting bitmap * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap * @return A bitmap sampled down from the original with the same aspect ratio and dimensions * that are equal to or greater than the requested width and height */ public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight, ImageCache cache) { // BEGIN_INCLUDE (read_bitmap_dimensions) // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // END_INCLUDE (read_bitmap_dimensions) // If we're running on Honeycomb or newer, try to use inBitmap if (Utils.hasHoneycomb()) { addInBitmapOptions(options, cache); } // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } /** * Decode and sample down a bitmap from a file to the requested width and height. * * @param filename The full path of the file to decode * @param reqWidth The requested width of the resulting bitmap * @param reqHeight The requested height of the resulting bitmap * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap * @return A bitmap sampled down from the original with the same aspect ratio and dimensions * that are equal to or greater than the requested width and height */ public static Bitmap decodeSampledBitmapFromFile(String filename, int reqWidth, int reqHeight, ImageCache cache) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(filename, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // If we're running on Honeycomb or newer, try to use inBitmap if (Utils.hasHoneycomb()) { addInBitmapOptions(options, cache); } // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeFile(filename, options); } /** * Decode and sample down a bitmap from a file input stream to the requested width and height. * * @param fileDescriptor The file descriptor to read from * @param reqWidth The requested width of the resulting bitmap * @param reqHeight The requested height of the resulting bitmap * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap * @return A bitmap sampled down from the original with the same aspect ratio and dimensions * that are equal to or greater than the requested width and height */ public static Bitmap decodeSampledBitmapFromDescriptor( FileDescriptor fileDescriptor, int reqWidth, int reqHeight, ImageCache cache) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; // If we're running on Honeycomb or newer, try to use inBitmap if (Utils.hasHoneycomb()) { addInBitmapOptions(options, cache); } return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); } /** * Calculate an inSampleSize for use in a {@link android.graphics.BitmapFactory.Options} object when decoding * bitmaps using the decode* methods from {@link android.graphics.BitmapFactory}. This implementation calculates * the closest inSampleSize that is a power of 2 and will result in the final decoded bitmap * having a width and height equal to or larger than the requested width and height. * * @param options An options object with out* params already populated (run through a decode* * method with inJustDecodeBounds==true * @param reqWidth The requested width of the resulting bitmap * @param reqHeight The requested height of the resulting bitmap * @return The value to be used for inSampleSize */ public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // BEGIN_INCLUDE (calculate_sample_size) // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } // This offers some additional logic in case the image has a strange // aspect ratio. For example, a panorama may have a much larger // width than height. In these cases the total pixels might still // end up being too large to fit comfortably in memory, so we should // be more aggressive with sample down the image (=larger inSampleSize). long totalPixels = width * height / inSampleSize; // Anything more than 2x the requested pixels we'll sample down further final long totalReqPixelsCap = reqWidth * reqHeight * 2; while (totalPixels > totalReqPixelsCap) { inSampleSize *= 2; totalPixels /= 2; } } return inSampleSize; // END_INCLUDE (calculate_sample_size) } ``` ## EventBus ### 1. 事件总线 组件(activity,fragment,service)间的交互,通信,主线程子线程间的通信 观察者模式,发布订阅,注解,Map<订阅对象,订阅方法> 事件类型EventType,事件响应函数 ### 2. 发布接收事件 - 发布事件post() EventBus.getDefault().post(new User("mr.simple"), "my_tag"); 反射调用,回调 - 注册事件register() 扫描订阅对象的所有方法,包括分类的方法,匹配方法,保存到Map集合中 - 接收事件,事件响应函数 @Subscriber(tag = "my_tag", mode=ThreadMode.POST) ### 3. EventBus 订阅队列 Map 判断是否是主线程:Looper.getMainLooper() == Looper.myLooper(); Handler mHandler = new Handler(Looper.getMainLooper()); ThreadLocal<PostingThreadState> 存储了⼀个事件队列以及事件的状态 ``` class PostingThread { List mEventQueue = new ArrayList(); boolean isMainThread; boolean isPosting; } ``` Map<Class, CopyOnWriteArrayList<SubscribeMethod>> 通过订阅对象的字节码拿到订阅对象匹配的方法以及方法的参数,其中threadmode是通过截取字符串获得,EventBus3.0是通过注解的方式拿到threadmode,方法的参数就是EventType ## Android EventBus ### EventType 方法的参数和tag组成EventType ## 依赖注入IOC ## ImageLoader 面向接口编程,策略模式,BitmapRequest,链式调用 return this Builder模式,单例模式,生产者消费者模式 模板方法模式(算法骨架) ### BitmapRequest 图片请求 ### ImageLoaderConfig 设置一些基本的东西,比如加载中的图片、加载失败的图片、缓存策略 ### RequestQueue 内部维持着一个PriorityBlockingQueue ``` BlockingQueue mRequestQueue = new PriorityBlockingQueue(); ``` ### RequestDispatcher ## HttpUtils ### 常用的网络请求框架 - Retrofit - OkHttp - NoHttp - Volley - HttpURLConnection(省电省流量) - HttpClient(6.0后删除) - AsyncHttpclient 请求头Map mHeaders,请求参数Map mBodyParams,URLEncoder.encode() - NetworkExecutor 网络请求线程 - HttpStack Http执行器 - ResponseDelivery Response分发 ### Http协议 请求Request - 请求首行 - 请求头 - 空行 - 请求体 响应Response - 响应首行 - 响应头 - 空行 - 响应体 请求头 响应头 通用头 ContentType - get 查 - post 改 - put 增 提交表单 - delete 删 - head 只返回首部 - trace 诊断,跟踪http请求是否被修改 - options 请求服务器告知其支持的各种功能 ### 网络模型 TCP/IP参考模型 - 应用层 http,https,ftp - 传输层 tcp udp - 网络层 ip地址 - 物理层 - 数据链路层 ### 三次、四次握手 ### 三要素 ip地址,端口号,协议 ### 文件上传 表单数据,Content-Type:multipart/form-data ## ORM框架 注解(运行时注解或编译时注解)反射 注解标记表和表中的列,javabean上添加注解,指定一个类对应的表名,一个字段在表中对应的列名 在清单文件中配置数据库名,版本号,还可以配置model model类可以从清单文件中获取,也可以通过扫描整个应用获取,扫描当前App的dex文件 创建表:通过注解标记表名和字段名,通过反射拿到model的信息来拼接sql语句 ### 数据库操作类 - Selection - Delete - Update - Insert ================================================ FILE: docs/android/Android-Interview/Android/Android面试题-1.md ================================================ ![](img/面试题1.jpg) ![](img/面试题2.jpg) ![](img/面试题3.jpg) ![](img/面试题4.jpg) ### 整个面试题视频如下(持续更新中): #### 与IPC机制相关面试题 - [1- Davik进程linux进程线程之间的区别](https://v.qq.com/x/page/a03916l1n7h.html) - [2- aidl实现进程间通信](https://v.qq.com/x/page/m0391pnoyl7.html) - [3- messenger实现进程间通信](https://v.qq.com/x/page/t0391b2gjm5.html) - [4- ContentProvider实现进程间通信](https://v.qq.com/x/page/v0391vx3ynb.html) #### 与性能优化相关试题 - [5- 什么是内存泄漏](https://v.qq.com/x/page/n0391if5dtb.html) - [6- 什么是内存溢出](https://v.qq.com/x/page/q03917e4zk5.html) - [7- 什么情况会导致内存泄漏](https://v.qq.com/x/page/j03927ullcj.html) - [8- 避免程序的OOM异常](https://v.qq.com/x/page/w0392bn6wto.html) - [9- 线程池原理](https://v.qq.com/x/page/u0393izwfut.html) - [10- UI性能优化](https://v.qq.com/x/page/j0393ytx9ob.html) - [11- 内存优化之字符串优化](https://v.qq.com/x/page/k0393ataw3l.html) - [12- 常见内存优化方式](https://v.qq.com/x/page/j0393gm2p7j.html) - [13- 性能分析之hierarchyviewer使用](https://v.qq.com/x/page/y0393sa0jlp.html) - [14- 性能分析之Lint规范代码](https://v.qq.com/x/page/d039381wbas.html) - [15- 性能分析之规避内存抖动](https://v.qq.com/x/page/x0393gf7qp6.html) - [16- 性能分析之内存检测工具介绍](https://v.qq.com/x/page/e03933o0tp7.html) #### 与XMPP相关试题 - [17- 什么是XMPP和XMPP的数据格式](https://v.qq.com/x/page/t0394w3zhoa.html) - [18- 及时聊天的展示形式](https://v.qq.com/x/page/k0394y5jo6d.html) - [19- TCP和UDP协议](https://v.qq.com/x/page/b0394lzj76e.html) - [20- 极光推送原理](https://v.qq.com/x/page/h0394a7zioh.html) - [21- XMPP的基本概念](https://v.qq.com/x/page/s0394k4p10i.html) - [22- 常见消息推送的解决方案](https://v.qq.com/x/page/h0394s3mc5k.html) #### 与登录相关试题 - [23- 微信扫一扫登录内部实现原理](https://v.qq.com/x/page/u03952rbbkc.html) - [24- 腾讯QQ三方登录实现原理](https://v.qq.com/x/page/p03953hoam3.html) - [25- 登录为什么要使用Token](https://v.qq.com/x/page/c0395s3jd4f.html) #### 与开发相关试题 - [26- 迭代开发的时候如何向前兼容新旧接口](https://v.qq.com/x/page/a0395pv28zm.html) - [27- 应用程序的开发流程](https://v.qq.com/x/page/v0395agrpdw.html) ================================================ FILE: docs/android/Android-Interview/Android/Android面试题-2.md ================================================ --- typora-copy-images-to: img --- ## Android启动流程 ![](img/Android启动流程.png) ![](img/启动流程.png) ![](img/系统启动流程图.png) 在Android系统启动时,第一个启动起来的进程就是Zygote(进程孵化器)进程,然后由Zygote启动SystemServer,再后就是启动ActivityManagerService,WindowManagerService等系统核心服务,这些服务承载着整个Android系统与客户端程序交互的重担。Zygote除了启动系统服务和进程之外,普通的用户进程也由Zygote进程fork而来,当一个应用进程启动起来后,就会加载用户在清单文件(AndroidManifest.xml)中配置的默认加载的Activity,此时加载的入口是`ActivityThread.main(String[] args)`,这个方法就类似与C语言中的main()方法,是整个应用程序的入口。 由操作系统的引导文件去运行linux的内核程序,内核程序开始启动的时候会加载各种驱动和数据结构,开始加载android应用层的第一个进程(init进程c代码(system\core\init目录) Init.c) Zygote进程 --> SystemServer --> 系统服务AMS,WMS,PMS... 1、当引导程序启动Linux内核后,会加载各种驱动和数据结构,当有了驱动以后,开始启动Android系统同时会加载用户级别的第一个进程init(system\core\init.c)代码如下: ```c int main(int argc, char **argv) { // 创建文件夹 挂载 mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755"); mkdir("/dev/pts", 0755); // 打卡日志 log_init(); INFO("reading config file\n"); // 加载init.rc配置文件 init_parse_config_file("/init.rc"); } ``` 2、加载init.rc文件,会启动一个Zygote进程,此进程是Android系统的一个母进程,用来启动Android的其他服务进程,代码: ``` service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server socket zygote stream 666 onrestart write /sys/android_power/request_state wake onrestart write /sys/power/state on onrestart restart media onrestart restart netd ``` 3、从c++代码调到java代码: ```c++ int main(int argc, const char* const argv[]) { ... // Android运行时环境 AppRuntime runtime; ... // Next arg is startup classname or "--zygote" if (i < argc) { arg = argv[i++]; if (0 == strcmp("--zygote", arg)) { bool startSystemServer = (i < argc) ? strcmp(argv[i], "--start-system-server") == 0 : false; setArgv0(argv0, "zygote"); set_process_name("zygote"); // 启动java代码 runtime.start("com.android.internal.os.ZygoteInit", ... } ``` 4、ZygoteInit.java 代码: ```java public static void main(String argv[]) { try { VMRuntime.getRuntime().setMinimumHeapSize(5 * 1024 * 1024); // 加载Android依赖的类 preloadClasses(); //cacheRegisterMaps(); preloadResources(); ... if (argv[1].equals("true")) { // 启动系统服务 startSystemServer(); } else if (!argv[1].equals("false")) { ... } private static boolean startSystemServer() ... args = new String[] { "--setuid=1000", "--setgid=1000", "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,3001,3002,3003,3006", "--capabilities=130104352,130104352", "--rlimit=8,", "--runtime-init", "--nice-name=system_server", "com.android.server.SystemServer", ... /* Request to fork the system server process */ // 母进程开始分叉服务 启动SystemServer pid = Zygote.forkSystemServer( parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, debugFlags, rlimits, parsedArgs.permittedCapabilities, parsedArgs.effectiveCapabilities); .. } ``` 5、SystemServer.java 代码 ```java public static void main(String[] args) { ... // 加载jni库 System.loadLibrary("android_servers"); // 调用native方法 init1(args); } native public static void init1(String[] args); ``` 6、SystemServer 对应的c++代码 com_android_server_SystemServer.cpp 代码如下: ``` // 类似java的抽象方法 extern "C" int system_init(); static void android_server_SystemServer_init1(JNIEnv* env, jobject clazz) { // 转调 system_init(); } /* * JNI registration. */ static JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ // 函数指针 把init1方法映射到android_server_SystemServer_init1 { "init1", "([Ljava/lang/String;)V", (void*) android_server_SystemServer_init1 }, }; ``` 7、system_init 的实现方法在System_init.cpp 代码如下: ``` extern "C" status_t system_init() { ... // 启动硬件的服务 if (strcmp(propBuf, "1") == 0) { // Start the SurfaceFlinger SurfaceFlinger::instantiate(); } AndroidRuntime* runtime = AndroidRuntime::getRuntime(); LOGI("System server: starting Android services.\n"); // 启动完硬件服务后,又回到Systemserver的init2方法 runtime->callStatic("com/android/server/SystemServer", "init2"); ... } ``` 8、SystemServer 的init2方法代码: ```java public static final void init2() { Slog.i(TAG, "Entered the Android system server!"); Thread thr = new ServerThread(); thr.setName("android.server.ServerThread"); thr.start(); } ``` 9、ServerThread的run方法: ``` public void run() { ... // 开启Android各种服务并且添加到ServiceManager去管理 Slog.i(TAG, "Device Policy"); devicePolicy = new DevicePolicyManagerService(context); ServiceManager.addService(Context.DEVICE_POLICY_SERVICE, ottle = ... // We now tell the activity manager it is okay to run third party // code. It will call back into us once it has gotten to the state // where third party code can really run (but before it has actually // started launching the initial applications), for us to complete our // initialization. // 各种服务开启后调用ActivityManagerService.systemReady ((ActivityManagerService)ActivityManagerNative.getDefault()) .systemReady(new Runnable() { public void run() { Slog.i(TAG, "Making services ready"); ``` 10、ActivityMangerService的systemReady的方法: ``` public void systemReady(final Runnable goingCallback) { ... // 打开第一个Activity mMainStack.resumeTopActivityLocked(null); } } ``` 11、ActivityStack的resumeTopActivityLocked方法 ``` final boolean resumeTopActivityLocked(ActivityRecord prev) { // Find the first activity that is not finishing. // 没有已经打开的Activity next为 null ActivityRecord next = topRunningActivityLocked(null); // Remember how we'll process this pause/resume situation, and ensure // that the state is reset however we wind up proceeding. final boolean userLeaving = mUserLeaving; mUserLeaving = false; if (next == null) { // There are no more activities! Let's just start up the // Launcher... if (mMainStack) { // 启动lucher应用的锁屏界面 return mService.startHomeActivityLocked(); } } ``` 12、Android系统启动完成,打开了Luncher应用的Home界面。 ## 面试记录 1.对比ListView跟RecyclerView,RecyclerView有什么优势? 2.了解过哪些网络框架?OkHttp比HttpURLConntaion好在哪里? 3.Retrofit怎么进行二次封装?有哪些好处? 4.自定义View的了解? 5.短信验证码怎么保证登录的安全? - 密码加密 md5,非对称加密 - 动态密码 - 验证码 - 有效时间 - TrustZone https://www.zhihu.com/question/24173904 身份认证有三个方式:你知道的,你持有的,以及你固有的。一般的口令密码之类算第一类(你知道的),持有令牌通行证之类算第二类(你持有的),指纹虹膜等生物特征算第三类(你固有的)。由于获取/伪造的难度不同,一般认为第一类的安全性比第二类差,第二类又比第三类差;但需要明确的是,如果只有其中一种都算是弱认证,必须独立使用两种甚至三种才算是强认证。 普通应用比如邮箱的认证方式都是口令或密码这第一类认证,使用短信验证码则是为了提供第二类认证。在安全设定中,做得好的系统会要求关键修改要同时使用两种认证方式,即:使用密码登录,然后修改关键信息比如已经注册过的手机号,还需要先用之前的手机接收验证码;单独获得手机后,是不应该能够登入账户并修改所有信息的,否则就破坏了多种认证方式直接的独立性,进而破坏了系统的安全性。 在智能手机的年代,由于OS开放了短信操作和拦截的接口(Android直接提供,iOS需要越狱),对于一个安装了支付类App的智能手机且绑定账户的SIM卡也安装在同一个手机的情况(绝大部分情况下是这样),短信验证事实上已经退化成了单因子验证,只要智能手机被安装了木马那么这些验证体系就会全线崩溃,攻击者甚至可以只通过钓鱼wifi全部搞定登陆密码、支付密码和短信验证 [短信验证码](http://www.jinloushiji.cn/)算是短信细分行业里的小众行业,因为价格便宜却能针对性的起到大力宣传作用,自然短信这个行业的竞争也愈演愈烈,各个短信运营商都绞尽脑汁在确保网站短信验证码接口相关措施的严密。 阅信短信验证码平台小编下面从五个方面入手说说阅信短信平台是怎么做的: - 绑定服务器IP。供应商的短信验证码接口只能识别客户的绑定服务器IP,否则将会报错; - 开启反投诉策略。短信验证码接口容易受到轰炸,供应商应将同个手机号码在24小时内接收同一根通道短信条数最大值设置为10; 阅信网站短信验证码接口 - 防盗用策略。为了防止客户账号被盗,供应商可以限制短信验证码接口任何时间段的任意条数上限。类似于刚刚上线的App客户,短信日发送上限可以设置成10000条; - 关闭网页发送短信权限。验证码基本上经由短信验证码接口提交,所以web端的网页提交功能应该关闭; - 绑定后台登录手机号码。短信接口管理后台可以查看到每一条短信的发送详情,为了保证数据安全,必须要指定专人管理,登录后台要收到手机短信验证码才能登录成功。 当然,验证码安全并不完全由通道供应商单方面控制,从平台开发者本身出发,也需要做好防范机制,例如完善网站入口处的二次验证。 随着App项目的推广,网站或手机短信验证码接口广泛应用在用户验证各方面,大大降低了非法注册,烂注册的数据的出现,所以其安全防范机制尤为重要。 6.线程间通信有哪些方式? gravity跟layout_gravity区别 静态成员变量运行题 常见五种布局 自动更新原理 Activity生命周期 手写数据表查询年龄最大的十个人 数据存储的几种方式 通过网络请求加载图片并缓存到本地 第二次加载图片时从本地加载 用实际例子问我权重,sp,数据库,然后看了下app,问了哪些新控件使用比较熟,然后问RecyclerView怎么添加分隔线,我说用网上找的工具类。数据库很重视,但是问的几个问题我都只能说出思路。 Android已离职,面试我的估计是java,上来先问了个反射,然后对着简历挑着跟java沾边的东西问,比如线程间通信,MVP跟MVC模式,Cookie,然后问了些安卓的自定义View的绘制流程、事件分发机制、第三方SDK。一开始还问了我做没做过百度地图,我说做过但是没有扩展很多功能,只做了基本的功能实现,然后他问导航做过没,我说没有。实际上他们公司下个项目就要用到百度地图。 公司环境一般,一个大厅用玻璃隔出了一个会议室跟老板办公室,剩下的就是两排桌子面对面坐着8~10个人,员工都比较年轻,很多刚毕业的。面试我的人感觉经验也不丰富,技术水平应该比较一般 1.AIDL简历写了的,抓紧了解清楚怎么使用! 2.对称加密与非对称加密的区别,进程间通信的所有方式,数据库优化与框架(存入一万条数据怎么优化?),事件分发细节不够清楚(返回true/false/super相应有什么结果?),内容提供者不熟悉,网络框架源码必须得看一个! 上来狂怼Java基础和Java相关的知识点,问了怎么创建线程?暂停线程的两种方法及区别?AIDL服务怎么暴露方法?工厂模式?观察者模式?http长连接短连接和组成?使用什么请求?数据库?线程池的使用场景? 面试上来先自我介绍,完了就开始聊项目,感觉整个过程都挺随意的,问了挺多问题,但是基本都没有深究,基本没问到太细节的实现。JNI跟混合开发都有被问到,看来还是得了解一点其他知识点给自己涨涨身价 面试官起码三十多岁,问的网络的问题听着都很懵逼,还问到app测试版跟正式版有什么不同?三个gradle有什么区别?对gradle有什么理解?有没有发生过闪退?怎么定位问题? 问了些实际开发的问题,比如pad上图标如何适配,UI给比例图时我们怎么确定文字的大小,集成第三方SDK需要多少时间。 问的比较简单,就自定义View跟事件分发、Activity、服务、网络、进程通信,还简单讲了一下Binder的底层原理。顺利通过技术面试 跟老板谈了下,被问到认为自己技术还有哪些方面需要提升,顺势说目前已经了解过Binder底层,还需要了解更多Android底层的东西和框架的实现原理,以便更好运用和修改。问到职业规划表示自己不光对技术感兴趣,对产品也很感兴趣。然后讲了下对上个公司产品的一些看法,对他们公司产品的看法,提了两个优化的点。基本就顺利过关了 ================================================ FILE: docs/android/Android-Interview/Android/Android高级面试10大开源框架源码解析.md ================================================ ## Android高级面试,10大开源框架源码解析 编程最好的学习方法是阅读顶尖工程师的源码!本课程将带你深度剖析Android主流开源框架的源码,让你全面掌握框架的使用场景、内部机制、构造原理、核心类、架构与设计思想等,提升你的代码阅读与分析能力、提高代码设计能力及改造能力,快速突破技术瓶颈,轻松应对Android高级面试与技术难题! ## 课程章节 ### 第1章 课程介绍 编程最好的学习方法是阅读顶级工程师的源码!本课程将带你深度剖析Android主流开源框架的源码,让你全面掌握框架的使用场景、内部机制、构造原理、核心类、架构与设计思想等,提升你的代码阅读与分析能力、提高代码设计能力及改造能力,快速突破技术瓶颈,轻松应对Android高级面试与技术难题! ... - 1-1 课程导学 ### 第2章 Okhttp网络库深入解析和相关面试题分析 本章主要先通过分析OKhttp的简单使用,对于OKhttp的调度器、拦截器、缓存策略、连接池等进行了相应的源码和原理分析,并对于socket、websocket、http缓存、多线程下载、文件下载、https等经典Android面试题进行分析。 - 2-1 okhttp框架流程分析 - 2-2 okhttp同步请求方法 - 2-3 okhttp异步请求方法 - 2-4 okhttp同步请求流程和源码分析 - 2-5 okhttp异步请求流程和源码分析-1 - 2-6 okhttp异步请求流程和源码分析-2 - 2-7 okhttp任务调度核心类dispatcher解析-1 - 2-8 okhttp任务调度核心类dispatcher解析-2 - 2-9 okhttp拦截器流程 - 2-10 okhttp拦截器链介绍 - 2-11 okhttp之RetryAndFollowUpInterceptor解析 - 2-12 okhttp之BridgeInterceptor解析 - 2-13 okhttp缓存策略源码分析:put方法 - 2-14 okhttp缓存策略源码分析:get方法 - 2-15 okhttp拦截器之CacheInterceptor解析 - 2-16 okhttp拦截器之ConnectInterceptor解析-1 - 2-17 okhttp拦截器之ConnectInterceptor解析-2 - 2-18 okhttp连接池:put,get方法 - 2-19 okhttp连接池:connection回收 - 2-20 okhttp拦截器之CallServerInterceptor解析 - 2-21 okhttp面试: Socket-1 - 2-22 okhttp面试: Socket-2 - 2-23 okhttp面试: HttpClient&HttpUrlConnection - 2-24 okhttp面试: OkHttp来实现WebSocket连接 - 2-25 okhttp面试: WebSocket&轮询相关 - 2-26 okhttp面试: Http缓存、Etag等标示作用 - 2-27 okhttp面试: 断点续传原理&Okhttp如何实现 - 2-28 okhttp面试:多线程下载 - 2-29 okhttp面试:文件上传&Okhttp如何处理文件上传 - 2-30 okhttp面试:如何解析Json类型数据 - 2-31 okhttp面试:Https/对称加密&不对称加密 ### 第3章 Retrofit网络库深入解析和相关面试题分析 本章主要先通过分析retrofit的使用,对于retrofit的接口、动态代理、适配工厂、数据转换等进行相应的源码和原理分析,并对于retrofit的设计模式、线程切换、Hook、MVC和MVP架构、SP跨进程问题等经典Android面试题进行分析。 - 3-1 retrofit流程分析 - 3-2 retrofit概述 - 3-3 retrofit官网例子解析 - 3-4 retrofit请求过程7步骤详解 - 3-5 静态代理模式讲解 - 3-6 动态代理模式讲解 - 3-7 retrofit网络通信流程8步骤&7个关键成员变量解析 - 3-8 retrofit中builder构建者模式&builder内部类解析 - 3-9 retrofit中baseurl/converter/calladapter解析 - 3-10 retrofit中build方法完成retrofit对象创建流程解析 - 3-11 retrofit中RxjavaCallAdapterFactory内部构造与工作原理解析 - 3-12 retrofit中网络请求接口实例解析 - 3-13 retrofit中serviceMethod对象解析 - 3-14 retrofit中okHttpCall对象和adapt返回对象解析 - 3-15 retrofit中同步请求&重要参数解析 - 3-16 retrofit中异步请求解析 - 3-17 retrofit设计模式解析-1:构建者模式 - 3-18 retrofit设计模式解析-2:工厂模式 - 3-19 retrofit设计模式解析-3:外观模式 - 3-20 retrofit设计模式解析-4:策略模式 - 3-21 retrofit设计模式解析-5:适配器模式 - 3-22 retrofit设计模式解析-6:动态代理模式/观察者 - 3-23 retrofit面试题:retfrofit线程切换(异步机制Looper) - 3-24 retrofit面试题:rxjava和retrofit如何结合进行网络请求 - 3-25 retrofit面试题:Hook与动态代理 - 3-26 retrofit面试题:Android MVC架构优势和缺点 - 3-27 retrofit面试题:MVP优点和缺点 - 3-28 retrofit面试题:sp跨进程&apply和commit方法 ### 第4章 Glide图片库深入解析和相关面试题分析 本章主要先通过分析Glide的使用,对于glide的内存和硬盘缓存、加载策略、如何进行图片网络请求等方面,并将重点放在梳理整个Glide请求的流程,最后对于bitmap、性能优化OOM和三级缓存、Lrucache等Android面试题进行分析。 - 4-1 glide框架流程分析 - 4-2 glide框架介绍-1 - 4-3 glide框架介绍-2 - 4-4 glide图片加载流程和源码分析-1:with方法(requestManager获取) - 4-5 glide图片加载流程和源码分析-2:with方法(requestManagerRetriever的get方法) - 4-6 glide图片加载流程和源码分析-3:load方法 - 4-7 glide图片加载流程和源码分析-4:into方法(buildTarget) - 4-8 glide图片加载流程和源码分析-5:into方法(request建立和begin方法) - 4-9 glide图片加载流程和源码分析-6:into方法(Loadprovider) - 4-10 glide图片加载流程和源码分析-7:into方法(硬盘缓存/内存缓存) - 4-11 glide图片加载流程和源码分析-8:into方法(内存缓存的读取) - 4-12 glide图片加载流程和源码分析-9:into方法(内存缓存的写入) - 4-13 Glide面试一:bitmap&oom&优化bitmap - 4-14 Glide面试二:三级缓存&lrucache ### 第5章 LeakCanary内存泄漏框架解析和相关面试题分析 本章主要先通过leakcanary使用,然后分析内存泄漏产生原因,并对于Leakcanary如何进行泄漏Activity收集策略、转换内存快照、定位内存泄漏位置等分析,最后对于现在业界比较关心的UI流畅度和性能数据上报等进行对应分析。 - 5-1 leakcanary预备知识:android性能优化&Gcroots - 5-2 leakcanary内存框架:内存泄漏基础&为什么需要leakcanary - 5-3 android常见内存泄漏分析-1:单例VS非静态内部类 - 5-4 android常见内存泄漏分析-2:handler&解决办法 - 5-5 android常见内存泄漏分析-3:线程&WebView - 5-6 leakcanary原理分析-1:Leakcanary原理概述和弱引用/引用队列 - 5-7 leakcanary原理分析-2:ActivityRefWatcher如何监视Activity - 5-8 leakcanary原理分析-3:.hprof转换snapshot - 5-9 leakcanary原理分析-4:查找内存泄漏引用和最短泄漏路径 - 5-10 leakcanary面试题:Application&内存 - 5-11 leakcanary面试题:性能数据上报:网络流量和冷启动 - 5-12 leakcanary面试题:性能数据上报:UI卡顿和内存占用 ### 第6章 butterknife依赖注入框架源码解析 本章从butterknife的基本使用讲起,首先会介绍框架相关注解和APT知识点,然后开始逐步分析butterknife源码,并逐步理清butterknife注入框架的原理,最后提炼butterknife中有关android面试相关问题。 - 6-1 butterknife的引言和基本使用 - 6-2 butterknife原理必备知识点1:注解 - 6-3 butterknife原理必备知识点2:APT工作原理 - 6-4 butterknife原理必备知识点3:反射+运行时注解举例 - 6-5 butterknife原理分析-1:注解处理器如何处理注解和保存注解 - 6-6 butterknife原理分析-2:如何生成findviewByID代码 ### 第7章 blockcanary UI卡顿优化框架源码解析 本章会从blockcanary基本使用讲起,首先会简单介绍ActivityThread/handler/looper相关框架知识点,然后通过分析blockcanary源码,逐步理清blockcanary如何解决UI卡顿的原理,最后会提炼blockcanary中有关android面试相关问题,并总结android性能优化相关问题。... - 7-1 blockcanary背景/UI卡顿原理/UI卡顿常见原因 - 7-2 blockcanary使用/阀值参数 - 7-3 blockcanary核心原理实现和流程图简述 - 7-4 blockcanary源码解析-1:框架初始化 - 7-5 blockcanary源码解析-2:stacksampler/cpusampler/start方法 - 7-6 blockcanary面试一:anr场景/原因/解决 - 7-7 blockcanary面试二:watchdog-anr 如何检测anr - 7-8 blockcanary面试三:new Thread开启线程的4点弊端 - 7-9 blockcanary面试四:线程间通信:子线程--UI线程 - 7-10 blockcanary面试五:主线程--子线程(handlerThread-IntentService) - 7-11 blockcanary面试六:多进程的4点好处与问题/voliate关键字 - 7-12 blockcanary面试七:voliate关键字和单例的写法 ### 第8章 eventbus异步框架源码解析 本章会从eventbus的基本用法开始讲起,主要包括Event、Subscriber、Publisher、ThreadMode几大部分,并结合handler、组件间传递等消息知识点深入分析,然后对比分析eventbus3.0和2.0的区别,并结合eventbus在android面试中遇到的高频问题,对eventbus框架进行总结。... - 8-1 eventbus框架核心概念:事件传递/EventBus的优点/传统handler通信的两种方式 - 8-2 eventbus框架基本用法 - 8-3 eventbus框架源码解析-1:EventBus对象构建/如何进行线程调度 - 8-4 eventbus框架源码解析-2 subscribe注解/threadMode - 8-5 eventbus框架源码解析-3:register订阅(上) - 8-6 eventbus框架源码解析-4:register订阅(中) - 8-7 eventbus框架源码解析-5:register订阅(下) - 8-8 eventbus框架源码解析-6:subscribe方法完成订阅(上) - 8-9 eventbus框架源码解析-7:subscribe方法完成订阅(下) - 8-10 eventbus框架源码解析-8:发送事件post ### 第9章 dagger2依赖注入框架源码解析 本章从dagger2的基本使用讲起,首先会介绍框架相关依赖注入的知识点,然后逐步分析dagger2源码,并逐步理清dagger2注入框架原理,并对比分析dagger2与dagger的区别,最后会根据android面试相关问题,给大家总结dagger2的相关知识点。 - 9-1 dagger2引言:依赖注入和使用场景 - 9-2 dagger2四种注入方式和依赖注入总结 - 9-3 dagger2的四种基本注解:@inject注解 - 9-4 dagger2的四种基本注解:@component注解 - 9-5 dagger2的inject和component注解实例和源码分析 - 9-6 dagger2的@Module和@Provides注解 - 9-7 dagger2的@Module和@Provides注解实例和代码分析 ### 第10章 rxjava异步框架源码解析 本章会从rxjava的基本使用讲起:主要包括观察者模式、操作符、线程控制等,然后逐步分析rxjava中的响应式编程原理,最后会结合rxjava在android面试中遇到的高频面试问题,给大家总结rxjava相关知识。 - 10-1 rxjava基本用法和观察者模式:01-传统观察者模式 - 10-2 rxjava观察者模式和基本用法 - 10-3 rxjava如何创建Observable&observer/subscriber - 10-4 rxjava如何创建subscriber以及如何完成订阅 - 10-5 rxjava操作符之map基本使用 - 10-6 rxjava操作符之map源码探究:lift - 10-7 rxjava操作符之flatmap - 10-8 rxjava线程控制:多线程编程准则&Rxjava如何处理多线程&&Schedulers - 10-9 rxjava线程控制:两个小例子&observeOn和SubscribeOn - 10-10 rxjava线程控制:SubscribeOn源码剖析 - 10-11 rxjava线程控制:ObserveOn源码剖析&&subscribeOn可以调用几次 ### 第11章 picasso图片框架源码解析 本章从picasso基本用法和配置讲起,逐步分析picasso的源码,并从DownLoader,Dispatcher,service线程池等核心类进行分析,最后根据picasso流程图进行总结,并给大家提炼android面试中有关picasso框架的问题。 - 11-1 picasso框架基本使用API - 11-2 picasso源码with方法:内存缓存Lrucache和线程池的调度 - 11-3 piacsso源码with:dispatcher如何完成线程切换 - 11-4 picasso源码with:NetworkRequestHandler处理图片请求和回调 - 11-5 picasso源码load方法 - 11-6 picasso源码into方法:Action&BitmapHunter - 11-7 picasso源码into方法:线程池&PicassoFutureTask - 11-8 picasso源码into:线程开启如何执行图片加载请求? - 11-9 picasso源码into:Okhttp和UrlConnectionDownloader下载图片 - 11-10 picasso源码into方法:完成加载 ### 第12章 课程总结 本章将通过对Android面试技巧的梳理,帮助大家整体的认知和提高Android面试能力以及需要做的面试准备等,希望能对大家的面试有所帮助!最后非常感谢大家对课程的认可和支持,祝愿你们都能找到好工作。收到你们的Offer消息,是做好这门课程最大的动力。... - 12-1 Android面试技巧梳理 ================================================ FILE: docs/android/Android-Interview/Android/BAT大咖助力全面升级Android面试.md ================================================ ## BAT大咖助力,全面升级Android面试 安卓面试全新升级课程,已经帮助很多同学在短时间内拿到了满意的offer,作为Android开发求职路上快速赢取满意offer的必备课程,不管你是什么级别的工程师,都可以通过这门课程的学习,轻松快速搞定Android面试,赢取满意offer! ## 课程章节 ### 第1章 课程介绍 本章带你了解面试过程中会遇到的问题,个人应该摆正的心态,以及面试官最为看重你的解决问题的思路。 ### 第2章 一线互联网公司初中高Android开发工程师的技能要求 本章对一线互联网公司各个级别Android开发工程师的招聘需求进行深入分析,并带大家清晰完整的了解面试复习与准备思路,做到有的放矢,有侧重点的进行复习与准备。 ### 第3章 Android基础相关面试问题 本章主要从Android的五大组件给大家讲解,讲解的思路主要通过一道道面试题的思路带大家逐题破解,以点带面来复习巩固android基础知识,并会在每道课程结尾处给大家总结补充一些知识点,主要讲解:Fragment 、Service 、Binder 、Activity、 BroadCast 、WebView安全漏洞、android6.0/7.0增加的新功能。... ### 第4章 异步消息处理机制相关面试问题 Android中异步消息处理在面试中是一定会被问到的,在实战过程中也是非常重要的一个开发手段,我们会从handler、Asynctask、IntentService、HandlerThread给大家详细讲解,主要通过使用、源码机制来给大家深入分析异步消息处理机制。 ### 第5章 View相关面试问题 本章主要从view的绘制、事件分发、listview、属性动画来给大家进行讲解,自定义控件在日常开发中是也是必不可少的,本章主旨就是能了解其中的原理,从而在面试中遇到同类问题能给出相应的思路。 ### 第6章 Android项目构建相关面试问题 开发过程中项目的构建是很重要一环,也是检验你是不是一个合格的android开发工程师的标志,面试中也会经常问到,在这里我们主要通过Android的编译打包、Proguard混淆、git的使用、gradle、渠道包这五个部分给大家分析,带大家了解Android构建的全过程,从而轻松应对这类问题的各种面试与开发。... ### 第7章 开源框架相关面试问题 本章主要带大家分析现在热门的开源框架主要有网络框架:retrofit/okhttp/volley,图片加载框架fresco/gilde/uil,IOC框架:butterknife/dagger2,分析的思路是从使用到深入源码分析,开源框架可以说是一个高级工程师的试金石,如果对于以上框架很熟悉,并能画出流程图,会让面试官对于刮目相看。... ### 第8章 Android异常与性能优化相关面试问题 随着现在的android开发业务逻辑不断扩大,对于手机的性能也提出了很高的要求,所以一款app在性能上如果能区别其他app也将脱颖而出,同样如果候选人能对性能优化很熟悉,也将在面试中脱颖而出,本章主要从UI卡顿、内存管理、内存泄漏这几个角度带大家分析性能优化。... ### 第9章 热门前沿知识相关面试问题 现在Android发展越来越快,对于一些前沿的知识,在面试中我们也是需要做到了解,这章从Android的插件化、热更新、rxjava、进程保活,组件化,签名过程,应用沙盒等方面给大家讲解,主要想做到扩大大家的知识面,让面试官看到你对android的热爱。 ### 第10章 Java高级技术点面试问题 在Android的面试中,面试官通常缺少不了会问一下Java高级技术,本章就会为大家讲解Java相关高级技术面试点,包括GC/回收算法/堆栈/、反射/编译时vs运行时、注解(结合android annotation库)、范型、线程池/并发编程、Socket、IO/NIO、集合框架、类加载器、异常、继承/组合/多态、引用类型/内存泄漏、java虚拟机/d... ### 第11章 设计模式相关面试问题 设计模式是高级开发者的必备知识,面试中也是经常被问到,本章将结合Android使用场景,讲解常用的设计模式,让大家既掌握Android下设计模式的使用,又可轻松应对面试中关于设计模式的面试问题。包括观察者模式、动态代理 、工厂、策略类、装饰、桥接、单例等常用设计模式。... ### 第12章 网络协议相关面试问题 网络编程无论在开发中还是在面试中都是非常重要的,在面试中尤其对网络协议问的比较多,本章将会对网络协议进行讲解,包括https/http、dns、tcp/ip以及加密算法。 ### 第13章 算法相关面试问题 算法作为编程的重要部分,在BAT等大公司基本是必考项,本章将结合案例为大家讲解常用常考的算法面试问题,帮助大家提高算法能力的同时轻松应对算法相关的面试。 ### 第14章 课程总结 本章主要总结面试过程的相关技术点。同时也将面试的内容做一个归纳总结,最后非常感谢大家的支持,课程中遇到任何问题都可以在问答区提问,我在那里等着大家,有问必答,也祝愿大家都能尽早的获得一份心仪的offer。 ================================================ FILE: docs/android/Android-Interview/Android/README.md ================================================ ## Android面试题 - [Android基础面试核心内容](Android基础面试核心内容.md) - [Android面试精华题目总结](Android面试精华题目总结.md) - [Android面试题-1](Android面试题-1.md) - [Android面试题-2](Android面试题-2.md) - [Android面试重点](Android面试重点.md) - [接口安全](接口安全.md) ================================================ FILE: docs/android/Android-Interview/Android/平台架构.md ================================================ Android 是一种基于 Linux 的开放源代码软件栈,为广泛的设备和机型而创建。下图所示为 Android 平台的主要组件。 ![图 1. Android 软件栈](img/framework.png) ## Linux 内核 Android 平台的基础是 Linux 内核。例如,[Android Runtime (ART)](https://developer.android.google.cn/guide/platform/index.html#art) 依靠 Linux 内核来执行底层功能,例如线程和低层内存管理。 使用 Linux 内核可让 Android 利用[主要安全功能](https://source.android.com/security/overview/kernel-security.html),并且允许设备制造商为著名的内核开发硬件驱动程序。 ## 硬件抽象层 (HAL) [硬件抽象层 (HAL)](https://source.android.com/devices/index.html#Hardware%20Abstraction%20Layer) 提供标准界面,向更高级别的 [Java API 框架](https://developer.android.google.cn/guide/platform/index.html#api-framework)显示设备硬件功能。HAL 包含多个库模块,其中每个模块都为特定类型的硬件组件实现一个界面,例如[相机](https://source.android.com/devices/camera/index.html)或[蓝牙](https://source.android.com/devices/bluetooth.html)模块。当框架 API 要求访问设备硬件时,Android 系统将为该硬件组件加载库模块。 ## Android Runtime 对于运行 Android 5.0(API 级别 21)或更高版本的设备,每个应用都在其自己的进程中运行,并且有其自己的 [Android Runtime (ART)](http://source.android.com/devices/tech/dalvik/index.html) 实例。ART 编写为通过执行 DEX 文件在低内存设备上运行多个虚拟机,DEX 文件是一种专为 Android 设计的字节码格式,经过优化,使用的内存很少。编译工具链(例如 [Jack](https://source.android.com/source/jack.html))将 Java 源代码编译为 DEX 字节码,使其可在 Android 平台上运行。 ART 的部分主要功能包括: - 预先 (AOT) 和即时 (JIT) 编译 - 优化的垃圾回收 (GC) - 更好的调试支持,包括专用采样分析器、详细的诊断异常和崩溃报告,并且能够设置监视点以监控特定字段 在 Android 版本 5.0(API 级别 21)之前,Dalvik 是 Android Runtime。如果您的应用在 ART 上运行效果很好,那么它应该也可在 Dalvik 上运行,但[反过来不一定](https://developer.android.google.cn/guide/platform/verifying-apps-art.html)。 Android 还包含一套核心运行时库,可提供 Java API 框架使用的 Java 编程语言大部分功能,包括一些 [Java 8 语言功能](https://developer.android.google.cn/guide/platform/j8-jack.html)。 ## 原生 C/C++ 库 许多核心 Android 系统组件和服务(例如 ART 和 HAL)构建自原生代码,需要以 C 和 C++ 编写的原生库。Android 平台提供 Java 框架 API 以向应用显示其中部分原生库的功能。例如,您可以通过 Android 框架的 [Java OpenGL API](https://developer.android.google.cn/reference/android/opengl/package-summary.html) 访问 [OpenGL ES](https://developer.android.google.cn/guide/topics/graphics/opengl.html),以支持在应用中绘制和操作 2D 和 3D 图形。 如果开发的是需要 C 或 C++ 代码的应用,可以使用 [Android NDK](https://developer.android.google.cn/ndk/index.html) 直接从原生代码访问某些[原生平台库](https://developer.android.google.cn/ndk/guides/stable_apis.html)。 ## Java API 框架 您可通过以 Java 语言编写的 API 使用 Android OS 的整个功能集。这些 API 形成创建 Android 应用所需的构建块,它们可简化核心模块化系统组件和服务的重复使用,包括以下组件和服务: - 丰富、可扩展的[视图系统](https://developer.android.google.cn/guide/topics/ui/overview.html),可用以构建应用的 UI,包括列表、网格、文本框、按钮甚至可嵌入的网络浏览器 - [资源管理器](https://developer.android.google.cn/guide/topics/resources/overview.html),用于访问非代码资源,例如本地化的字符串、图形和布局文件 - [通知管理器](https://developer.android.google.cn/guide/topics/ui/notifiers/notifications.html),可让所有应用在状态栏中显示自定义提醒 - [Activity 管理器](https://developer.android.google.cn/guide/components/activities.html),用于管理应用的生命周期,提供常见的[导航返回栈](https://developer.android.google.cn/guide/components/tasks-and-back-stack.html) - [内容提供程序](https://developer.android.google.cn/guide/topics/providers/content-providers.html),可让应用访问其他应用(例如“联系人”应用)中的数据或者共享其自己的数据 开发者可以完全访问 Android 系统应用使用的[框架 API](https://developer.android.google.cn/reference/packages.html)。 ## 系统应用 Android 随附一套用于电子邮件、短信、日历、互联网浏览和联系人等的核心应用。平台随附的应用与用户可以选择安装的应用一样,没有特殊状态。因此第三方应用可成为用户的默认网络浏览器、短信 Messenger 甚至默认键盘(有一些例外,例如系统的“设置”应用)。 系统应用可用作用户的应用,以及提供开发者可从其自己的应用访问的主要功能。例如,如果您的应用要发短信,您无需自己构建该功能,可以改为调用已安装的短信应用向您指定的接收者发送消息。 ================================================ FILE: docs/android/Android-Interview/Android/接口安全.md ================================================ 保证接口安全一般分为两种,一个是防数据篡改,一个是防数据泄漏,防篡改使用摘要验证,防泄漏使用加密 ## token token=5位随机数+时间戳 aesEnctrypt(token) (1)验证时间和服务器时间不能超过3分。 (2)同一个时间戳,随机数只能使用一次。 ## 对称加密 把参数对称加密 aes ## 签名验证 ras 调用方,要提供公钥,sign(通过双方定义好的[算法](http://lib.csdn.net/base/datastructure)把摘要和参数计算后的值) 我们把post过来的参数先解密,通过制定的算法算出sign 我们看我们算出sign与调用方传过来的sign是否一致,一致则可以进行业务处理,不一致返回签名失败。 sign算法 secretKey是双方定义的摘要 params是参数的hashmap集合 ```java byte[] data=Digest.getDigest(secretKey, params); public class Digest { public static byte[] getDigest(String secretKey, Map params) throws Exception{ Set keySet = params.keySet(); TreeSet sortSet = new TreeSet<>(); sortSet.addAll(keySet); String keyValueStr = ""; Iterator it = sortSet.iterator(); while(it.hasNext()){ String key = it.next(); String value = params.get(key); keyValueStr += key + value; } keyValueStr = keyValueStr + secretKey; MessageDigest md = MessageDigest.getInstance("SHA-1"); return md.digest(keyValueStr.getBytes("utf-8")); } } boolean b=RsaUtil.verify(data, sign, publicKeyString); ``` b=false 返回签名失败。 以下是rasutil ```java /** * */ package com.hlmedicals.app.util; import Java.io.UnsupportedEncodingException; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import com.itextpdf.xmp.impl.Base64; /** * @author dell * */ public class RsaUtil { public static void main (String [] args){ try { byte[] privateKeyString= IConst.privateKeyString.getBytes("utf-8"); byte[] publicKeyString= IConst.publicKeyString.getBytes("utf-8"); String data1 = "testabc"; //siyao签名 byte[] data=data1.getBytes("utf-8"); byte[] s = sign(data, privateKeyString); boolean b=verify(data,s,publicKeyString); System.out.println(b); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 使用私钥对数据进行加密签名 * @param data 数据 * @param privateKeyString 私钥 * @return 加密后的签名 */ public static byte[] sign(byte[] data, byte[] privateKeyString) throws Exception { KeyFactory keyf = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyf.generatePrivate(new PKCS8EncodedKeySpec(Base64.decode(privateKeyString))); java.security.Signature signet = java.security.Signature.getInstance("SHA1withRSA"); signet.initSign(privateKey); signet.update(data); byte[] signed = signet.sign(); return Base64.encode(signed); } /** * 使用公钥判断签名是否与数据匹配 * @param data 数据 * @param sign 签名 * @param publicKeyString 公钥 * @return 是否篡改了数据 */ public static boolean verify(byte[] data, byte[] sign, byte[] publicKeyString) throws Exception { KeyFactory keyf = KeyFactory.getInstance("RSA"); PublicKey publicKey = keyf.generatePublic(new X509EncodedKeySpec(Base64.decode(publicKeyString))); java.security.Signature signet = java.security.Signature.getInstance("SHA1withRSA"); signet.initVerify(publicKey); signet.update(data); return signet.verify(Base64.decode(sign)); } } ``` 4.http 转成htts 5.后台登录要有验证码,没有验证码 ,系统会被爆破,验证码不能被多次使用。 我的项目登录成功后把验证码存在session里。要把session验证码设定为空就可以。 6.系统上传文件时候,应该上传白名单的文件类型,防止jsp、jspx在系统中执行导致系统崩掉。 在开发过程中,肯定会有和第三方或者app端的接口调用。在调用的时候,如何来保证非法链接或者恶意攻击呢? ## 签名 根据用户名或者用户id,结合用户的ip或者设备号,生成一个token。在请求后台,后台获取http的head中的token,校验是否合法(和[数据库](http://lib.csdn.net/base/mysql)或者[Redis](http://lib.csdn.net/base/redis)中记录的是否一致,在登录或者初始化的时候,存入数据库/redis) 在使用Base64方式的编码后,Token字符串还是有20多位,有的时候还是嫌它长了。由于GUID本身就有128bit,在要求有良好的可读性的前提下,很难进一步改进了。那我们如何产生更短的字符串呢?还有一种方式就是较少Token的长度,不用GUID,而采用一定长度的随机数,例如64bit,再用Base64编码表示: ``` var rnd = new Random(); var tokenData = userIp+userId; rnd.NextBytes(tokenData); var token = Convert.ToBase64String(tokenData).TrimEnd('='); ``` 由于这里只用了64bit,此时得到的字符串为Onh0h95n7nw的形式,长度要短一半。这样就方便携带多了。但是这种方式是没有唯一性保证的。不过用来作为身份认证的方式还是可以的(如网盘的提取码)。 ## 加密 客户端和服务器都保存一个秘钥,每次传输都加密,服务端根据秘钥解密。 客户端: ​ 1、设置一个key(和服务器端相同) ​ 2、根据上述key对请求进行某种加密(加密必须是可逆的,以便服务器端解密) ​ 3、发送请求给服务器 服务器端: ​ 1、设置一个key ​ 2、根据上述的key对请求进行解密(校验成功就是「信任」的客户端发来的数据,否则拒绝响应) ​ 3、处理业务逻辑并产生结果 ​ 4、将结果反馈给客户端 ## 第三方支持 比如[spring](http://lib.csdn.net/base/javaee) security-oauth 有兴趣的,可以参考这篇帖子 http://wwwcomy.iteye.com/blog/2230265 多数采用OAuth2,不过对于盗cookie没招,走HTTPS吧 api进行token校验或者签名,另外对于防刷,要进行限流 题主要看你如何定义接口安全了,我们可以看看新浪微博,每天无数的爬虫在爬,他的接口安全么?一般意义上安全指的是传输过程中不被盗取,走https基本可以做到,至于有人拿到api文档一类的东西(app被反向),那怎么样也防不了 所以如同楼上 1.对IP进行过滤,频繁访问的进行限制。 2.使用token令牌进行验证,通过后进行业务逻辑。 3.不对参数过渡暴露,传输的文本根据业务级别进行加密。 没有绝对的安全,题主可以看看微信开发者文档,看看他们的接口是怎么设计的 题主要看你如何定义接口安全了,我们可以看看新浪微博,每天无数的爬虫在爬,他的接口安全么?一般意义上安全指的是传输过程中不被盗取,走https基本可以做到,至于有人拿到api文档一类的东西(app被反向),那怎么样也防不了 所以如同楼上 1.对IP进行过滤,频繁访问的进行限制。 2.使用token令牌进行验证,通过后进行业务逻辑。 3.不对参数过渡暴露,传输的文本根据业务级别进行加密。 没有绝对的安全,题主可以看看微信开发者文档,看看他们的接口是怎么设计的 ================================================ FILE: docs/android/Android-Interview/HR/README.md ================================================ ## 人事面 - [人事面试宝典一之自我介绍](人事面试宝典一之自我介绍.md) - [人事面试宝典二之离职](人事面试宝典二之离职.md) - [人事面试宝典](人事面试宝典.md) ================================================ FILE: docs/android/Android-Interview/HR/人事面试宝典.md ================================================ ## [面试题-史上最全人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### 1. 请你自我介绍一下你自己? 回答提示:一般人回答这个问题过于平常,只说姓名、年龄、爱好、工作经验,这些在简历上都有,其实,企业最希望知道的是求职者能否胜任工作,包括:最强的技能、最深入研究的知识领域、个性中最积极的部分、做过的最成功的事,主要的成就等,这些都可以和学习无关,也可以和学习有关,但要突出积极的个性和做事的能力,说得合情合理企业才会相信。企业很重视一个人的礼貌,求职者要尊重考官,在回答每个问题之后都说一句“谢谢”。企业喜欢有礼貌的求职者。 ### 2. 你觉得你个性上最大的优点是什么? 回答提示:沉着冷静、条理清楚、立场坚定、顽强向上。 乐于助人和关心他人、适应能力和幽默感、乐观和友爱。 ### 3. 说说你最大的缺点? 回答提示:这个问题企业问的概率很大,通常不希望听到直接回答的缺点是什么等,如果求职者说自己小心眼、爱忌妒人、非常懒、脾气大、工作效率低,企业肯定不会录用你。绝对不要自作聪明地回答“我最大的缺点是过于追求完美”,有的人以为这样回答会显得自己比较出色,但事实上,他已经岌芨可危了。企业喜欢求职者从自己的优点说起,中间加一些小缺点,最后再把问题转回到优点上,突出优点的部分。企业喜欢聪明的求职者。 ### 4. 你对加班的看法? 回答提示:实际上好多公司问这个问题,并不证明一定要加班。 只是想测试你是否愿意为公司奉献。 回答样本:如果是工作需要我会义不容辞加班。我现在单身,没有任何家庭负担,可以全身心的投入工作。但同时,我也会提高工作效率,减少不必要的加班 ### 5. 你对薪资的要求? 回 答提示:如果你对薪酬的要求太低,那显然贬低自己的能力;如果你对薪酬的要求太高,那又会显得你分量过重,公司受用不起。一些雇主通常都事先对求聘的职位定下开支预算,因而他们第一次提出的价钱往往是他们所能给予的最高价钱。他们问你只不过想证实一下这笔钱是否足以引起你对该工作的兴趣。 回答样本一:“我对工资没有硬性要求。我相信贵公司在处理我的问题上会友善合理。我注重的是找对工作机会,所以只要条件公平,我则不会计较太多 回答样本二:我受过系统的软件编程的训练,不需要进行大量的培训。而且我本人也对编程特别感兴趣。因此,我希望公司能根据我的情况和市场标准的水平,给我合理的薪水。 回答样本三:如果你必须自己说出具体数目,请不要说一个宽泛的范围,那样你将只能得到最低限度的数字。最好给出一个具体的数字,这样表明你已经对当今的人才市场作了调查,知道像自己这样学历的雇员有什么样的价值。 ### 6. 在五年的时间内,你的职业规划? 回答提示:这是每一个应聘者都不希望被问到的问题,但是几乎每个人都会被问到。比较多的答案是“管理者”。但是近几年来,许多公司都已经建立了专门的技术途径。这些工作地位往往被称作“顾问”、“参议技师”或“高级软件工程师”等等。当然,说出其他一些你感兴趣的职位也是可以的,比如产品销售部经理,生产部经理等一些与你的专业有相关背景的工作。要知道,考官总是喜欢有进取心的应聘者,此时如果说“不知道”,或许就会使你丧失一个好机会。最普通的回答应该是“我准备在技术领域有所作为”或“我希望能按照公司的管理思路发展”。 ### 7. 你朋友对你的评价? 回答提示: 想从侧面了解一下你的性格及与人相处的问题。 回答样本:“我的朋友都说我是一个可以信赖的人。因为,我一旦答应别人的事情,就一定会做到。如果我做不到,我就不会轻易许诺。 回答样本:”我觉的我是一个比较随和的人,与不同的人都可以友好相处。在我与人相处时,我总是能站在别人的角度考虑问题“ ### 8. 你还有什么问题要问吗? 回答提示:企业的这个问题看上去可有可无,其实很关键,企业不喜欢说“没有问题”的人,因为其很注重员工的个性和创新能力。企业不喜欢求职者问个人福利之类的问题,如果有人这样问:贵公司对新入公司的员工有没有什么培训项目,我可以参加吗?或者说贵公司的晋升机制是什么样的?企业将很欢迎,因为体现出你对学习的热情和对公司的忠诚度以及你的上进心。 ### 9. 如果通过这次面试我们单位录用了你,但工作一段时间却发现你根本不适合这个职位,你怎么办? 回答提示:一段时间发现工作不适合我,有两种情况: 1、如果你确实热爱这个职业,那你就要不断学习,虚心向领导和同事学习业务知识和处事经验,了解这个职业的精神内涵和职业要求,力争减少差距; 2、你觉得这个职业可有可无,那还是趁早换个职业,去发现适合你的,你热爱的职业,那样你的发展前途也会大点,对单位和个人都有好处。 ### 10. 在完成某项工作时,你认为领导要求的方式不是最好的,自己还有更好的方法,你应该怎么做? 回答提示: - 原则上我会尊重和服从领导的工作安排;同时私底下找机会以请教的口吻,婉转地表达自己的想法,看看领导是否能改变想法; - 如果领导没有采纳我的建议,我也同样会按领导的要求认真地去完成这项工作; - 还有一种情况,假如领导要求的方式违背原则,我会坚决提出反对意见;如领导仍固执己见,我会毫不犹豫地再向上级领导反映。 ### 11. 如果你的工作出现失误,给本公司造成经济损失,你认为该怎么办? 回答提示: - 我本意是为公司努力工作,如果造成经济损失,我认为首要的问题是想方设法去弥补或挽回经济损失。如果我无能力负责,希望单位帮助解决; - 是责任问题。分清责任,各负其责,如果是我的责任,我甘愿受罚;如果是一个我负责的团队中别人的失误,也不能幸灾乐祸,作为一个团队,需要互相提携共同完成工作,安慰同事并且帮助同事查找原因总结经验。 - 总结经验教训,一个人的一生不可能不犯错误,重要的是能从自己的或者是别人的错误中吸取经验教训,并在今后的工作中避免发生同类的错误。检讨自己的工作方法、分析问题的深度和力度是否不够,以致出现了本可以避免的错误 ### 12. 如果你在这次考试中没有被录用,你怎么打算? 回答提示:现在的社会是一个竞争的社会,从这次面试中也可看出这一点,有竞争就必然有优劣,有成功必定就会有失败.往往成功的背后有许多的困难和挫折,如果这次失败了也仅仅是一次而已,只有经过经验经历的积累才能塑造出一个完全的成功者。我会从以下几个方面来正确看待这次失败. 第一、要敢于面对,面对这次失败不气馁,接受已经失去了这次机会就不会回头这个现实,从心理意志和精神上体现出对这次失败的抵抗力。要有自信,相信自己经历了这次之后经过努力一定能行.能够超越自我. 第二、善于反思,对于这次面试经验要认真总结,思考剖析,能够从自身的角度找差距。正确对待自己,实事求是地评价自己,辩证的看待自己的长短得失,做一个明白人. 第三、走出阴影,要克服这一次失败带给自己的心理压力,时刻牢记自己弱点,防患于未然,加强学习,提高自身素质. 第四、认真工作,回到原单位岗位上后,要实实在在、踏踏实实地工作,三十六行,行行出状元,争取在本岗位上做出一定的成绩. 第五、再接再厉,成为软件工程师或网络工程师一直是我的梦想,以后如果有机会我仍然后再次参加竞争. ### 13. 如果你做的一项工作受到上级领导的表扬,但你主管领导却说是他做的,你该怎样? 回答提示:我首先不会找那位上级领导说明这件事,我会主动找我的主管领导来沟通,因为沟通是解决人际关系的最好办法,但结果会有两种:1.我的主管领导认识到自己的错误,我想我会视具体情况决定是否原谅他;2.他更加变本加厉的来威胁我,那我会毫不犹豫地找我的上级领导反映此事,因为他这样做会造成负面影响,对今后的工作不利。 ### 14. 谈谈你对跳槽的看法? 回答提示: - 正常的"跳槽"能促进人才合理流动,应该支持; - 频繁的跳槽对单位和个人双方都不利,应该反对。 ### 15. 工作中你难以和同事、上司相处,你该怎么办? 回答提示: - 我会服从领导的指挥,配合同事的工作。 - 我会从自身找原因,仔细分析是不是自己工作做得不好让领导不满意,同事看不惯。还要看看是不是为人处世方面做得不好。如果是这样的话 我会努力改正。 - 如果我找不到原因,我会找机会跟他们沟通,请他们指出我的不足。有问题就及时改正。 - 作为优秀的员工,应该时刻以大局为重,即使在一段时间内,领导和同事对我不理解,我也会做好本职工作,虚心向他们学习,我相信,他们会看见我在努力,总有一天会对我微笑的! ### 16. 假设你在某单位工作,成绩比较突出,得到领导的肯定。但同时你发现同事们越来越孤立你,你怎么看这个问题?你准备怎么办? 回答提示: - 成绩比较突出,得到领导的肯定是件好事情,以后更加努力 - 检讨一下自己是不是对工作的热心度超过同事间交往的热心了,加强同事间的交往及共同的兴趣爱好。 - 工作中,切勿伤害别人的自尊心 - 不再领导前拨弄是非 - 乐于助人对面 ### 17. 你最近是否参加了培训课程?谈谈培训课程的内容。是公司资助还是自费参加? 回答提示:可以回答一些线上的自我提升的平台,极客学院,慕课网等. ### 18. 你对于我们公司了解多少? 回答提示:在去公司面试前上网查一下该公司主营业务。如回答:贵公司有意改变策略,加强与国外大厂的OEM合作,自有品牌的部分则透过海外经销商。 ### 19. 请说出你选择这份工作的动机? 回答提示:这是想知道面试者对这份工作的热忱及理解度,并筛选因一时兴起而来应试的人,如果是无经验者,可以强调“就算职种不同,也希望有机会发挥之前的经验”。 ### 20. 你最擅长的技术方向是什么? 回答提示:说和你要应聘的职位相关的课程,表现一下自己的热诚没有什么坏处。 ### 21. 你能为我们公司带来什么呢? 回答提示: 其实我们为公司所做的,也就是为自己所做的,你在为公司不断付出,取得业绩的同时,也是实现了自己价值,自我成为,所以在回答“你能为公司带来什么”时,不妨站在以上角度 ### 22. 最能概括你自己的三个词是什么? 回答提示: 我经常用的三个词是:适应能力强,有责任心和做事有始终,结合具体例子向主考官解释, ### 23. 你的业余爱好是什么? 回答提示:找一些富于团体合作精神的,这里有一个真实的故事:有人被否决掉,因为他的爱好是深海潜水。主考官说:因为这是一项单人活动,我不敢肯定他能否适应团体工作。 ### 24. 作为被面试者给我打一下分 回答提示:试着列出四个优点和一个非常非常非常小的缺点,(可以抱怨一下设施,没有明确责任人的缺点是不会有人介意的)。 ### 25. 你怎么理解你应聘的职位? 回答提示:把岗位职责和任务及工作态度阐述一下 ### 26. 喜欢这份工作的哪一点? 回 答提示:相信其实大家心中一定都有答案了吧!每个人的价值观不同,自然评断的标准也会不同,但是,在回答面试官这个问题时可不能太直接就把自己心理的话说出来,尤其是薪资方面的问题,不过一些无伤大雅的回答是不错的考虑,如交通方便,工作性质及内容颇能符合自己的兴趣等等都是不错的答案,不过如果这时自己能仔细思考出这份工作的与众不同之处,相信在面试上会大大加分。 ### 27. 为什么要离职? 回答提示: 回答这个问题时一定要小心,就算在前一个工作受到再大的委屈,对公司有多少的怨言,都千万不要表现出来,尤其要避免对公司本身主管的批评,避免面试官的负面情绪及印象;建议此时最好的回答方式是将问题归咎在自己身上,例如觉得工作没有学习发展的空间,自己想在面试工作的相关产业中多加学习,或是前一份工作与自己的生涯规划不合等等,回答的答案最好是积极正面的。 我希望能获得一份更好的工作,如果机会来临,我会抓住;我觉得目前的工作,已经达到顶峰,即沒有升迁机会。 ### 28. 说说你对行业、技术发展趋势的看法? 回答提示:企业对这个问题很感兴趣,只有有备而来的求职者能够过关。求职者可以直接在网上查找对你所申请的行业部门的信息,只有深入了解才能产生独特的见解。企业认为最聪明的求职者是对所面试的公司预先了解很多,包括公司各个部门,发展情况,在面试回答问题的时候可以提到所了解的情况,企业欢迎进入企业的人是“知己”,而不是“盲人”。 ### 29. 对工作的期望与目标何在? 回答提示:这是面试者用来评断求职者是否对自己有一定程度的期望、对这份工作是否了解的问题。对于工作有确实学习目标的人通常学习较快,对于新工作自然较容易进入状况,这时建议你,最好针对工作的性质找出一个确实的答案,如业务员的工作可以这样回答:“我的目标是能成为一个超级业务员,将公司的产品广泛的推销出去,达到最好的业绩成效;为了达到这个目标,我一定会努力学习,而我相信以我认真负责的态度,一定可以达到这个目标。”其他类的工作也可以比照这个方式来回答,只要在目标方面稍微修改一下就可以了。 ### 30. 说说你的家庭。 回答提示:企业面试时询问家庭问题不是非要知道求职者家庭的情况,探究隐私,企业不喜欢探究个人隐私,而是要了解家庭背景对求职者的塑造和影响。企业希望听到的重点也在于家庭对求职者的积极影响。企业最喜欢听到的是:我很爱我的家庭!我的家庭一向很和睦,虽然我的父亲和母亲都是普通人,但是从小,我就看到我父亲起早贪黑,每天工作特别勤劳,他的行动无形中培养了我认真负责的态度和勤劳的精神。我母亲为人善良,对人热情,特别乐于助人,所以在单位人缘很好,她的一言一行也一直在教导我做人的道理。企业相信,和睦的家庭关系对一个人的成长有潜移默化的影响。 ### 31. 就你申请的这个职位,你认为你还欠缺什么? 回答提示:企业喜欢问求职者弱点,但精明的求职者一般不直接回答。他们希望看到这样的求职者:继续重复自己的优势,然后说:“对于这个职位和我的能力来说,我相信自己是可以胜任的,只是缺乏经验,这个问题我想我可以进入公司以后以最短的时间来解决,我的学习能力很强,我相信可以很快融入公司的企业文化,进入工作状态。”企业喜欢能够巧妙地躲过难题的求职者。 ### 32. 你欣赏哪种性格的人? 回答提示:诚实、不死板而且容易相处的人、有"实际行动"的人。 ### 33. 你通常如何处理別人的批评? 回答提示:①沈默是金。不必说什么,否则情况更糟,不过我会接受建设性的批评;②我会等大家冷靜下来再讨论。 ### 34. 你怎样对待自己的失敗? 回答提示:我们大家生来都不是十全十美的,我相信我有第二个机会改正我的错误。 ### 35. 什么会让你有成就感? 回答提示:为贵公司竭力效劳;尽我所能,完成一个项目 ### 36. 眼下你生活中最重要的是什么? 回答提示:对我来说,能在这个领域找到工作是最重要的;望能在贵公司任职对我说最重要。 ### 37. 你为什么愿意到我们公司来工作? 回答提示:对于这个问题,你要格外小心,如果你已经对该单位作了研究,你可以回答一些详细的原因,像“公司本身的高技术开发环境很吸引我。”,“我同公司出生在同样的时代,我希望能够进入一家与我共同成长的公司。”“你们公司一直都稳定发展,在近几年来在市场上很有竞争力。”或者“我认为贵公司能够给我提供一个与众不同的发展道路。”这都显示出你已经做了一些调查,也说明你对自己的未来有了较为具体的远景规划。 ### 38. 你和别人发生过争执吗?你是怎样解决的? 回答提示:这是面试中最险恶的问题。其实是考官布下的一个陷阱。千万不要说任何人的过错。应知成功解决矛盾是一个协作团体中成员所必备的能力。假如你工作在一个服务行业,这个问题简直成了最重要的一个环节。你是否能获得这份工作,将取决于这个问题的回答。考官希望看到你是成熟且乐于奉献的。他们通过这个问题了解你的成熟度和处世能力。在没有外界干涉的情况下,通过妥协的方式来解决才是正确答案。 ### 39. 问题:你做过的哪件事最令自己感到骄傲? 回答提示:这是考官给你的一个机会,让你展示自己把握命运的能力。这会体现你潜在的领导能力以及你被提升的可能性。假如你应聘于一个服务性质的单位,你很可能会被邀请去午餐。记住:你的前途取决于你的知识、你的社交能力和综合表现。 ### 40. 你新到一个部门,一天一个客户来找你解决问题,你努力想让他满意,可是始终达不到群众得满意,他投诉你们部门工作效率低,你这个时候怎么作? 回 答提示: (1)首先,我会保持冷静。作为一名工作人员,在工作中遇到各种各样的问题是正常的,关键是如何认识它,积极应对,妥善处理。 (2)其次,我会反思一下客户不满意的原因。一是看是否是自己在解决问题上的确有考虑的不周到的地方,二是看是否是客户不太了解相关的服务规定而提出超出规定的要求,三是看是否是客户了解相关的规定,但是提出的要求不合理。 (3)再次,根据原因采取相对的对策。如果是自己确有不周到的地方,按照服务规定作出合理的安排,并向客户作出解释;如果是客户不太了解政策规定而造成的误解,我会向他作出进一步的解释,消除他的误会;如果是客户提出的要求不符合政策规定,我会明确地向他指出。 (4)再次,我会把整个事情的处理情况向领导作出说明,希望得到他的理解和支持。(5)我不会因为客户投诉了我而丧失工作的热情和积极性,而会一如既往地牢记为客户服务的宗旨,争取早日做一名领导信任、公司放心、客户满意的职员。 ### 41. 对这项工作,你有哪些可预见的困难? 回答提示: 不宜直接说出具体的困难,否则可能令对方怀疑应聘者不行; 可以尝试迂回战术,说出应聘者对困难所持有的态度——“工作中出现一些困难是正常的,也是难免的,但是只要有坚忍不拔的毅力、良好的合作精神以及事前周密而充分的准备,任何困难都是可以克服。” 分析:一般问这个问题,面试者的希望就比较大了,因为已经在谈工作细节。但常规思路中的回答,又被面试官“骗”了。当面试官询问这个问题的时候,有两个目的。第一,看看应聘者是不是在行,说出的困难是不是在这个职位中一般都不可避免的问题。第二,是想看一下应聘者解决困难的手法对不对,及公司能否提供这样的资源。而不是想了解应聘者对困难的态度。 ### 42. 如果我录用你,你将怎样开展工作?” 回答提示: 如果应聘者对于应聘的职位缺乏足够的了解,最好不要直接说出自己开展工作的具体办法; 可以尝试采用迂回战术来回答,如“首先听取领导的指示和要求,然后就有关情况进行了解和熟悉,接下来制定一份近期的工作计划并报领导批准,最后根据计划开展工作。” 分析:这个问题的主要目的也是了解应聘者的工作能力和计划性、条理性,而且重点想要知道细节。如果向思路中所讲的迂回战术,面试官会认为回避问题,如果引导了几次仍然是回避的话。此人绝对不会录用了。 ### 43. 你希望与什么样的上级共事? 回答提示: - 通过应聘者对上级的“希望”可以判断出应聘者对自我要求的意识,这既上一个陷阱,又是一次机会; - 最好回避对上级具体的希望,多谈对自己的要求; - 如“做为刚步入社会的新人,我应该多要求自己尽快熟悉环境、适应环境,而不应该对环境提出什么要求,只要能发挥我的专长就可以了 分析:这个问题比较好的回答是,希望我的上级能够在工作中对我多指导,对我工作中的错误能够立即指出。总之,从上级指导这个方面谈,不会有大的纰漏。 ### 44. 在完成某项工作时,你认为领导要求的方式不是最好的,自己还有更好的方法,你应该怎么做? 回答提示: - 原则上我会尊重和服从领导的工作安排;同时私底下找机会以请教的口吻,婉转地表达自己的想法,看看领导是否能改变想法; - 如果领导没有采纳我的建议,我也同样会按领导的要求认真地去完成这项工作; - 还有一种情况,假如领导要求的方式违背原则,我会坚决提出反对意见;如领导仍固执己见,我会毫不犹豫地再向上级领导反映。 ### 45. 与上级意见不一是,你将怎么办? 回答提示: - 一般可以这样回答“我会给上级以必要的解释和提醒,在这种情况下,我会服从上级的意见。” - 如果面试你的是总经理,而你所应聘的职位另有一位经理,且这位经理当时不在场,可以这样回答:“对于非原则性问题,我会服从上级的意见,对于涉及公司利益的重大问题,我希望能向更高层领导反映。” 分析:这个问题的标准答案是思路1,如果用2的回答,必死无疑。你没有摸清楚改公司的内部情况,先想打小报告,这样的人没有人敢要。 ### 46. 你工作经验欠缺,如何能胜任这项工作? 常规思路: - 如果招聘单位对应届毕业生的应聘者提出这个问题,说明招聘公司并不真正在乎“经验”,关键看应聘者怎样回答; - 对这个问题的回答最好要体现出应聘者的诚恳、机智、果敢及敬业; - 如“作为应届毕业生,在工作经验方面的确会有所欠缺,因此在读书期间我一直利用各种机会在这个行业里做兼职。我也发现,实际工作远比书本知识丰富、复杂。但我有较强的责任心、适应能力和学习能力,而且比较勤奋,所以在兼职中均能圆满完成各项工作,从中获取的经验也令我受益非浅。请贵公司放心,学校所学及兼职的工作经验使我一定能胜任这个职位。” 点评:这个问题思路中的答案尚可。突出自己的吃苦能力和适应性以及学习能力(不是学习成绩)为好。 ### 47. 您在前一家公司的离职原因是什么? 回答提示: - 最重要的是:应聘者要使找招聘单位相信,应聘者在过往的单位的“离职原因”在此家招聘单位里不存在 - 避免把“离职原因”说得太详细、太具体 - 不能掺杂主观的负面感受,如“太辛苦”、“人际关系复杂”、“管理太混乱”、“公司不重视人才”、“公司排斥我们某某的员工”等 - 但也不能躲闪、回避,如“想换换环境”、“个人原因”等 - 不能涉及自己负面的人格特征,如不诚实、懒惰、缺乏责任感、不随和等 - 尽量使解释的理由为应聘者个人形象添彩;⑦相关例子:如“我离职是因为这家公司倒闭;我在公司工作了三年多,有较深的感情;从去年始,由于市场形势突变,公司的局面急转直下;到眼下这一步我觉得很遗憾,但还要面对显示,重新寻找能发挥我能力的舞台。”同一个面试问题并非只有一个答案,而同一个答案并不是在任何面试场合都有效,关键在应聘者掌握了规律后,对面试的具体情况进行把握,有意识地揣摩面试官提出问题的心理背景,然后投其所好 分析:除非是薪资太低,或者是最初的工作,否则不要用薪资作为理由。“求发展”也被考官听得太多,离职理由要根据每个人的真实离职理由来设计,但是在回答时一定要表现得真诚。实在想不出来的时候,家在外地可以说是因为家中有事,须请假几个月,公司又不可能准假,所以辞职。这个答案一般面试官还能接受。 ### 48. 你工作经验欠缺,如何能胜任这项工作? 回答提示: - 如果招聘单位对应届毕业生的应聘者提出这个问题,说明招聘公司并不真正在乎“经验”,关键看应聘者怎样回答; - 对这个问题的回答最好要体现出应聘者的诚恳、机智、果敢及敬业; - 如“作为应届毕业生,在工作经验方面的确会有所欠缺,因此在读书期间我一直利用各种机会在这个行业里做兼职。我也发现,实际工作远比书本知识丰富、复杂。但我有较强的责任心、适应能力和学习能力,而且比较勤奋,所以在兼职中均能圆满完成各项工作,从中获取的经验也令我受益非浅。请贵公司放心,学校所学及兼职的工作经验使我一定能胜任这个职位。” 分析:这个问题思路中的答案尚可。突出自己的吃苦能力和适应性以及学习能力(不是学习成绩)为好。 ### 49. 为了做好你工作份外之事,你该怎样获得他人的支持和帮助? 回答提示:每个公司都在不断变化发展的过程中;你当然希望你的员工也是这样。你希望得到那些希望并欢迎变化的人,因为这些人明白,为了公司的发展,变化是公司日常生活中重要组成部分。这样的员工往往很容易适应公司的变化,并会对变化做出积极的响应。此外,他们遇到矛盾和问题时,也能泰然处之。下面的问题能够考核应聘者这方面的能力。 据说有人能从容避免正面冲突。请讲一下你在这方面的经验和技巧。 有些时候,我们得和我们不喜欢的人在一起共事。说说你曾经克服了性格方面的冲突而取得预期工作效果的经历。 ### 50. 如果你在这次面试中没有被录用,你怎么打算? 回答提示:现在的社会是一个竞争的社会,从这次面试中也可看出这一点,有竞争就必然有优劣,有成功必定就会有失败.往往成功的背后有许多的困难和挫折,如果这次失败了也仅仅是一次而已,只有经过经验经历的积累才能塑造出一个完全的成功者。我会从以下几个方面来正确看待这次失败. 第一、要敢于面对,面对这次失败不气馁,接受已经失去了这次机会就不会回头这个现实,从心理意志和精神上体现出对这次失败的抵抗力。要有自信,相信自己经历了这次之后经过努力一定能行.能够超越自我. 第二、善于反思,对于这次面试经验要认真总结,思考剖析,能够从自身的角度找差距。正确对待自己,实事求是地评价自己,辩证的看待自己的长短得失,做一个明白人. 第三、走出阴影,要克服这一次失败带给自己的心理压力,时刻牢记自己弱点,防患于未然,加强学习,提高自身素质. 第四、认真工作,回到原单位岗位上后,要实实在在、踏踏实实地工作,三十六行,行行出状元,争取在本岗位上做出一定的成绩. 第五、再接再厉,成为国家公务员一直是我的梦想,以后如果有机会我仍然后再次参加竞争. ### 51. 假如你晚上要去送一个出国的同学去机场,可单位临时有事非你办不可,你怎么办? 回答提示:我觉得工作是第一位的,但朋友间的情谊也是不能偏废的。这个问题我觉得要按照当时具体的情况来决定。 - 如果我的朋友晚上9点中的飞机,而我的 加班八点就能够完成的话,那就最理想了,干完工作去机场,皆大欢喜。 - 如果说工作不是很紧急,加班仅仅是为了明天上班的时候能把报告交到办公室,那完全可以跟领导打声招呼,先去机场然后回来加班,晚点睡就是了。 - 如果工作很紧急,两者不可能兼顾的情况下,我觉得可以由两种选择。 - 如果不是全单位都加班的话,是不是可以要其他同事来代替以下工作,自己去机场,哪怕就是代替你离开的那一会儿。 - 如果连这一点都做不到的话,那只好忠义不能两全了,打电话给朋友解释一下,小心他会理解,毕竟工作做完了就完了,朋友还是可以再见面的。 ### 52. 如果通过这次面试我们单位录用了你,但工作一段时间却发现你根本不适合这个职位,你怎么办? 回答提示:一段时间发现工作不适合我,有两种情况: 1、如果你确实热爱这个职业,那你就要不断学习,虚心向领导和同事学习业务知识和处事经验,了解这个职业的精神内涵和职业要求,力争减少差距; 2、你觉得这个职业可有可无,那还是趁早换个职业,去发现适合你的,你热爱的职业,那样你的发展前途也会大点,对单位和个人都有好处。 ### 53. 你做过的哪件事最令自己感到骄傲? 回答提示:这是考官给你的一个机会,让你展示自己把握命运的能力。这会体现你潜在的领导能力以及你被提升的可能性。假如你应聘于一个服务性质的单位,你很可能会被邀请去午餐。记住:你的前途取决于你的知识、你的社交能力和综合表现。 ### 54. 谈谈你过去做过的成功案例 回答提示:举一个你最有把握的例子,把来龙去脉说清楚,而不要说了很多却没有重点。切忌夸大其词,把别人的功劳到说成自己的,很多主管为了确保要用的人是最适合的,会打电话向你的前一个主管征询对你的看法及意见,所以如果说谎,是很容易穿梆的。 ### 55. 谈谈你过去的工作经验中,最令你挫折的事情 回答提示:曾经接触过一个客户,原本就有耳闻他们以挑剔出名,所以事前的准备功夫做得十分充分,也投入了相当多的时间与精力,最后客户虽然并没有照单全收,但是接受的程度已经出乎我们意料之外了。原以为从此可以合作愉快,却得知客户最后因为预算关系选择了另一家代理商,之前的努力因而付诸流水。尽管如此,我还是从这次的经验学到很多,如对该产业的了解,整个team的默契也更好了。 分析:借此了解你对挫折的容忍度及调解方式。 ### 56. 如何安排自己的时间?会不会排斥加班? 回答提示:基本上,如果上班工作有效率,工作量合理的话,应该不太需要加班。可是我也知道有时候很难避免加班,加上现在工作都采用责任制,所以我会调配自己的时间,全力配合。 分析:虽然不会有人心甘情愿的加班,但依旧要表现出高配合度的诚意。 ### 57. 为什么我们要在众多的面试者中选择你? 回答提示:根据我对贵公司的了解,以及我在这份工作上所累积的专业、经验及人脉,相信正是贵公司所找寻的人才。而我在工作态度、EQ上,也有圆融、成熟的一面,和主管、同事都能合作愉快。 分析:别过度吹嘘自己的能力,或信口开河地乱开支票,例如一定会为该公司带来多少钱的业务等,这样很容易给人一种爱说大话、不切实际的感觉。 ### 58. 对这个职务的期许? 回答提示:希望能借此发挥我的所学及专长,同时也吸收贵公司在这方面的经验,就公司、我个人而言,缔造“双赢”的局面。 分析:回答前不妨先询问该公司对这项职务的责任认定及归属,因为每一家公司的状况不尽相同。以免说了一堆理想抱负却发现牛头不对马嘴。 ### 59. 为什么选择这个职务? 回答提示::这一直是我的兴趣和专长,经过这几年的磨练,也累积了一定的经验及人脉,相信我一定能胜任这个职务的。 分析:适时举出过去的“丰功伟业”,表现出你对这份职务的熟稔度,但避免过于夸张的形容或流于炫耀。 ### 60. 为什么选择我们这家公司? 回答提示:曾经在报章杂志看过关于贵公司的报道,与自己所追求的理念有志一同。而贵公司在业界的成绩也是有目共睹的,而且对员工的教育训练、升迁等也都很有制度。 分析:去面试前先做功课,了解一下该公司的背景,让对方觉得你真的很有心想得到这份工作,而不只是探探路。 ### 61. 你认为你在学校属于好学生吗? 回答提示:企业的招聘者很精明,问这个问题可以试探出很多问题:如果求职者学习成绩好,就会说:“是的,我的成绩很好,所有的成绩都很优异。当然,判断一个学生是不是好学生有很多标准,在学校期间我认为成绩是重要的,其他方面包括思想道德、实践经验、团队精神、沟通能力也都是很重要的,我在这些方面也做得很好,应该说我是一个全面发展的学生。”如果求职者成绩不尽理想,便会说:“我认为是不是一个好学生的标准是多元化的,我的学习成绩还可以,在其他方面我的表现也很突出,比如我去很多地方实习过,我很喜欢在快节奏和压力下工作,我在学生会组织过 ××活动,锻炼了我的团队合作精神和组织能力。” 有经验的招聘者一听就会明白,企业喜欢诚实的求职者。 ### 62. 请谈谈如何适应办公室工作的新环境? 回答提示 - 办公室里每个人有各自的岗位与职责,不得擅离岗位。 - 根据领导指示和工作安排,制定工作计划,提前预备,并按计划完成。 - 多请示并及时汇报,遇到不明白的要虚心请教。 - 抓间隙时间,多学习,努力提高自己的政治素质和业务水平。 ### 63. 在工作中学习到了些什么? 回答提示:这是针对转职者提出的问题,建议此时可以配合面试工作的特点作为主要依据来回答,如业务工作需要与人沟通,便可举出之前工作与人沟通的例子,经历了哪些困难,学习到哪些经验,把握这些要点做陈述,就可以轻易过关了 ### 64. 有想过创业吗? 回答提示:这个问题可以显示你的冲劲,但如果你的回答是“有”的话,千万小心,下一个问题可能就是“那么为什么你不这样做呢?” ### 65. 最能概括你自己的三个词是什么? 回答提示:我经常用的三个词是:适应能力强,有责任心和做事有始终,结合具体例子向主考官解释,使他们觉得你具有发展潜力 ### 66. 你认为你在学校属于好学生吗? 回答提示:企业的招聘者很精明,问这个问题可以试探出很多问题:如果求职者学习成绩好,就会说:“是的,我的成绩很好,所有的成绩都很优异。当然,判断一个学生是不是好学生有很多标准,在学校期间我认为成绩是重要的,其他方面包括思想道德、实践经验、团队精神、沟通能力也都是很重要的,我在这些方面也做得很好,应该说我是一个全面发展的学生。”如果求职者成绩不尽理想,便会说:“我认为是不是一个好学生的标准是多元化的,我的学习成绩还可以,在其他方面我的表现也很突出,比如我去很多地方实习过,我很喜欢在快节奏和压力下工作,我在学生会组织过 ××活动,锻炼了我的团队合作精神和组织能力。” 有经验的招聘者一听就会明白,企业喜欢诚实的求职者。 ### 67. 除了本公司外,还应聘了哪些公司? 回答提示:很奇怪,这是相当多公司会问的问题,其用意是要概略知道应徵者的求职志向,所以这并非绝对是负面答案,就算不便说出公司名称,也应回答“销售同种产品的公司”,如果应聘的其他公司是不同业界,容易让人产生无法信任的感觉。 ### 68. 何时可以到职? 回答提示:大多数企业会关心就职时间,最好是回答\’如果被录用的话,到职日可按公司规定上班”,但如果还未辞去上一个工作、上班时间又太近,似乎有些强人所难,因为交接至少要一个月的时间,应进一步说明原因,录取公司应该会通融的 ### 69. 你并非毕业于名牌院校? 回答提示:是否毕业于名牌院校不重要,重要的是有能力完成您交给我的工作,我有什么什么项目经验,如何帮助项目经理解决了问题,不拉不拉。 ### 70. 你怎样看待学历和能力? 回答提示:学历我想只要是大学专科的学历,就表明觉得我具备了根本的学习能力。剩下的,你是学士也好,还是博士也好,对于这一点的讨论,不是看你学了多少知识,而是看你在这个领域上发挥了什么,也就是所说的能力问题。一个人工作能力的高低直接决定其职场命运,而学历的高低只是进入一个企业的敲门砖,如果贵公司把学历卡在博士上,我就无法进入贵公司,当然这不一定只是我个人的损失,如果一个专科生都能完成的工作,您又何必非要招聘一位博士生呢? - 欢迎关注微信公众号,长期推荐技术文章和技术视频 - 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) > 原文链接:马伟奇,http://www.jianshu.com/p/d61b553ff8c9 ================================================ FILE: docs/android/Android-Interview/HR/人事面试宝典一之自我介绍.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) 可别小瞧人事面试时的各个介绍环节 #### 1. 请你自我介绍一下你自己? 回答提示:一般人回答这个问题过于平常,只说姓名、年龄、爱好、工作经验,这些在简历上都有,其实,企业最希望知道的是求职者能否胜任工作,包括:最强的技能、最深入研究的知识领域、个性中最积极的部分、做过的最成功的事,主要的成就等,这些都可以和学习无关,也可以和学习有关,但要突出积极的个性和做事的能力,说得合情合理企业才会相信。企业很重视一个人的礼貌,求职者要尊重考官,在回答每个问题之后都说一句“谢谢”。企业喜欢有礼貌的求职者。 #### 2. 你觉得你个性上最大的优点是什么? 回答提示:沉着冷静、条理清楚、立场坚定、顽强向上。 乐于助人和关心他人、适应能力和幽默感、乐观和友爱。 #### 3. 说说你最大的缺点? 回答提示:这个问题企业问的概率很大,通常不希望听到直接回答的缺点是什么等,如果求职者说自己小心眼、爱忌妒人、非常懒、脾气大、工作效率低,企业肯定不会录用你。绝对不要自作聪明地回答“我最大的缺点是过于追求完美”,有的人以为这样回答会显得自己比较出色,但事实上,他已经岌芨可危了。企业喜欢求职者从自己的优点说起,中间加一些小缺点,最后再把问题转回到优点上,突出优点的部分。企业喜欢聪明的求职者。 #### 4. 你对加班的看法? 回答提示:实际上好多公司问这个问题,并不证明一定要加班。 只是想测试你是否愿意为公司奉献。 回答样本:如果是工作需要我会义不容辞加班。我现在单身,没有任何家庭负担,可以全身心的投入工作。但同时,我也会提高工作效率,减少不必要的加班 #### 5. 你对薪资的要求? 回 答提示:如果你对薪酬的要求太低,那显然贬低自己的能力;如果你对薪酬的要求太高,那又会显得你分量过重,公司受用不起。一些雇主通常都事先对求聘的职位定下开支预算,因而他们第一次提出的价钱往往是他们所能给予的最高价钱。他们问你只不过想证实一下这笔钱是否足以引起你对该工作的兴趣 。 回答样本一:“我对工资没有硬性要求。我相信贵公司在处理我的问题上会友善合理。我注重的是找对工作机会,所以只要条件公平,我则不会计较太多 回答样本二:我受过系统的软件编程的训练,不需要进行大量的培训。而且我本人也对编程特别感兴趣。因此,我希望公司能根据我的情况和市场标准的水平,给我合理的薪水。 回答样本三:如果你必须自己说出具体数目,请不要说一个宽泛的范围,那样你将只能得到最低限度的数字。最好给出一个具体的数字,这样表明你已经对当今的人才市场作了调查,知道像自己这样学历的雇员有什么样的价值。 #### 6. 除了本公司外,还应聘了哪些公司? 回答提示:很奇怪,这是相当多公司会问的问题,其用意是要概略知道应徵者的求职志向,所以这并非绝对是负面答案,就算不便说出公司名称,也应回答“销售同种产品的公司”,如果应聘的其他公司是不同业界,容易让人产生无法信任的感觉。 #### 7. 你还有什么问题要问吗? 回答提示:企业的这个问题看上去可有可无,其实很关键,企业不喜欢说“没有问题”的人,因为其很注重员工的个性和创新能力。企业不喜欢求职者问个人福利之类的问题,如果有人这样问:贵公司对新入公司的员工有没有什么培训项目,我可以参加吗?或者说贵公司的晋升机制是什么样的?企业将很欢迎,因为体现出你对学习的热情和对公司的忠诚度以及你的上进心。 > 总结一点:把命卖给公司,最后祝大家五一节快乐。 - 欢迎关注微信公众号,长期推荐技术文章和技术视频 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/HR/人事面试宝典二之离职.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) #### 为什么要离职? 回答提示: ①回答这个问题时一定要小心,就算在前一个工作受到再大的委屈,对公司有多少的怨言,都千万不要表现出来,尤其要避免对公司本身主管的批评,避免面试官的负面情绪及印象;建议此时最好的回答方式是将问题归咎在自己身上,例如觉得工作没有学习发展的空间,自己想在面试工作的相关产业中多加学习,或是前一份工作与自己的生涯规划不合等等,回答的答案最好是积极正面的。 ②我希望能获得一份更好的工作,如果机会来临,我会抓住;我觉得目前的工作,已经达到顶峰,即沒有升迁机会。 #### 您在前一家公司的离职原因是什么? 回答提示: ①最重要的是:应聘者要使找招聘单位相信,应聘者在过往的单位的“离职原因”在此家招聘单位里不存在; ②避免把“离职原因”说得太详细、太具体; ③不能掺杂主观的负面感受,如“太辛苦”、“人际关系复杂”、“管理太混乱”、“公司不重视人才”、“公司排斥我们某某的员工”等; ④但也不能躲闪、回避,如“想换换环境”、“个人原因”等; ⑤不能涉及自己负面的人格特征,如不诚实、懒惰、缺乏责任感、不随和等; ⑥尽量使解释的理由为应聘者个人形象添彩; ⑦相关例子:如“我离职是因为这家公司倒闭;我在公司工作了三年多,有较深的感情;从去年始,由于市场形势突变,公司的局面急转直下;到眼下这一步我觉得很遗憾,但还要面对显示,重新寻找能发挥我能力的舞台。”同一个面试问题并非只有一个答案,而同一个答案并不是在任何面试场合都有效,关键在应聘者掌握了规律后,对面试的具体情况进行把握,有意识地揣摩面试官提出问题的心理背景,然后投其所好。 分析:除非是薪资太低,或者是最初的工作,否则不要用薪资作为理由。“求发展”也被考官听得太多,离职理由要根据每个人的真实离职理由来设计,但是在回答时一定要表现得真诚。实在想不出来的时候,家在外地可以说是因为家中有事,须请假几个月,公司又不可能准假,所以辞职。这个答案一般面试官还能接受。 - 欢迎关注微信公众号,长期推荐技术文章和技术视频 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/Java/115个Java面试题及回答.md ================================================ ## 115个Java面试题及回答 简介 在本教程中,我们将讨论在Java面试中,用人单位用来测试应聘者Java以及面向对象的能力的面试题目.以下章节我们将按照以下结构讨论面试问题,面向对象编程及其特性,Java及其特性的一般问题,集合,垃圾回收,异常处理,Java applets,Swing,JDBC,RMI, Servlet 和 JSP. 来,我们一起出发吧。 [115个Java面试题及回答](https://github.com/snowdream/115-Java-Interview-Questions-and-Answers/tree/master/zh) ================================================ FILE: docs/android/Android-Interview/Java/66道经典的Java基础面试题集锦.md ================================================ ### 问题:如果main方法被声明为private会怎样? 答案:能正常编译,但运行的时候会提示”main方法不是public的”。 ### 问题:Java里的传引用和传值的区别是什么? 答案:传引用是指传递的是地址而不是值本身,传值则是传递值的一份拷贝。 ### 问题:如果要重写一个对象的equals方法,还要考虑什么? 答案:hashCode。 ### 问题:Java的”一次编写,处处运行”是如何实现的? 答案:Java程序会被编译成字节码组成的class文件,这些字节码可以运行在任何平台,因此Java是平台独立的。 ### 问题:说明一下public static void main(String args[])这段声明里每个关键字的作用 答案:public: main方法是Java程序运行时调用的第一个方法,因此它必须对Java环境可见。所以可见性设置为pulic. static: Java平台调用这个方法时不会创建这个类的一个实例,因此这个方法必须声明为static。 void: main方法没有返回值。 String是命令行传进参数的类型,args是指命令行传进的字符串数组。 ### 问题:==与equals的区别 答案:==比较两个对象在内存里是不是同一个对象,就是说在内存里的存储位置一致。两个String对象存储的值是一样的,但有可能在内存里存储在不同的地方 . ==比较的是引用而equals方法比较的是内容。public boolean equals(Object obj) 这个方法是由Object对象提供的,可以由子类进行重写。默认的实现只有当对象和自身进行比较时才会返回true,这个时候和==是等价的。String, BitSet, Date, 和File都对equals方法进行了重写,对两个String对象 而言,值相等意味着它们包含同样的字符序列。对于基本类型的包装类来说,值相等意味着对应的基本类型的值一样。 ``` public class EqualsTest { public static void main(String[] args) { String s1 = “abc”; String s2 = s1; String s5 = “abc”; String s3 = new String(”abc”); String s4 = new String(”abc”); System.out.println(”== comparison : ” + (s1 == s5)); System.out.println(”== comparison : ” + (s1 == s2)); System.out.println(”Using equals method : ” + s1.equals(s2)); System.out.println(”== comparison : ” + s3 == s4); System.out.println(”Using equals method : ” + s3.equals(s4)); } } ``` 结果: ``` == comparison : true == comparison : true Using equals method : true false Using equals method :true ``` ### 问题:如果去掉了main方法的static修饰符会怎样? 答案:程序能正常编译。运行时会抛NoSuchMethodError异常。 ### 问题:为什么oracle type4驱动被称作瘦驱动? 答案:oracle提供了一个type 4 JDBC驱动,被称为瘦驱动。这个驱动包含了一个oracle自己完全用Java实现的一个TCP/IP的Net8的实现,因此它是平台独立的,可以在运行时由浏览器下载,不依赖任何客户端 的oracle实现。客户端连接字符串用的是TCP/IP的地址端口,而不是数据库名的tnsname。 ### 问题:介绍一下finalize方法 答案: final: 常量声明。 finally: 处理异常。 finalize: 帮助进行垃圾回收。 接口里声明的变量默认是final的。final类无法继承,也就是没有子类。这么做是出于基础类型的安全考虑,比如String和Integer。这样也使得编译器进行一些优化,更容易保证线程的安全性。final方法无法重写。final变量的值不能改变。finalize()方法在一个对象被销毁和回收前会被调用。finally,通常用于异常处理,不管有没有异常被抛出都会执行到。比如,关闭连接通常放到finally块中完成。 ### 问题:什么是Java API? 答案:Java API是大量软件组件的集合,它们提供了大量有用的功能,比如GUI组件。 ### 问题:GregorianCalendar类是什么东西? 答案:GregorianCalendar提供了西方传统日历的支持。 ### 问题:ResourceBundle类是什么? 答案:ResourceBundle用来存储指定语言环境的资源,应用程序可以根据运行时的语言环境来加载这些资源,从而提供不同语言的展示。 ### 问题:为什么Java里没有全局变量? 答案:全局变量是全局可见的,Java不支持全局可见的变量,因为:全局变量破坏了引用透明性原则。全局变量导致了命名空间的冲突。 ### 问题:如何将String类型转化成Number类型? 答案:Integer类的valueOf方法可以将String转成Number。下面是代码示例: ``` String numString = “1000″; int id=Integer.valueOf(numString).intValue(); ``` ### 问题:SimpleTimeZone类是什么? 答案:SimpleTimeZone提供公历日期支持。 ### 问题:while循环和do循环有什么不同? 答案:while结构在循环的开始判断下一个迭代是否应该继续。do/while结构在循环的结尾来判断是否将继续下一轮迭代。do结构至少会执行一次循环体。 ### 问题:Locale类是什么? 答案:Locale类用来根据语言环境来动态调整程序的输出。 ### 问题:面向对象编程的原则是什么? 答案:主要有三点,多态,继承和封装。 ### 问题:介绍下继承的原则 答案:继承使得一个对象可以获取另一个对象的属性。使用继承可以让已经测试完备的功能得以复用,并且可以一次修改,所有继承的地方都同时生效。 ### 问题:什么是隐式的类型转化? 答案:隐式的类型转化就是简单的一个类型赋值给另一个类型,没有显式的告诉编译器发生了转化。并不是所有的类型都支持隐式的类型转化。 代码示例: ``` int i = 1000; long j = i; //Implicit casting ``` ### 问题:sizeof是Java的关键字吗? 答案:不是。 ### 问题:native方法是什么? 答案:native方法是非Java代码实现的方法。 ### 问题:在System.out.println()里面,System, out, println分别是什么? 答案:System是系统提供的预定义的final类,out是一个PrintStream对象,println是out对象里面一个重载的方法。 ### 问题:封装,继承和多态是什么? 答案:简单来说,多态是指一个名字多种实现。多态使得一个实体通过一个通用的方式来实现不同的操作。具体的操作是由实际的实现来决定的。 多态在Java里有三种表现方式:方法重载通过继承实现方法重写通过Java接口进行方法重写。 ### 问题:显式的类型转化是什么? 答案:显式的类型转化是明确告诉了编译器来进行对象的转化。 代码示例: ``` long i = 700.20; int j = (int) i; //Explicit casting ``` ### 问题:什么是Java虚拟机? 答案:Java虚拟机是能移植到不同硬件平台上的软件系统。 ### 问题:类型向下转换是什么? 答案:向下转换是指由一个通用类型转换成一个具体的类型,在继承结构上向下进行。 ### 问题:Java的访问修饰符是什么? 答案:访问权限修饰符是表明类成员的访问权限类型的关键字。使用这些关键字来限定程序的方法或者变量的访问权限。它们包含: public: 所有类都可以访问 protected: 同一个包内以及所有子类都可以访问 private: 只有归属的类才能访问默认: 归属类及相同包下的子类可以访问 ### 问题:所有类的父类是什么? 答案:Object. ### 问题:Java的基本类型有哪些? 答案:byte,char, short, int, long, float, double, boolean。 ### 问题:静态类型有什么特点? 答案:静态变量是和类绑定到一起的,而不是类的实例对象。每一个实例对象都共享同样一份静态变量。也就是说,一个类的静态变量只有一份,不管它有多少个对象。类变量或者说静态变量是通过static这个关键字来声明的。类变量通常被用作常量。静态变量通常通过类名字来进行访问。当程序运行的时候这个变量就会创建直到程序结束后才会被销毁。类变量的作用域和实例变量是一样的。它的初始值和成员变量也是一样的,当变量没被初始化的时候根据它的数据类型,会有一个默认值。类似的,静态方法是属于类的方法,而不是类对象,它的调用并不作用于类对象,也不需要创建任何的类实例。静态方法本身就是final的,因为重写只会发生在类实例上,静态方法是和类绑定在一起的,不是对象。父类的静态方法会被子类的静态方法屏蔽,只要原来方法没有声明为final。非静态方法不能重写静态方法,也就是说,你不能在子类中把一个静态方法改成实例方法。 非静态变量在每一个对象实例上都有单独的一份值。 ### 问题:&操作符和&&操作符有什么区别? 答案:当一个&表达式在求值的时候,两个操作数都会被求值,&&更像是一个操作符的快捷方式。当一个&&表达式求值的时候,先计算第一个操作数,如果它返回true才会计算第二个操作数。如果第一个操作数取值为fale,第二个操作数就不会被求值。 ### 问题:Java是如何处理整型的溢出和下溢的? 答案:Java根据类型的大小,将计算结果中的对应低阶字节存储到对应的值里面。 ### 问题:public static void写成static public void会怎样? 答案:程序正常编译及运行。 ### 问题,声明变量和定义变量有什么不同? 答案:声明变量我们只提供变量的类型和名字,并没有进行初始化。定义包括声明和初始化两个阶段String s;只是变量声明,String s = new String(“bob”); 或者String s = “bob”;是变量定义。 ### 问题:Java支持哪种参数传递类型? 答案:Java参数都是进行传值。对于对象而言,传递的值是对象的引用,也就是说原始引用和参数引用的那个拷贝,都是指向同一个对象。 ### 问题:对象封装的原则是什么? 答案:封装是将数据及操作数据的代码绑定到一个独立的单元。这样保障了数据的安全,防止外部代码的错误使用。对象允许程序和数据进行封装,以减少潜在的干涉。对封装的另一个理解是作为数据及代码的保护层,防止保护层外代码的随意访问。 ### 问题:你怎么理解变量? 答案:变量是一块命名的内存区域,以便程序进行访问。变量用来存储数据,随着程序的执行,存储的数据也可能跟着改变。 ### 问题:数值提升是什么? 答案:数值提升是指数据从一个较小的数据类型转换成为一个更大的数据类型,以便进行整型或者浮点型运算。在数值提升的过程中,byte,char,short值会被转化成int类型。需要的时候int类型也可能被提升成long。long和float则有可能会被转换成double类型。 ### 问题:Java的类型转化是什么? 答案:从一个数据类型转换成另一个数据类型叫做类型转换。Java有两种类型转换的方式,一个是显式的类型转换,一个是隐式的。 ### 问题:main方法的参数里面,字符串数组的第一个参数是什么? 答案:数组是空的,没有任何元素。不像C或者C++,第一个元素默认是程序名。如果命令行没有提供任何参数的话,main方法中的String数组为空,但不是null。 ### 问题:怎么判断数组是null还是为空? 答案:输出array.length的值,如果是0,说明数组为空。如果是null的话,会抛出空指针异常。 ### 问题:程序中可以允许多个类同时拥有都有main方法吗? 答案:可以。当程序运行的时候,我们会指定运行的类名。JVM只会在你指定的类中查找main方法。因此多个类拥有main方法并不存在命名冲突的问题。 ### 问题:静态变量在什么时候加载?编译期还是运行期?静态代码块加载的时机呢? 答案:当类加载器将类加载到JVM中的时候就会创建静态变量,这跟对象是否创建无关。静态变量加载的时候就会分配内存空间。静态代码块的代码只会在类第一次初始化的时候执行一次。一个类可以有多个静态代码块,它并不是类的成员,也没有返回值,并且不能直接调用。静态代码块不能包含this或者super,它们通常被用初始化静态变量。 ### 问题:一个类能拥有多个main方法吗? 答案:可以,但只能有一个main方法拥有以下签名: ``` public static void main(String[] args) {} ``` 否则程序将无法通过编译。编译器会警告你main方法已经存在。 ### 问题:简单的介绍下JVM是如何工作的? 答案:JVM是一台抽象的计算机,就像真实的计算机那样,它们会先将.java文件编译成.class文件(.class文件就是字节码文件),然后用它的解释器来加载字节码。 ### 问题:如果原地交换两个变量的值? 答案:先把两个值相加赋值给第一个变量,然后用得到的结果减去第二个变量,赋值给第二个变量。再用第一个变量减去第二个变量,同时赋值给第一个变量。代码如下: ``` int a=5,b=10;a=a+b; b=a-b; a=a-b; ``` 使用异或操作也可以交换。第一个方法还可能会引起溢出。异或的方法如下: int a=5,b=10;a=a+b; b=a-b; a=a-b; ``` int a = 5; int b = 10; a = a ^ b; b = a ^ b; a = a ^ b; ``` ### 问题:什么是数据的封装? 答案:数据封装的一种方式是在类中创建set和get方法来访问对象的数据变量。一般来说变量是private的,而get和set方法是public的。封装还可以用来在存储数据时进行数据验证,或者对数据进行计算,或者用作自省(比如在struts中使用javabean)。把数据和功能封装到一个独立的结构中称为数据封装。封装其实就是把数据和关联的操作方法封装到一个独立的单元中,这样使用关联的这些方法才能对数据进行访问操作。封装提供的是数据安全性,它其实就是一种隐藏数据的方式。 ### 问题:什么是反射API?它是如何实现的? 答案:反射是指在运行时能查看一个类的状态及特征,并能进行动态管理的功能。这些功能是通过一些内建类的反射API提供的,比如Class,Method,Field, Constructors等。使用的例子:使用Java反射API的getName方法可以获取到类名。 ### 问题:JVM自身会维护缓存吗,是不是在堆中进行对象分配,操作系统的堆还是JVM自己管理的堆?为什么? 答案:是的,JVM自身会管理缓存,它在堆中创建对象,然后在栈中引用这些对象。 ### 问题:虚拟内存是什么? 答案:虚拟内存又叫延伸内存,实际上并不存在真实的物理内存。 ### 问题:方法可以同时即是static又是synchronized的吗? 答案:可以。如果这样做的话,JVM会获取和这个对象关联的java.lang.Class实例上的锁。这样做等于: ``` synchronized(XYZ.class) { } ``` ### 问题:String和StringTokenizer的区别是什么? 答案:StringTokenizer是一个用来分割字符串的工具类。 ``` StringTokenizer st = new StringTokenizer(”Hello World”); while (st.hasMoreTokens()) { System.out.println(st.nextToken()); } ``` 输出: ``` Hello World ``` ### 问题:transient变量有什么特点? 答案:transient变量不会进行序列化。例如一个实现Serializable接口的类在序列化到ObjectStream的时候,transient类型的变量不会被写入流中,同时,反序列化回来的时候,对应变量的值为null。 ### 问题:哪些容器使用Border布局作为它们的默认布局? 答案:Window, Frame, Dialog。 ### 问题:怎么理解什么是同步? 答案:同步用来控制共享资源在多个线程间的访问,以保证同一时间内只有一个线程能访问到这个资源。在非同步保护的多线程程序里面,一个线程正在修改一个共享变量的时候,可能有另一个线程也在使用或者更新它的值。同步避免了脏数据的产生。 对方法进行同步: ``` public synchronized void Method1 () { // Appropriate method-related code. } ``` 在方法内部对代码块进行同步: ``` public myFunction (){ synchronized (this) { // Synchronized code here. } } ``` ================================================ FILE: docs/android/Android-Interview/Java/J2SE基础面试核心内容.md ================================================ 有人可能会问对于我们学Android的同学来讲,面试还会问Java基础吗?答案是会的,但是不会太多,因此我给了两颗星的重要程度。一般笔试的时候出现java基础题的概率比较大,口头面试的时候比较少,比如自己在面试的时候一些对基础知识比较看重的面试官会深究着Java基础去问,比如问你异常的类型以及处理方式,集合的体系结构等等。 # 1. Java面向对象思想 ## 面向对象都有哪些特性以及你对这些特性的理解 继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。 封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。 多态:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当A系统访问B系统提供的服务时,B系统有多种提供服务的方式,但一切对A系统来说都是透明的。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事: - 方法重写(子类继承父类并重写父类中已有的或抽象的方法) - 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为) 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。 # 2. Java中的多态 ## Java中实现多态的机制是什么? 靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。 # 3. Java的异常处理 ## 3.1 Java中异常分为哪些种类 按照异常需要处理的时机分为编译时异常也叫CheckedException和运行时异常也叫RuntimeException。只有java语言提供了Checked异常,Java认为Checked异常都是可以被处理的异常,所以Java程序必须显式处理Checked异常。如果程序没有处理Checked异常,该程序在编译时就会发生错误无法编译。这体现了Java的设计哲学:没有完善错误处理的代码根本没有机会被执行。对Checked异常处理方法有两种: - 当前方法知道如何处理该异常,则用try...catch块来处理该异常。 - 当前方法不知道如何处理,则在定义该方法是声明抛出该异常。 运行时异常只有当代码在运行时才发行的异常,编译时不需要try、catch。Runtime如除数是0和数组下标越界等,其产生频繁,处理麻烦,若显示申明或者捕获将会对程序的可读性和运行效率影响很大。所以由系统自动检测并将它们交给缺省的异常处理程序。当然如果你有处理要求也可以显示捕获它们。 ## 3.2 调用下面的方法,得到的返回值是什么 ```java public int getNum(){ try { int a = 1/0; return 1; } catch (Exception e) { return 2; } finally{ return 3; } } ``` 代码在走到第3行的时候遇到了一个MathException,这时第四行的代码就不会执行了,代码直接跳转到catch语句中,走到第6行的时候,异常机制有这么一个原则如果在catch中遇到了return或者异常等能使该函数终止的话那么用finally就必须先执行完finally代码块里面的代码然后再返回值。因此代码又跳到第8行,可惜第8行是一个return语句,那么这个时候方法就结束了,因此第6行的返回结果就无法被真正返回。如果finally仅仅是处理了一个释放资源的操作,那么该道题最终返回的结果就是2。 因此上面返回值是3。 # 4. Java的数据类型 ## 4.1 Java的基本数据类型都有哪些各占几个字节? Java有8种基本数据类型 | 数据类型 | 字节数 | | :------ | :--- | | byte | 1 | | char | 2 | | short | 2 | | int | 4 | | float | 4 | | double | 8 | | long | 8 | | boolean | 1 | PS:boolean类型比较特别可能只占一个bit,多个boolean可能共同占用一个字节 ## 4.2 String是基本数据类型吗?可以被继承吗? String是引用类型,底层用char数组实现的。因为String是final类,在java中被final修饰的类不能被继承,因此String当然不可以被继承。 # 5. Java的IO # 5.1 Java中有几种类型的流 字节流和字符流。字节流继承于InputStream和OutputStream,字符流继承于InputStreamReader 和OutputStreamWriter。 ## 5.2 字节流如何转为字符流 字节输入流转字符输入流通过InputStreamReader实现,该类的构造函数可以传入InputStream对象。 字节输出流转字符输出流通过OutputStreamWriter实现,该类的构造函数可以传入OutputStream对象。 ## 5.3 如何将一个java对象序列化到文件里? 在java中能够被序列化的类必须先实现Serializable接口,该接口没有任何抽象方法只是起到一个标记作用。 ```java //对象输出流 ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("D://obj"))); objectOutputStream.writeObject(new User("zhangsan", 100)); objectOutputStream.close(); //对象输入流 ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("D://obj"))); User user = (User)objectInputStream.readObject(); System.out.println(user); objectInputStream.close(); ``` # 6. Java的集合 ## 6.1 HashMap排序题,上机题。(本人主要靠这道题入职的第一家公司) 已知一个HashMap\集合, User有name(String)和age(int)属性。请写一个方法实现对HashMap的排序功能,该方法接收HashMap\为形参,返回类型为HashMap\,要求对HashMap中的User的age倒序进行排序。排序时key=value键值对不得拆散。 要做出这道题必须对集合的体系结构非常的熟悉。HashMap本身就是不可排序的,但是该道题偏偏让给HashMap排序,那我们就得想在API中有没有这样的Map结构是有序的,LinkedHashMap,对的,就是他,他是Map结构,也是链表结构,有序的,更可喜的是他是HashMap的子类,我们返回LinkedHashMap\即可,还符合面向接口(父类编程的思想)。但凡是对集合的操作,我们应该保持一个原则就是能用JDK中的API就有JDK中的API,比如排序算法我们不应该去用冒泡或者选择,而是首先想到用Collections集合工具类。 ```java public class HashMapTest { public static void main(String[] args) { HashMap users = new HashMap<>(); users.put(1, new User("张三", 25)); users.put(3, new User("李四", 22)); users.put(2, new User("王五", 28)); System.out.println(users); HashMap sortHashMap = sortHashMap(users); System.out.println(sortHashMap); / * 控制台输出内容 * {1=User [name=张三, age=25], 2=User [name=王五, age=28], 3=User [name=李四, age=22]} * {2=User [name=王五, age=28], 1=User [name=张三, age=25], 3=User [name=李四, age=22]} */ } public static HashMap sortHashMap(HashMap map) { // 首先拿到map的键值对集合 Set> entrySet = map.entrySet(); // 将set集合转为List集合,为什么,为了使用工具类的排序方法 List> list = new ArrayList>(entrySet); // 使用Collections集合工具类对list进行排序,排序规则使用匿名内部类来实现 Collections.sort(list, new Comparator>() { @Override public int compare(Entry o1, Entry o2) { //按照要求根据User的age的倒序进行排 return o2.getValue().getAge()-o1.getValue().getAge(); } }); //创建一个新的有序的HashMap子类的集合 LinkedHashMap linkedHashMap = new LinkedHashMap(); //将List中的数据存储在LinkedHashMap中 for(Entry entry : list){ linkedHashMap.put(entry.getKey(), entry.getValue()); } //返回结果 return linkedHashMap; } } ``` ## 6.2 集合的安全性问题 请问ArrayList、HashSet、HashMap是线程安全的吗?如果不是我想要线程安全的集合怎么办? 我们都看过上面那些集合的源码(如果没有那就看看吧),每个方法都没有加锁,显然都是线程不安全的。话又说过来如果他们安全了也就没第二问了。 在集合中Vector和HashTable倒是线程安全的。你打开源码会发现其实就是把各自核心方法添加上了synchronized关键字。Collections工具类提供了相关的API,可以让上面那3个不安全的集合变为安全的。 ```java Collections.synchronizedCollection(c); Collections.synchronizedList(list); Collections.synchronizedMap(m); Collections.synchronizedSet(s); ``` 上面几个函数都有对应的返回值类型,传入什么类型返回什么类型。打开源码其实实现原理非常简单,就是将集合的核心方法添加上了synchronized关键字。 # 7. Java的多线程 ## 7.1 多线程的两种创建方式 java.lang.Thread 类的实例就是一个线程但是它需要调用java.lang.Runnable接口来执行,由于线程类本身就是调用的Runnable接口所以你可以继承java.lang.Thread 类或者直接实现Runnable接口来重写run()方法实现线程。 ## 7.2 在java中wait和sleep方法的不同? 最大的不同是在等待时wait会释放锁,而sleep一直持有锁。wait通常被用于线程间交互,sleep通常被用于暂停执行。 ## 7.3 synchronized和volatile关键字的作用 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: - 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。 - 禁止进行指令重排序。 volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。 - volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的 - volatile仅能实现变量的修改可见性,并不能保证原子性;synchronized则可以保证变量的修改可见性和原子性 - volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。 - volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化 ## 7.4 分析代码解释原因 ```java public class Counter { private volatile int count = 0; public void inc(){ try { Thread.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } count++; } @Override public String toString() { return "[count=" + count + "]"; } } public class VolatileTest { public static void main(String[] args) { final Counter counter = new Counter(); for(int i=0;i<1000;i++){ new Thread(new Runnable() { @Override public void run() { counter.inc(); } }).start(); } System.out.println(counter); } } ``` 上面的代码执行完后输出的结果确定为1000吗? 答案是不一定,或者不等于1000。这是为什么吗? 在 java 的内存模型中每一个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。也就是说上面主函数中开启了1000个子线程,每个线程都有一个变量副本,每个线程修改变量只是临时修改了自己的副本,当线程结束时再将修改的值写入在主内存中,这样就出现了线程安全问题。因此结果就不可能等于1000了,一般都会小于1000。 上面的解释用一张图表示如下: ![img](img/java内存模型.jpg) ## 7.5 什么是线程池,如何使用? 线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用new线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高的代码执行效率。 ```java ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4); ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4); ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor(); ``` 在JDK的java.util.concurrent.Executors中提供了生成多种线程池的静态方法。然后调用他们的execute方法即可。 ================================================ FILE: docs/android/Android-Interview/Java/J2SE高级面试核心内容.md ================================================ # 1. Java中的反射 ## 1.1 说说你对Java中反射的理解 Java中的反射首先是能够获取到Java中要反射类的字节码,获取字节码有三种方法 - Class.forName(className) - 类名.class - this.getClass() 然后将字节码中的方法,变量,构造函数等映射成相应的Method、Filed、Constructor等类,这些类提供了丰富的方法可以被我们所使用。 # 2. Java中的动态代理 ## 2.1 写一个ArrayList的动态代理类(笔试题) ```java final List list = new ArrayList(); List proxyInstance = (List) Proxy.newProxyInstance(list.getClass().getClassLoader(), list.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(list, args); } } ); proxyInstance.add("你好"); System.out.println(list); ``` ## 3. Java中的设计模式 ## 3.1 你所知道的设计模式有哪些 Java中一般认为有23种设计模式,我们不需要所有的都会,但是其中常用的几种设计模式应该去掌握。下面列出了所有的设计模式。需要掌握的设计模式我单独列出来了,当然能掌握的越多越好。 总体来说设计模式分为三大类: - 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式 - 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式 - 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式 ## 3.2 单例设计模式 最好理解的一种设计模式,分为懒汉式和饿汉式。 ### 3.2.1 饿汉式 ```java public class Singleton { // 直接创建对象 public static Singleton instance = new Singleton(); // 私有化构造函数 private Singleton() { } // 返回对象实例 public static Singleton getInstance() { return instance; } } ``` ### 3.2.2 懒汉式 ```java public class Singleton { // 声明变量 private static volatile Singleton singleton2 = null; // 私有构造函数 private Singleton2() { } // 提供对外方法 public static Singleton2 getInstance() { if (singleton2 == null) { synchronized (Singleton2.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } } ``` ## 3.3 工厂设计模式 工厂模式分为工厂方法模式和抽象工厂模式。工厂方法模式分为三种 - 普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建 - 多个工厂方法模式,是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象 - 静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可 ### 普通工厂模式 ```java public interface Sender { public void Send(); } public class MailSender implements Sender { @Override public void Send() { System.out.println("this is mail sender!"); } } public class SmsSender implements Sender { @Override public void Send() { System.out.println("this is sms sender!"); } } public class SendFactory { public Sender produce(String type) { if ("mail".equals(type)) { return new MailSender(); } else if ("sms".equals(type)) { return new SmsSender(); } else { System.out.println("请输入正确的类型!"); return null; } } } ``` ### 多个工厂方法模式 该模式是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。 ```java public class SendFactory { public Sender produceMail(){ return new MailSender(); } public Sender produceSms(){ return new SmsSender(); } } public class FactoryTest { public static void main(String[] args) { SendFactory factory = new SendFactory(); Sender sender = factory.produceMail(); sender.send(); } } ``` 静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。 ```java public class SendFactory { public static Sender produceMail(){ return new MailSender(); } public static Sender produceSms(){ return new SmsSender(); } } public class FactoryTest { public static void main(String[] args) { Sender sender = SendFactory.produceMail(); sender.send(); } } ``` ### 抽象工厂模式 工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。 ```java public interface Provider { public Sender produce(); } public interface Sender { public void send(); } public class MailSender implements Sender { @Override public void send() { System.out.println("this is mail sender!"); } } public class SmsSender implements Sender { @Override public void send() { System.out.println("this is sms sender!"); } } public class SendSmsFactory implements Provider { @Override public Sender produce() { return new SmsSender(); } } public class SendMailFactory implements Provider { @Override public Sender produce() { return new MailSender(); } } public class Test { public static void main(String[] args) { Provider provider = new SendMailFactory(); Sender sender = provider.produce(); sender.send(); } } ``` ## 3.4 建造者模式(Builder) 工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性,其实建造者模式就是前面抽象工厂模式和最后的Test结合起来得到的。 ```java public class Builder { private List list = new ArrayList(); public void produceMailSender(int count) { for (int i = 0; i < count; i++) { list.add(new MailSender()); } } public void produceSmsSender(int count) { for (int i = 0; i < count; i++) { list.add(new SmsSender()); } } } public class TestBuilder { public static void main(String[] args) { Builder builder = new Builder(); builder.produceMailSender(10); } } ``` ## 3.5 适配器设计模式 适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。 ### 类的适配器模式 ```java public class Source { public void method1() { System.out.println("this is original method!"); } } public interface Targetable { /* 与原类中的方法相同 */ public void method1(); /* 新类的方法 */ public void method2(); } public class Adapter extends Source implements Targetable { @Override public void method2() { System.out.println("this is the targetable method!"); } } public class AdapterTest { public static void main(String[] args) { Targetable target = new Adapter(); target.method1(); target.method2(); } } ``` ### 对象的适配器模式 基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承Source类,而是持有Source类的实例,以达到解决兼容性的问题。 ```java public class Wrapper implements Targetable { private Source source; public Wrapper(Source source) { super(); this.source = source; } @Override public void method2() { System.out.println("this is the targetable method!"); } @Override public void method1() { source.method1(); } } public class AdapterTest { public static void main(String[] args) { Source source = new Source(); Targetable target = new Wrapper(source); target.method1(); target.method2(); } } ``` ### 接口的适配器模式 接口的适配器是这样的:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行。 ## 3.6 装饰模式(Decorator) 顾名思义,装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例。 ```java public interface Sourceable { public void method(); } public class Source implements Sourceable { @Override public void method() { System.out.println("the original method!"); } } public class Decorator implements Sourceable { private Sourceable source; public Decorator(Sourceable source) { super(); this.source = source; } @Override public void method() { System.out.println("before decorator!"); source.method(); System.out.println("after decorator!"); } } public class DecoratorTest { public static void main(String[] args) { Sourceable source = new Source(); Sourceable obj = new Decorator(source); obj.method(); } } ``` ## 3.7 策略模式(strategy) 策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数。策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可。 ```java public interface ICalculator { public int calculate(String exp); } public class Minus extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int arrayInt[] = split(exp, "-"); return arrayInt[0] - arrayInt[1]; } } public class Plus extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int arrayInt[] = split(exp, "\\+"); return arrayInt[0] + arrayInt[1]; } } public class AbstractCalculator { public int[] split(String exp, String opt) { String array[] = exp.split(opt); int arrayInt[] = new int[2]; arrayInt[0] = Integer.parseInt(array[0]); arrayInt[1] = Integer.parseInt(array[1]); return arrayInt; } } public class StrategyTest { public static void main(String[] args) { String exp = "2+8"; ICalculator cal = new Plus(); int result = cal.calculate(exp); System.out.println(result); } } ``` ## 3.8 观察者模式(Observer) 观察者模式很好理解,类似于邮件订阅和RSS订阅,当我们浏览一些博客或wiki时,经常会看到RSS图标,就这的意思是,当你订阅了该文章,如果后续有更新,会及时通知你。其实,简单来讲就一句话:当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化!对象之间是一种一对多的关系。 ```java public interface Observer { public void update(); } public class Observer1 implements Observer { @Override public void update() { System.out.println("observer1 has received!"); } } public class Observer2 implements Observer { @Override public void update() { System.out.println("observer2 has received!"); } } public interface Subject { /*增加观察者*/ public void add(Observer observer); /*删除观察者*/ public void del(Observer observer); /*通知所有的观察者*/ public void notifyObservers(); /*自身的操作*/ public void operation(); } public abstract class AbstractSubject implements Subject { private Vector vector = new Vector(); @Override public void add(Observer observer) { vector.add(observer); } @Override public void del(Observer observer) { vector.remove(observer); } @Override public void notifyObservers() { Enumeration enumo = vector.elements(); while (enumo.hasMoreElements()) { enumo.nextElement().update(); } } } public class MySubject extends AbstractSubject { @Override public void operation() { System.out.println("update self!"); notifyObservers(); } } public class ObserverTest { public static void main(String[] args) { Subject sub = new MySubject(); sub.add(new Observer1()); sub.add(new Observer2()); sub.operation(); } } ``` ================================================ FILE: docs/android/Android-Interview/Java/Java面试题-1.md ================================================ ### 1. JRE与JDK的区别? JRE:java运行时的环境,JDK:包含JRE并且可以查看源码 ### 2. Java中的数据类型都有哪些? 分别是8种基本数据类型:byte、short、int、long、float、double、boolean、char 除8种以外统称为引用类型,例如:String类、Object类、数组及自己创建的类等 ### 3. 等号“==”与equals的区别? "=="比较的是栈上的值(基本数据类型比较值的方式) equlas比较的是堆上的值(引用类型比较值的方式) ### 4. i++与++i的区别? i++为代码执行后自加1,++i为代码执行前自加1 ### 5. break和continue和return的区别? break:跳出整个循环,continue:跳出本次循环,进入下一次循环,return:跳出方法,结束这个方法,并返回一个数据 ### 6. int类型和String类型之间的互相转换? int -->String常用的方法: - 字符串拼接 - String b = String.valueof(int a)方法 String -->int常用的方法: - int i = Integer.valueof(String a)方法 - int i = Integer.parseInt(String a)方法 ### 7. &、|与&&、||的区别? &、| :为位运算符可以进行位运算,符号两边都需要判断才会结束判断,效率低 &&、||:为逻辑判断符用来进行逻辑判断,从左到右判断,一面为否就结束判断,效率高 ### 8. swich()语句中小括号能使用String类型数据么? - 只用JDK1.7版本以后才可以使用String类型数据 - long类型数据任何版本都不可以使用 ### 9. 循环都有哪些? - for循环 - for each循环 - while循环 - do while 循环 (先执行一次) - 递归(方法自己调用自己) ### 10. 类的结构是什么? - 成员变量 - 构造器(构造方法)(不可以被static修饰) - 普通方法 - 代码块 - 内部类 ### 11. 图解java面试题 - JVM 内容大纲 ![img](img/jvm1.png) GC是什么?为什么要有GC? ![img](img/jvm2.png) 垃圾回收的优点和原理,并考虑两种回收机制 ![img](img/jvm3.png) 垃圾回收器的基本原理是什么 ![img](img/gc基本原理.png) Java中会有内存泄漏吗 ![img](img/jvm4.png) ClassLoader如何加载class ![img](img/jvm5.png) JVM内存模型图 ![img](img/jvm6.png) ================================================ FILE: docs/android/Android-Interview/Java/Java面试题-2.md ================================================ **1.Java泛型中的泛型参数能不能是基本类型,比如ArrayList中的T可不可以是int,为什么?ArrayList中可不可以插入数字,如果可以,请用代码示例,如果不可以,请说明原因?** 答:不能,泛型要求能包容的是对象类型,而基本类型在java里不属于对象。 ArrayList<String>中不可以插入数字,因为他的泛型参数规定是String类型,所以只能插入字符串。但是如果非要插入数字,有三种方法可以解决。比如泛型参数改成Integer,或者不声明泛型类型或者是数字和空字符串拼接。 **2.什么是值传递和引用传递?** 答:值传递:传递的是实际参数的一个副本,这个值可能是基本类型,也可能是引用类型的地址。意思指的是在方法调用时,传递的参数是按值的拷贝传递。 引用传递:传递的是实际参数的地址的一个副本。意思指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引用的地址,也就是变量所对应的内存空间的地址。 在java中,只有值传递.引用传递其实也就是内存地址值的传递。 **3.Java集合类框架的基本接口有哪些?请说明各自的用途** - Collection:代表一组对象,每一个对象都是它的子元素。 - Set:不包含重复元素的Collection。 - List:有顺序的collection,并且可以包含重复元素。 - Map:可以把键(key)映射到值(value)的对象,键不能重复。 **4.下面的代码中list对象总共扩容了几次,请详细讲述一下ArrayList的扩容规则。** ```java ArrayList list = new ArrayList<>(); for (int i = 0; i < 20; i++) { list.add(Integer.toString(i)); } ``` 答:总共扩容了2次。它默认是大小(size)是10个,每次扩容都会比较现在的容量是否够用,如果不够用就扩容1.5倍,调用Arrays.copyOf方法,所以每次扩容都是new一个新的数组!如果一个list初始化容量为10,如果需要添加1000个对象,需要初始化11次,而每次都生成新的数组对象,将原来的对象复制到新数组对象里,就是说每次都要丢弃原来的数组对象,这样的操作确实特别费时又占用内存(虽说只是复制对象的引用,但数组对象本身也是需要占用内存的)。看来如果元素很多的话,估计数量初始化一个容量还是很有必要的。 **5.请详细讲述一下RandomAccess接口有什么作用** 答:RandomAccess用来当标记的,是一种标记接口,接口的非典型用法。意思是,随机访问任意下标元素都比较快。用处,当要实现某些算法时,会判断当前类是否实现了RandomAccess接口,会根据结果选择不同的算法 **6. 请详细讲述一下HashMap的内部实现原理。** 答:HashMap的底层实现都是数组+链表结构实现的。添加、删除、获取元素时都是先计算hash,根据hash和table.length计算index也就是table数组的下标,然后进行相应操作。HashMap创建时会默认初始化时创建一个默认容量为16的Entry数组,默认加载因子为0.75,同时设置临界值为16*0.75。 **7. 请编写一段Java代码,对数组{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}进行随机排序。** 答:代码如下 ![随机排序](img/随机排序.png) 打印后的结果是: ``` 8 0 9 2 3 5 4 7 6 1 ``` **8. 请用您认为最优的算法计算斐波那契数列:F(n)=F(n-1)+F(n-2) (n为自然数,F(0)=0, F(1)=1)。** 答: ![斐波那契数列](img/斐波那契数列.png) **9. 请详细讲述一下Java中volatile关键字的作用。** 答:用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。 主要用在多线程,同步变量。 线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B。只在某些动作时才进行A和B的同步。因此存在A和B不一致的情况。volatile就是用来避免这种情况的。volatile告诉jvm, 它所修饰的变量不保留拷贝,直接访问主内存中的(也就是上面说的A)。在Java内存模型中,有main memory,每个线程也有自己的memory (例如寄存器)。为了性能,一个线程会在自己的memory中保持要访问的变量的副本。这样就会出现同一个变量在某个瞬间,在一个线程的memory中的值可能与另外一个线程memory中的值,或者main memory中的值不一致的情况。 一个变量声明为volatile,就意味着这个变量是随时会被其他线程修改的,因此不能将它cache在线程memory中。 **10. 写一段代码在遍历 ArrayList 时移除一个元素。** 答:我们知道ArrayList的底层是用数组实现的,如果你删除了其中一个元素,那么后边的元素都会向前移动。所以在遍历时如果删除元素,就要小心了。 第一种方法,用数组下标进行遍历,如果需要删除元素,我们从后向前遍历,这样不论有没有元素删除,我们都不会遗漏未被遍历的元素。 第二种方法,我们使用迭代器。 ```java Iterator itr = list.iterator(); while(itr.hasNext()) { if(...) { itr.remove(); } } ``` 总之,如果你的删除操作比较多的话,建议使用LinkedList。 **11. 请详细解释一下Java泛型中<? super T>和<? extends T>的作用和区别。** 答:<? super T>表示包括T在内的任何T的父类,<? extends T>表示包括T在内的任何T的子类。 遵循一个原则则是,生产者(Producer)使用extends,消费者(Consumer)使用super。 **生产者使用extends** 如果你需要一个列表提供T类型的元素(即你想从列表中读取T类型的元素),你需要把这个列表声明成< extends T>,比如List< extends Integer>,因此你不能往该列表中添加任何元素。 **消费者使用super** 如果需要一个列表使用T类型的元素(即你想把T类型的元素加入到列表中),你需要把这个列表声明成< super T>,比如List< super Integer>,因此你不能保证从中读取到的元素的类型。 即是生产者,也是消费者 如果一个列表即要生产,又要消费,你不能使用泛型通配符声明列表,比如List<Integer>。 **12. 请用JDBC写一段访问数据库读取数据的代码,SL语句和驱动自己选择。** ![jdbc](img/jdbc.png) **13. 请谈谈Proxy模式,Adapter模式,Decorator模式以及Façade模式分别的使用场景和区别。** 答:**Proxy模式**是设计模式中的代理模式,比如我去优衣库买衣服,那么我肯定不会直接去优衣库工厂(生产衣服工厂)去买衣服吧,那么我们直接去门店啊或者代理店去买。这些地方其实就是优衣库造衣工厂的代理。 什么时候开始使用呢?当我们需要使用的对象很复杂或者需要很长时间去构造,这时就可以使用代理模式(Proxy)。例如:如果构建一个对象很耗费时间和计算机资源,代理模式(Proxy)允许我们控制这种情况,直到我们需要使用实际的对象。一个代理(Proxy)通常包含和将要使用的对象同样的方法,一旦开始使用这个对象,这些方法将通过代理(Proxy)传递给实际的对象。 一些可以使用代理模式(Proxy)的情况: 一个对象,比如一幅很大的图像,需要载入的时间很长。     一个需要很长时间才可以完成的计算结果,并且需要在它计算过程中显示中间结果 一个存在于远程计算机上的对象,需要通过网络载入这个远程对象则需要很长时间,特别是在网络传输高峰期。 一个对象只有有限的访问权限,代理模式(Proxy)可以验证用户的权限 代理模式(Proxy)也可以被用来区别一个对象实例的请求和实际的访问,例如:在程序初始化过程中可能建立多个对象,但并不都是马上使用,代理模式(Proxy)可以载入需要的真正的对象。这是一个需要载入和显示一幅很大的图像的程序,当程序启动时,就必须确定要显示的图像,但是实际的图像只能在完全载入后才可以显示!这时我们就可以使用代理模式(Proxy)。 **Adapter模式**是设计模式的适配器模式,它主要是将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。 使用场景即系统需要使用现有的类,而这些类的接口不符合系统的接口。 想要建立一个可以重用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。 两个类所做的事情相同或相似,但是具有不同接口的时候。 旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们不希望手动更改原有类的时候。 使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第三方组件接口的功能。 **Decorator模式**即设计模式中的装饰器模式。它能动态地给一个对象添加一些额外的职责或者行为。就增加功能来说, Decorator模式相比生成子类更为灵活。它提供了改变子类的灵活方案。装饰器模式在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。 当用于一组子类时,装饰器模式更加有用。如果你拥有一族子类(从一个父类派生而来),你需要在与子类独立使用情况下添加额外的特性,你可以使用装饰器模式,以避免代码重复和具体子类数量的增加。 **Façade模式**即设计模式的外观设计模式。它为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 应用场景是 当你要为一个复杂子系统提供一个简单接口时。 客户程序与抽象类的实现部分之间存在着很大的依赖性。 当你需要构建一个层次结构的子系统时,使用Facade模式定义子系统中每层的入口点。仅通过facade进行通讯。 **14. OOP的基本特性和设计原则有哪些。** 答:OOP的三大基本特性是抽象与封装,继承与多态。 **1.抽象与封装:** 抽象是把系统中需要处理的数据和在这些数据上的操作结合在一起,根据功能、性质和用途等因素抽象成不同的抽象数据类型。每个抽象数据类型既包含了数据,又包含了针对这些数据的授权操作。在面向对象的程序设计中,抽象数据类型是用“类”这种结构来实现的,每个类里都封装了相关的数据和操作。 封装是指利用抽象数据类型和基于数据的操作结合在一起,数据被保护在抽象数据类型的内部,系统的其他部分只有通过包裹在数据之外被授权的操作,才能与这个抽象数据类型进行交互。 **2. 继承:** 它是与传统方法不同的一个最有特色的方法。它是面向对象的程序中两个类之间的一种关系,即一个类可以从另一个类(即它的父类)继承状态和行为。继承父类的类称为子类。 继承的优越性:通过使用继承,程序员可以在不同的子类中多次重新使用父类中的代码,使程序结构清晰,易于维护和修改,而子类又可以提供一些特殊的行为,这些特殊的行为在父类中是没有的 **3.多态:** 是指一个程序中同名的方法共存的情况,调用者只需使用同一个方法名,系统会根据不同情况,调用相应的不同方法,从而实现不同的功能。多态性又被称为“一个名字,多个方法”。 设计原则有五种: **单一职责原则(Single-Resposibility Principle)**。"对一个类而言,应该仅有一个引起它变化的原因。"本原则是我们非常熟悉地"高内聚性原则"的引申,但是通过将"职责"极具创意地定义为"变化的原因",使得本原则极具操作性,尽显大师风范。同时,本原则还揭示了内聚性和耦合生,基本途径就是提高内聚性;如果一个类承担的职责过多,那么这些职责就会相互依赖,一个职责的变化可能会影响另一个职责的履行。其实OOD的实质,就是合理地进行类的职责分配。 **开放封闭原则(Open-Closed principle)。**"软件实体应该是可以扩展的,但是不可修改。"本原则紧紧围绕变化展开,变化来临时,如果不必改动软件实体裁的源代码,就能扩充它的行为,那么这个软件实体设计就是满足开放封闭原则的。如果说我们预测到某种变化,或者某种变化发生了,我们应当创建抽象类来隔离以后发生的同类变化。在Java中,这种抽象是指抽象基类或接口;在C++中,这各抽象是指抽象基类或纯抽象基类。当然,没有对所有情况都贴切的模型,我们必须对软件实体应该面对的变化做出选择。 **Liskov替换原则(Liskov-Substituion Principle)**。"子类型必须能够替换掉它们的基类型。"本原则和开放封闭原则关系密切,正是子类型的可替换性,才使得使用基类型模块无需修改就可扩充。Liskov替换原则从基于契约的设计演化而来,契约通过为每个方法声明"先验条件"和"后验条件";定义子类时,必须遵守这些"先验条件"和"后验条件"。当前基于契的设计发展势头正劲,对实现"软件工厂"的"组装生产"梦想是一个有力的支持。 **依赖倒置原则(Dependecy-Inversion Principle)。**"抽象不应依赖于细节,细节应该依赖于抽象。"本原则几乎就是软件设计的正本清源之道。因为人解决问题的思考过程是先抽象后具体,从笼统到细节,所以我们先生产出的势必是抽象程度比较高的实体,而后才是更加细节化的实体。于是,"细节依赖于抽象"就意味着后来的依赖于先前的,这是自然而然的重用之道。而且,抽象的实体代表着笼而统之的认识,人们总是比较容易正确认识它们,而且本身也是不易变的,依赖于它们是安全的。依赖倒置原则适应了人类认识过程的规律,是面向对象设计的标志所在。 **接口隔离原则(Interface-Segregation Principle)。**"多个专用接口优于一个单一的通用接口。"本原则是单一职责原则用于接口设计的自然结果。一个接口应该保证,实现该接口的实例对象可以只呈现为单一的角色;这样,当某个客户程序的要求发生变化,而迫使接口发生改变时,影响到其他客户程序的可能生性小。 良性依赖原则。"不会在实际中造成危害的依赖关系,都是良性依赖。"通过分析不难发现,本原则的核心思想是"务实",很好地揭示了极限编程(Extreme Programming)中"简单设计"各"重构"的理论基础。本原则可以帮助我们抵御"面向对象设计五大原则"以及设计模式的诱惑,以免陷入过度设计(Over-engineering)的尴尬境地,带来不必要的复杂性。 **15. 请谈谈Java中的多线程相关的内容以及各种相关的锁。** 答:实现Java中的多线程有两种方式。一是直接继承Thread类,二是实现Runnable接口。通常我们实现Runnabale接口方式去做。好处如下: (1)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数据有效的分离,较好地体现了面向对象的设计思想。 (2)可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式了。 (3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。 多个线程操作相同的数据,与它们的代码无关。当共享访问相同的对象是,即它们共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数 实参传递进去,这个对象就是一个实现了Runnable接口的类的实例 Java中多线程的状态是 1.新建状态(New):新创建了一个线程对象。 2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。 3.运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。 4.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。 5.死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。 Java多线程锁有对象锁和类锁。对象级别锁是一个机制,当你想同步一个非静态方法或者非静态代码块,让在给定的类实例中只有一个线程来执行这个代码块,这就可以使得实例级别的数据是线程安全的。类级别锁是在所有可变的实例或者运行环境中,类级别锁阻止多线程进入同步块,也就是说,如果运行环境中有DemoClass的100个实例,在任何时刻,只能有DemoClass的一个实例来执行它的demoMethod()方法,所有其他的DemoClass实例在其他线程中只能处于阻塞状态,这使得静态数据是线程安全的。 ================================================ FILE: docs/android/Android-Interview/Java/Java高级软件工程师面试考纲.md ================================================ 如果要应聘高级开发工程师职务,仅仅懂得Java的基础知识是远远不够的,还必须懂得常用数据结构、算法、网络、操作系统等知识。因此本文不会讲解具体的技术,笔者综合自己应聘各大公司的经历,整理了一份大公司对Java高级开发工程师职位的考核纲要,希望可以帮助到需要的人。 当前,市面上有《Java XX宝典》类似的图书,而且图书中的内容都着重在讲解Java最为基础的部分,最严重的是,里面有着大量错误的内容,极具误导性。另外,网上也有各种各样的[Java面试题](http://www.codeceo.com/article/tag/java%E9%9D%A2%E8%AF%95%E9%A2%98),很多也是着重在Java语言基础上。实际上,如果要应聘高级开发工程师职务,仅仅懂得Java的基础知识是远远不够的,还必须懂得常用数据结构、算法、网络、操作系统等知识。因此本文不会讲解具体的技术,笔者综合自己应聘各大公司的经历,整理了一份大公司对Java高级开发工程师职位的考核纲要,希望可以帮助到需要的人。 ## 1 Java基础 **1.1 Collection和Map** (1)掌握Collection和Map的继承体系。 (2)掌握ArrayList、LinkedList、Vector、Stack、PriorityQueue、HashSet、LinkedHashSet、TreeSet、HashMap、LinkedHashMap、TreeMap、[WeakHashMap](http://www.codeceo.com/article/java-weakhashmap-source.html)、EnumMap、TreeMap、HashTable的特点和实现原理。 (3)掌握CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap的实现原理和适用场景。 **1.2 IO** (1)掌握InputStream、OutputStream、Reader、Writer的继承体系。 (2)掌握字节流(FileInputStream、DataInputStream、BufferedInputStream、FileOutputSteam、DataOutputStream、BufferedOutputStream)和字符流(BufferedReader、InputStreamReader、FileReader、BufferedWriter、OutputStreamWriter、PrintWriter、FileWriter),并熟练运用。 (3)掌握NIO实现原理及使用方法。 **1.3 异常** (1)掌握Throwable继承体系。 (2)掌握异常工作原理。 (3)了解常见受检异常(比如FileNotFoundException)、非受检异常(比如NullPointerException)和错误(比如IOError)。 **1.4 多线程** (1)掌握[Executor](http://www.codeceo.com/article/java-executor-learning.html)s可以创建的三种(JAVA8增加了一种,共四种)线程池的特点及适用范围。 (2)掌握多线程同步机制,并熟练运用。 **1.5 Socket** (1)掌握Socket通信原理。 (2)熟练使用多线程结合Socket进行编程。 ## 2 Java虚拟机 **2.1 JVM内存区域划分** (1)掌握程序计数器、堆、虚拟机栈、本地方法栈、方法区(JAVA8已移除)、元空间(JAVA8新增)的作用及基本原理。 (2)掌握堆的划分:新生代(Eden、Survivor1、Survivor2)和老年代的作用及工作原理。 (3)掌握JVM内存参数设置及调优。 **2.2 类加载** (1)掌握类的加载阶段:加载、链接(验证、准备、解析)、初始化、使用、卸载。 (2)掌握类加载器分类及其应用:启动类加载器、扩展类加载器、应用程序类加载器、自定义加载器。 ## 3 J2EE (1) 掌握JSP内置对象、动作及相关特点和工作原理。 (2) 掌握Servlet的特点和工作原理。 (3) 掌握Spring框架的IOC和AOP实现原理(反射和动态代理)。 (4) 至少掌握一个[MVC框架](http://www.codeceo.com/article/mvc-framework-mvc-design.html)(Spring MVC,Struts等)的工作原理,并熟练运用。 (5) 至少掌握一个ORM框架(Hibernate,MyBatis等)的工作原理,并熟练运用。 ## 4 数据结构与算法 (1)掌握线性表和树的特点并熟练运用。 (2)掌握常用排序和查找算法:插入排序(直接插入排序、希尔排序)、选择排序(直接选择排序、堆排序)、交换排序(冒泡排序、快速排序)、归并排序,顺序查找、[二分查找](http://www.codeceo.com/article/binary-search.html)、哈希查找。 (3) 熟练运用常见排序和查找算法思想解决编程问题。 (4)了解几大基本算法:[贪心算法](http://www.codeceo.com/article/greedy-algorithm.html)、分治策略、动态规划。 ## 5 计算机网络 (1)掌握网络的分层结构,及每层的功能特点。 (2)掌握TCP/IP的通信原理(三次握手、四次挥手) ## 6 数据库 (1)掌握复杂的SQL语句编写。 (2)掌握数据库的优化(SQL层面和表设计层面)。 (3)至少掌握一款数据库产品。 (4)熟悉高并发、大数据情况下的数据库开发。 ## 7 Web技术 (1)掌握AJAX的工作原理。 (2)至少熟悉一款JS框架(比如JQuery)。 ## 8 [设计模式](http://www.codeceo.com/article/category/develop/design-patterns) (1)熟悉常见的设计模式。 (2)会将设计模式理论应用到实际开发中。 ## 9 Linux (1)熟练运用Linux常见命令。 (2)熟悉Linux操作系统基本概念及特点。 (3)熟悉Shell脚本。 ## 10 操作系统 (1)掌握操作系统的进程管理。 (2)了解操作系统的I/O。 ## 11 正则表达式 (1)掌握常见正则表达式符号。 (2)熟练运用正则表达式解决实际问题(比如匹配电话号码、邮箱、域名等)。 ================================================ FILE: docs/android/Android-Interview/Java/README.md ================================================ ## Java面试题 - [深拷贝浅拷贝](深拷贝浅拷贝.md) - [数据库求差](数据库求差.md) - [Java面试题-1](Java面试题-1.md) - [Java面试题-2](Java面试题-2.md) - [Java高级软件工程师面试考纲](Java高级软件工程师面试考纲.md) - [66道经典的Java基础面试题集锦](66道经典的Java基础面试题集锦.md) - [115个Java面试题及回答](115个Java面试题及回答.md) - [J2SE基础面试核心内容](J2SE基础面试核心内容.md) - [J2SE高级面试核心内容](J2SE高级面试核心内容.md) ================================================ FILE: docs/android/Android-Interview/Java/数据库求差.md ================================================ 今日,有同学跟我说他在最近在面试的时候,面试官问了他一个很简单的问题,结果他一脸懵逼,他可是有过一年开发经验的,怎么可能会在面试的时候马失前蹄了呢?大家来看下他的问题你们会不会。 ![img](http://www.10tiao.com/img.do?url=http%3A//mmbiz.qpic.cn/mmbiz_jpg/3aGGuJWRuJc7PgjGWJCIOVuGNNK96RCSicx4e1PXo9PDQFr53A30iakbx5yZHsicNynHetU2LA8ib5EiaRmyYhaRIPg/0%3Fwx_fmt%3Djpeg) 是很简单的问题吧 我一看,这问题糟了,我也不知道,是很尴尬,我就给了我们公司一位大牛,结果大牛神秘兮兮的给了一张图 ![img](http://www.10tiao.com/img.do?url=http%3A//mmbiz.qpic.cn/mmbiz_jpg/3aGGuJWRuJc7PgjGWJCIOVuGNNK96RCScFmic7MYlFtryBFSoYORsEXNyRguDLWqkNVsRibrqAUqicKuTT93TsFdg/0%3Fwx_fmt%3Djpeg) 这一下子我就看懂了,但我就是不说,我就也给他回了这张图 ![img](http://www.10tiao.com/img.do?url=http%3A//mmbiz.qpic.cn/mmbiz_jpg/3aGGuJWRuJc7PgjGWJCIOVuGNNK96RCS5PokXV5qDtM5SXCoLc4j9LqZ6T3ZkW8dXHQ6mpD7mabNPicAUBKEVcA/0%3Fwx_fmt%3Djpeg) 这是后来他回我的 但是,不得不说,他还是可以的,一下子他也看懂了,原来这就是个小问题,但是就是这个小细节,他却错失了一次很好的机会。 ![img](http://www.10tiao.com/img.do?url=http%3A//mmbiz.qpic.cn/mmbiz_jpg/3aGGuJWRuJc7PgjGWJCIOVuGNNK96RCSTAQic0MyRc5haDx1O7kf3wG6WxrHBsNsickktFzHRPWlkAPcIVPLtticw/0%3Fwx_fmt%3Djpeg) 我也帮大家整理了一些网上面网友遇到过的古怪面试题。可能不全,但也会帮到大家的吧 1、 使用两种方式写出SingleTon. 2、 简单绘制出Sun Hotspot VM的内存结构。并简单说明各自的作用。 3、 实现一个用于生产者-消费者模式的队列。假设队列的固定长度为10,FULL的时候只能消费不能生产,EMPTY的时候只能生产不能消费。 4、 调用A线程的interrupt方法,其在什么情况下会抛出InterruptException(不考虑网络IO的情况) 5、 Sevlet容器如何使用cookie跟踪回话?response.encodeURL(URL)什么情况下生效? 6、 DOM与SAX方式解析XML文件的原理?各自适用的场景是什么? 7、 将一个byte值转化为int值为什么要与0Xff进行&运算? 答案呢,在下面 1.至少有3种写法 2.新生代(eden+2survisor) 老年代 持久代 3.可考虑blockingqueue 4.该线程在wait或者sleep时 5.cookie里边有session id的内容。 6.dom是一口吃,生成节点树~另一个基于事件处理 7.8->32位 试题都很简单,但是越微小越容易出错,编程更是这样,虽然现在代码都会报错,但是我们也应该养成注意细节的好习惯。 ================================================ FILE: docs/android/Android-Interview/Java/深拷贝浅拷贝.md ================================================ “纵使面试无数,难敌考官吃素“,昨天去乐视面试,遇到一个从来没有遇到过的考题! ## 请分别实现深度和浅读的对象克隆? 原理:深度克隆和浅度克隆,Object中的克隆方法是浅度克隆。JDK规定了克隆需要满足的一些条件,简要总结一下就是:对某个对象进行克隆,对象的的成员变量如果包括引用类型或者数组,那么克隆的时候其实是不会把这些对象也带着复制到克隆出来的对象里面的,只是复制一个引用,这个引用指向被克隆对象的成员对象,但是基本数据类型是会跟着被带到克隆对象里面去的。而深度可能就是把对象的所有属性都统统复制一份新的到目标对象里面去。简单画个对比图: ![img](img/clone1.png) ![img](img/clone2.png) ## 实现方式 - 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆; - 实现Cloneable接口并重写Object类中的clone()方法,即可实现浅度克隆。 代码 Car.java ```java public class Car implements Serializable { private static final long serialVersionUID = 1L; private String brand;//品牌 private int maxSpeed;//最高时速 public Car(String brand, int maxSpeed) { this.brand = brand; this.maxSpeed = maxSpeed; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public int getMaxSpeed() { return maxSpeed; } public void setMaxSpeed(int maxSpeed) { this.maxSpeed = maxSpeed; } @Override public String toString() { return "Car{" + "brand='" + brand + '\'' + ", maxSpeed=" + maxSpeed + '}'; } } ``` Person.java ```java public class Person implements Serializable { private static final long serialVersionUID = 1L; private String name;//姓名 private int age;//年龄 private Car car;//座驾 public Person(String name, int age, Car car) { this.name = name; this.age = age; this.car = car; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Car getCar() { return car; } public void setCar(Car car) { this.car = car; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", car=" + car + '}'; } } ``` Person2.java ```java public class Person2 implements Serializable { private static final long serialVersionUID = 1L; private String name;//姓名 private int age;//年龄 private Car car;//座驾 public Person2(String name, int age, Car car) { this.name = name; this.age = age; this.car = car; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Car getCar() { return car; } public void setCar(Car car) { this.car = car; } @Override protected Object clone(){ Person2 s= null; try { s = (Person2) Person2.super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return s; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", car=" + car + '}'; } } ``` CloneUtil.java ```java public class CloneUtil { private CloneUtil() { throw new AssertionError(); } public static T clone(T object) throws IOException, ClassNotFoundException { // 说明:调用ByteArrayOutputStream或ByteArrayInputStream对象的close方法没有任何意义 // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外资源(如文件流)的释放 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(object); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); return (T) ois.readObject(); } } ``` CloneTest ```java public class CloneTest { public static void main(String[] args){ try { // 深拷贝 Person p1 = new Person("xiaoming",18,new Car("Benz",300)); Person p2 = CloneUtil.clone(p1); // 修改克隆的Person对象p2关联的汽车对象的品牌属性 // 原来的Person对象p1关联的汽车不会受到任何影响,还是Benz // 因为在克隆Person对象时其关联的汽车对象也被克隆了 p2.setAge(25); p2.getCar().setBrand("BYD"); System.out.println(p1); Person2 p3 = new Person2("xiaoming",18,new Car("Benz",300)); Person2 p4 = (Person2) p3.clone(); p4.setAge(25); p4.getCar().setBrand("BYD"); System.out.println(p3); } catch (Exception e) { e.printStackTrace(); } } } ``` 结果 ![img](img/clone3.png) ================================================ FILE: docs/android/Android-Interview/README.md ================================================ ## Android面试 1. [史上最全 Android 面试资料集合](Android/史上最全 Android 面试资料集合.md) 2. [2016Android某公司面试题](http://blog.csdn.net/jdsjlzx/article/details/51201925) 3. [国内一线互联网公司内部面试题库](https://github.com/JackyAndroid/AndroidInterview-Q-A/blob/master/README-CN.md) 4. [BAT无线工程师面试流程详细解析](http://blog.csdn.net/axi295309066/article/details/52317615) 5. [阿里面经1](http://blog.csdn.net/axi295309066/article/details/50512835) 6. [ Android面试题整理](http://blog.csdn.net/x605940745/article/category/1808335) 7. [2016Android某公司面试题](http://yuweiguocn.github.io/interview-2016-big-company/) 8. [Android面试题集合](http://blog.csdn.net/axi295309066/article/details/54089310) 9. [一个五年Android 开发者百度、阿里、聚美、映客的面试心经](http://blog.csdn.net/jdsjlzx/article/details/51860422?locationNum=2&fps=1) 10. [扫清Android面试障碍--面试前的准备](http://blog.csdn.net/jdsjlzx/article/details/51424303?locationNum=1&fps=1) 11. [Andorid-15k+的面试题](http://blog.csdn.net/jdsjlzx/article/details/40738053?locationNum=3&fps=1) 12. [Android 开发工程师面试指南](https://github.com/GeniusVJR/LearningNotes) 13. [面试不失败-揭秘IT面试各个环节成功的内幕](https://pan.baidu.com/s/1mhB0aSg?errno=0&errmsg=Auth%20Login%20Sucess&&bduss=&ssnerror=0#list/path=%2F) 14. [亲爱的面试官,这个我可没看过!(Android部分)](http://www.jianshu.com/p/89f19d67b348) 15. [115个Java面试题及回答](https://github.com/snowdream/115-Java-Interview-Questions-and-Answers/tree/master/zh) 16. [interview-about](https://github.com/closedevice/interview-about) ## 目录 - [前言](README.md) - [Android视频教程](Android/Android视频教程.md) - [BAT大咖助力,全面升级Android面试](Android/BAT大咖助力全面升级Android面试.md) - [Android高级面试,10大开源框架源码解析](Android/Android高级面试10大开源框架源码解析.md) - [Android](Android/README.md) - [Android基础面试核心内容](Android/Android基础面试核心内容.md) - [Android面试精华题目总结](Android/Android面试精华题目总结.md) - [Android面试题-1](Android/Android面试题-1.md) - [Android面试题-2](Android/Android面试题-2.md) - [Android面试重点](Android/Android面试重点.md) - [接口安全](Android/接口安全.md) - [平台架构](Android/平台架构.md) - [源码分析相关面试题](源码分析/README.md) - [Volley源码剖析](源码分析/Volley源码剖析.md) - [注解框架内部实现原理](源码分析/注解框架内部实现原理.md) - [okhttp内核剖析](源码分析/okhttp内核剖析.md) - [Android源码编译实现静默安装和静默偷拍](源码分析/Android源码编译实现静默安装和静默偷拍.md) - [Activity相关面试题](Activity/README.md) - [onSaveInstanceState源码内核分析](Activity/onSaveInstanceState源码内核分析.md) - [深刻剖析activity启动模式-1](Activity/深刻剖析activity启动模式-1.md) - [深刻剖析activity启动模式-2](Activity/深刻剖析activity启动模式-2.md) - [深刻剖析activity启动模式-3](Activity/深刻剖析activity启动模式-3.md) - [Activity Task和Process之间的关系](Activity/Activity Task和Process.md) - [为什么service里面startActivity抛异常](Activity/为什么service里面startActivity抛异常.md) - [App优雅退出](Activity/App优雅退出.md) - [onCreate源码分析](Activity/onCreate源码分析.md) - [Service相关面试题](Service/README.md) - [IntentService源码分析](Service/IntentService源码分析.md) - [Service是否在main thread中执行, service里面是否能执行耗时的操作?](Service/Android面试题-Service.md) - [Android面试题-Service不死之身](Service/Android面试题-Service不死之身.md) - [与XMPP相关面试题](网络编程/README.md) - [阐述一下对XMPP协议理解以及优缺点?](网络编程/阐述一下对XMPP协议理解以及优缺点?.md) - [简单阐述一下及时推送原理?](网络编程/简单阐述一下及时推送原理?.md) - [与性能优化相关面试题](性能优化/README.md) - [内存泄漏和内存溢出区别](性能优化/与性能优化相关试题一.md) - [UI优化和线程池实现原理](性能优化/与性能优化相关试题二.md) - [代码优化](性能优化/与性能优化相关试题三.md) - [Android应用UI性能分析](性能优化/Android应用UI性能分析.md) - [内存泄漏监测](性能优化/内存泄漏监测.md) - [App应用启动分析与优化](性能优化/App应用启动分析与优化.md) - [与IPC机制相关面试题](性能优化/与IPC机制相关面试题.md) - [与登录相关面试题](登陆注册/README.md) - [Oauth的实现原理](登陆注册/Oauth的实现原理.md) - [Token的实际意义](登陆注册/Token的实际意义.md) - [微信扫码登录内部实现原理](登陆注册/微信扫码登录内部实现原理.md) - [与开发相关面试题](开发遇到的问题/README.md) - [迭代开发的时候如何向前兼容新旧接口?](开发遇到的问题/迭代开发的时候如何向前兼容新旧接口?.md) - [手把手教你如何解决as jar包冲突](开发遇到的问题/手把手教你如何解决as jar包冲突.md) - [Context原理分析](开发遇到的问题/Context原理分析.md) - [解决ViewPager.setCurrentItem中间很多页面切换方案](开发遇到的问题/终极解决ViewPager.setCurrentItem中间页面过多解决方案.md) - [解决字体适配](开发遇到的问题/解决字体适配.md) - [软键盘顶出去解决方案](开发遇到的问题/软键盘顶出去解决方案.md) - [机型适配之痛](开发遇到的问题/机型适配之痛.md) - [ViewPager和Fragment使用过程中会遇到哪些问题](开发遇到的问题/ViewPager和Fragment使用过程中会遇到哪些问题.md) - [与人事相关面试题](HR/README.md) - [人事面试宝典一之自我介绍](HR/人事面试宝典一之自我介绍.md) - [人事面试宝典二之离职](HR/人事面试宝典二之离职.md) - [人事面试宝典](HR/人事面试宝典.md) - [网络编程](网络编程/README.md) - [Android客户端和服务端如何使用Token和Session](网络编程/Android客户端和服务端如何使用Token和Session.md) - [推送原理](网络编程/推送原理.md) - [Java面试题](Java/README.md) - [深拷贝浅拷贝](Java/深拷贝浅拷贝.md) - [数据库求差](Java/数据库求差.md) - [Java面试题-1](Java/Java面试题-1.md) - [Java面试题-2](Java/Java面试题-2.md) - [Java高级软件工程师面试考纲](Java/Java高级软件工程师面试考纲.md) - [66道经典的Java基础面试题集锦](Java/66道经典的Java基础面试题集锦.md) - [115个Java面试题及回答](Java/115个Java面试题及回答.md) - [J2SE基础面试核心内容](Java/J2SE基础面试核心内容.md) - [J2SE高级面试核心内容](Java/J2SE高级面试核心内容.md) - [面试技巧](面试技巧/README.md) - [程序员面试宝典](面试技巧/程序员面试宝典.md) - [罗永浩新东方万字求职信](面试技巧/罗永浩新东方万字求职信.md) - [我在面试中最喜欢问开发者的问题,和回答思路](面试技巧/我在面试中最喜欢问开发者的问题,和回答思路.md) - [经验分享](经验分享/README.md) - [我为什么要离开华为?](经验分享/我为什么要离开华为?.md) - [工作三年后,我选择离开腾讯](经验分享/工作三年后,我选择离开腾讯.md) - [一个程序员的血泪史](经验分享/一个程序员的血泪史.md) - [我为什么离开锤子科技?](经验分享/我为什么离开锤子科技?.md) - [互联网巨头BAT3内部员工的真实状况](经验分享/互联网巨头BAT3内部员工的真实状况.md) - [扫清Android面试障碍](经验分享/扫清Android面试障碍.md) - [史上最全 Android 面试资料集合](经验分享/史上最全 Android 面试资料集合.md) - [2016年4月某公司面试题及面试流程](经验分享/2016年4月某公司面试题及面试流程.md) - [2017届实习生招聘面经](经验分享/2017届实习生招聘面经.md) - [国内一线互联网公司内部面试题库](经验分享/国内一线互联网公司内部面试题库.md) - [互联网公司面试经验总结](经验分享/互联网公司面试经验总结.md) - [一个五年Android开发者百度、阿里、聚美、映客的面试心经](经验分享/一个五年Android开发者百度、阿里、聚美、映客的面试心经.md) - [腾讯公司程序员面试题及答案详解](经验分享/腾讯公司程序员面试题及答案详解.md) - [面试心得与总结:BAT、网易、蘑菇街 ](经验分享/面试心得与总结:BAT、网易、蘑菇街 .md) - [阿里+百度+CVTE面经合集](经验分享/阿里+百度+CVTE面经合集.md) - [技术硬碰硬—阳哥带你玩转上海Android招聘市场](经验分享/技术硬碰硬—阳哥带你玩转上海Android招聘市场.md) - [Android 曲折的求职之路](经验分享/Android 曲折的求职之路.md) - [杭州找 Android 工作的点点滴滴](经验分享/杭州找 Android 工作的点点滴滴.md) - [给培训班出来的一点不成熟的小建议](经验分享/给培训班出来的一点不成熟的小建议.md) - [Android 暑期实习生面试经验谈](经验分享/Android 暑期实习生面试经验谈.md) ================================================ FILE: docs/android/Android-Interview/SUMMARY.md ================================================ # Summary * [前言](README.md) * [Android视频教程](Android/Android视频教程.md) * [BAT大咖助力,全面升级Android面试](Android/BAT大咖助力全面升级Android面试.md) * [Android高级面试,10大开源框架源码解析](Android/Android高级面试10大开源框架源码解析.md) * [Android](Android/README.md) * [Android基础面试核心内容](Android/Android基础面试核心内容.md) * [Android面试精华题目总结](Android/Android面试精华题目总结.md) * [Android面试题-1](Android/Android面试题-1.md) * [Android面试题-2](Android/Android面试题-2.md) * [Android面试重点](Android/Android面试重点.md) * [接口安全](Android/接口安全.md) * [平台架构](Android/平台架构.md) * [源码分析相关面试题](源码分析/README.md) * [Volley源码剖析](源码分析/Volley源码剖析.md) * [注解框架内部实现原理](源码分析/注解框架内部实现原理.md) * [okhttp内核剖析](源码分析/okhttp内核剖析.md) * [Android源码编译实现静默安装和静默偷拍](源码分析/Android源码编译实现静默安装和静默偷拍.md) * [Activity相关面试题](Activity/README.md) * [onSaveInstanceState源码内核分析](Activity/onSaveInstanceState源码内核分析.md) * [深刻剖析activity启动模式-1](Activity/深刻剖析activity启动模式-1.md) * [深刻剖析activity启动模式-2](Activity/深刻剖析activity启动模式-2.md) * [深刻剖析activity启动模式-3](Activity/深刻剖析activity启动模式-3.md) * [Activity Task和Process之间的关系](Activity/Activity Task和Process.md) * [为什么service里面startActivity抛异常](Activity/为什么service里面startActivity抛异常.md) * [App优雅退出](Activity/App优雅退出.md) * [onCreate源码分析](Activity/onCreate源码分析.md) * [Service相关面试题](Service/README.md) * [IntentService源码分析](Service/IntentService源码分析.md) * [Service是否在main thread中执行, service里面是否能执行耗时的操作?](Service/Android面试题-Service.md) * [Android面试题-Service不死之身](Service/Android面试题-Service不死之身.md) * [与XMPP相关面试题](网络编程/README.md) * [阐述一下对XMPP协议理解以及优缺点?](网络编程/阐述一下对XMPP协议理解以及优缺点?.md) * [简单阐述一下及时推送原理?](网络编程/简单阐述一下及时推送原理?.md) * [与性能优化相关面试题](性能优化/README.md) * [内存泄漏和内存溢出区别](性能优化/与性能优化相关试题一.md) * [UI优化和线程池实现原理](性能优化/与性能优化相关试题二.md) * [代码优化](性能优化/与性能优化相关试题三.md) * [Android应用UI性能分析](性能优化/Android应用UI性能分析.md) * [内存泄漏监测](性能优化/内存泄漏监测.md) * [App应用启动分析与优化](性能优化/App应用启动分析与优化.md) * [与IPC机制相关面试题](性能优化/与IPC机制相关面试题.md) * [与登录相关面试题](登陆注册/README.md) * [Oauth的实现原理](登陆注册/Oauth的实现原理.md) * [Token的实际意义](登陆注册/Token的实际意义.md) * [微信扫码登录内部实现原理](登陆注册/微信扫码登录内部实现原理.md) * [与开发相关面试题](开发遇到的问题/README.md) * [迭代开发的时候如何向前兼容新旧接口?](开发遇到的问题/迭代开发的时候如何向前兼容新旧接口?.md) * [手把手教你如何解决as jar包冲突](开发遇到的问题/手把手教你如何解决as jar包冲突.md) * [Context原理分析](开发遇到的问题/Context原理分析.md) * [解决ViewPager.setCurrentItem中间很多页面切换方案](开发遇到的问题/终极解决ViewPager.setCurrentItem中间页面过多解决方案.md) * [解决字体适配](开发遇到的问题/解决字体适配.md) * [软键盘顶出去解决方案](开发遇到的问题/软键盘顶出去解决方案.md) * [机型适配之痛](开发遇到的问题/机型适配之痛.md) * [ViewPager和Fragment使用过程中会遇到哪些问题](开发遇到的问题/ViewPager和Fragment使用过程中会遇到哪些问题.md) * [与人事相关面试题](HR/README.md) * [人事面试宝典一之自我介绍](HR/人事面试宝典一之自我介绍.md) * [人事面试宝典二之离职](HR/人事面试宝典二之离职.md) * [人事面试宝典](HR/人事面试宝典.md) * [网络编程](网络编程/README.md) * [Android客户端和服务端如何使用Token和Session](网络编程/Android客户端和服务端如何使用Token和Session.md) * [推送原理](网络编程/推送原理.md) * [Java面试题](Java/README.md) * [深拷贝浅拷贝](Java/深拷贝浅拷贝.md) * [数据库求差](Java/数据库求差.md) * [Java面试题-1](Java/Java面试题-1.md) * [Java面试题-2](Java/Java面试题-2.md) * [Java高级软件工程师面试考纲](Java/Java高级软件工程师面试考纲.md) * [66道经典的Java基础面试题集锦](Java/66道经典的Java基础面试题集锦.md) * [115个Java面试题及回答](Java/115个Java面试题及回答.md) * [J2SE基础面试核心内容](Java/J2SE基础面试核心内容.md) * [J2SE高级面试核心内容](Java/J2SE高级面试核心内容.md) * [面试技巧](面试技巧/README.md) * [程序员面试宝典](面试技巧/程序员面试宝典.md) * [罗永浩新东方万字求职信](面试技巧/罗永浩新东方万字求职信.md) * [我在面试中最喜欢问开发者的问题,和回答思路](面试技巧/我在面试中最喜欢问开发者的问题,和回答思路.md) * [经验分享](经验分享/README.md) * [我为什么要离开华为?](经验分享/我为什么要离开华为?.md) * [工作三年后,我选择离开腾讯](经验分享/工作三年后,我选择离开腾讯.md) * [一个程序员的血泪史](经验分享/一个程序员的血泪史.md) * [我为什么离开锤子科技?](经验分享/我为什么离开锤子科技?.md) * [互联网巨头BAT3内部员工的真实状况](经验分享/互联网巨头BAT3内部员工的真实状况.md) * [扫清Android面试障碍](经验分享/扫清Android面试障碍.md) * [史上最全 Android 面试资料集合](经验分享/史上最全 Android 面试资料集合.md) * [2016年4月某公司面试题及面试流程](经验分享/2016年4月某公司面试题及面试流程.md) * [2017届实习生招聘面经](经验分享/2017届实习生招聘面经.md) * [国内一线互联网公司内部面试题库](经验分享/国内一线互联网公司内部面试题库.md) * [互联网公司面试经验总结](经验分享/互联网公司面试经验总结.md) * [一个五年Android开发者百度、阿里、聚美、映客的面试心经](经验分享/一个五年Android开发者百度、阿里、聚美、映客的面试心经.md) * [腾讯公司程序员面试题及答案详解](经验分享/腾讯公司程序员面试题及答案详解.md) * [面试心得与总结:BAT、网易、蘑菇街 ](经验分享/面试心得与总结:BAT、网易、蘑菇街 .md) * [阿里+百度+CVTE面经合集](经验分享/阿里+百度+CVTE面经合集.md) * [技术硬碰硬—阳哥带你玩转上海Android招聘市场](经验分享/技术硬碰硬—阳哥带你玩转上海Android招聘市场.md) * [Android 曲折的求职之路](经验分享/Android 曲折的求职之路.md) * [杭州找 Android 工作的点点滴滴](经验分享/杭州找 Android 工作的点点滴滴.md) * [给培训班出来的一点不成熟的小建议](经验分享/给培训班出来的一点不成熟的小建议.md) * [Android 暑期实习生面试经验谈](经验分享/Android 暑期实习生面试经验谈.md) ================================================ FILE: docs/android/Android-Interview/Service/Android面试题-Service.md ================================================ **Android面试题-Service是否在main thread中执行, service里面是否能执行耗时的操作?** Service不是独立的进程,也不是独立的线程,它是依赖于应用程序的主线程的,也就是说,在更多时候不建议在Service中编写耗时的逻辑和操作(比如:网络请求,拷贝数据库,大文件),否则会引起ANR。 如果想在服务中执行耗时的任务。有以下解决方案: 1) 在service中开启一个子线程 ``` new Thread(){}.start(); ``` 2) 可以使用IntentService异步管理服务 参考文章IntentService的使用: [http://blog.csdn.net/mwq384807683/article/details/72549222](http://blog.csdn.net/mwq384807683/article/details/72549222) Service 和 Activity 在同一个线程,对于同一 app 来说默认情况下是在同一个线程中的 main Thread (UI Thread) ================================================ FILE: docs/android/Android-Interview/Service/Android面试题-Service不死之身.md ================================================ ### 1. 在onStartCommand方法中将flag设置为START_STICKY; ``` return Service.START_STICKY; ``` ### 2. 在xml中设置了android:priority ```xml ``` ### 3. 在onStartCommand方法中设置为前台进程 ```java @Override public int onStartCommand(Intent intent, int flags, int startId) { Notification notification = new Notification(R.mipmap.ic_launcher, "服务正在运行",System.currentTimeMillis()); Intent notificationIntent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,notificationIntent,0); RemoteViews remoteView = new RemoteViews(this.getPackageName(),R.layout.notification); remoteView.setImageViewResource(R.id.image, R.mipmap.ic_launcher); remoteView.setTextViewText(R.id.text , "Hello,this message is in a custom expanded view"); notification.contentView = remoteView; notification.contentIntent = pendingIntent; startForeground(1, notification); return Service.START_STICKY; } ``` ### 4. 在onDestroy方法中重启service ```java @Override public void onDestroy() { super.onDestroy(); startService(new Intent(this, MyService.class)); } ``` ### 5. 用AlarmManager.setRepeating(...)方法循环发送闹钟广播,接收的时候调用service的onstart方法 ```java Intent intent = new Intent(MainActivity.this,MyAlarmReciver.class); PendingIntent sender = PendingIntent.getBroadcast( MainActivity.this, 0, intent, 0); // We want the alarm to go off 10 seconds from now. Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); calendar.add(Calendar.SECOND, 1); AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE); //重复闹钟 /** * @param type * @param triggerAtMillis t 闹钟的第一次执行时间,以毫秒为单位 * go off, using the appropriate clock (depending on the alarm type). * @param intervalMillis 表示两次闹钟执行的间隔时间,也是以毫秒为单位 * of the alarm. * @param operation 绑定了闹钟的执行动作,比如发送一个广播、给出提示等等 */ am.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), 2 * 1000, sender); ``` ### 6. 目前市场面的很多三方的消息推送SDK唤醒APP,例如Jpush ![img](http://upload-images.jianshu.io/upload_images/4037105-ac7fa1011763eae3?imageMogr2/auto-orient/strip) ### **总结** 这纯粹是面试的时候忽悠一下面试官,不代表着你的Service就永生不死了,只能说是提高了进程的优先级。迄今为止我没有发现能够通过常规方法达到流氓需求(通过长按home键清除都清除不掉)的方法,目前所有方法都是指通过Android的内存回收机制和普通的第三方内存清除等手段后仍然保持运行的方法,有些手机厂商把这些知名的app放入了自己的白名单中,保证了进程不死来提高用户体验(如微信、QQ、陌陌都在小米的白名单中)。如果从白名单中移除,他们终究还是和普通app一样躲避不了被杀的命运。 ================================================ FILE: docs/android/Android-Interview/Service/IntentService源码分析.md ================================================ IntentService是继承于Service并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作,启动IntentService的方式和启动传统Service一样,同时,当任务执行完后,IntentService会自动停止,而不需要我们去手动控制。另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandleIntent回调方法中执行,并且,每次只会执行一个工作线程,执行完第一个再执行第二个,以此类推。 而且,所有请求都在一个单线程中,不会阻塞应用程序的主线程(UI Thread),同一时间只处理一个请求。 ### **IntentService有什么好处呢?** 1)我们省去了在Service中手动开线程的麻烦, 2)当操作完成时,我们不用手动停止Service。 接下来让我们来看看如何使用,写一个Demo来模拟两个耗时操作,Operation1与Operation2,先执行1,2必须等1执行完才能执行2: 新建工程,新建一个继承IntentService的类,我这里是IntentServiceDemo.java ```java public class IntentServiceDemo extends IntentService { public IntentServiceDemo() { //必须实现父类的构造方法 super("IntentServiceDemo"); } @Override public IBinder onBind(Intent intent) { System.out.println("onBind"); return super.onBind(intent); } @Override public void onCreate() { System.out.println("onCreate"); super.onCreate(); } @Override public void onStart(Intent intent, int startId) { System.out.println("onStart"); super.onStart(intent, startId); } @Override public int onStartCommand(Intent intent, int flags, int startId) { System.out.println("onStartCommand"); return super.onStartCommand(intent, flags, startId); } @Override public void setIntentRedelivery(boolean enabled) { super.setIntentRedelivery(enabled); System.out.println("setIntentRedelivery"); } @Override protected void onHandleIntent(Intent intent) { //Intent是从Activity发过来的,携带识别参数,根据参数不同执行不同的任务 System.out.println("currentThread()=" + Thread.currentThread().getName()); String action = intent.getExtras().getString("param"); if (action.equals("oper1")) { System.out.println("Operation1"); }else if (action.equals("oper2")) { System.out.println("Operation2"); } try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void onDestroy() { System.out.println("onDestroy"); super.onDestroy(); } } ``` 我把生命周期方法全打印出来了,待会我们来看看它执行的过程是怎样的。接下来是Activity,在Activity中来启动IntentService: ```java public class TestActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //可以启动多次,每启动一次,就会新建一个work thread,但IntentService的实例始终只有一个 //Operation 1 Intent startServiceIntent = new Intent("com.test.intentservice"); Bundle bundle = new Bundle(); bundle.putString("param", "oper1"); startServiceIntent.putExtras(bundle); startService(startServiceIntent); //Operation 2 Intent startServiceIntent2 = new Intent("com.test.intentservice"); Bundle bundle2 = new Bundle(); bundle2.putString("param", "oper2"); startServiceIntent2.putExtras(bundle2); startService(startServiceIntent2); } } ``` 最后,别忘了配置Service,因为它继承于Service,所以,它还是一个Service,一定要配置,否则是不起作用的 ```xml ``` 最后来看看执行结果: ![img](http://upload-images.jianshu.io/upload_images/4037105-317f5bb26ae43dd3?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 从结果可以看到,onCreate方法只执行了一次,而onStartCommand和onStart方法执行了两次,开启了两个Work Thread,这就证实了之前所说的,启动多次,但IntentService的实例只有一个,这跟传统的Service是一样的。Operation1也是先于Operation2打印,并且我让两个操作间停顿了2s,最后是onDestroy销毁了IntentService。 ### **IntentService 源码分析** ```java @Override public void onCreate() { super.onCreate(); HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); thread.start(); mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); } ``` #### **源码可知:** 1)实际上是使用了一个 HandlerThread 来维护线程的, 2) HandleThread 中,内部已经维护一个 Looper,这里直接使用 HandlerThread 的 Looper 对象,便于在 IntentService 中去维护消息队列, 3)创建的 mServiceHandler 是属于 HandleThread 这个 WorkerThread 的。 ```java private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { onHandleIntent((Intent)msg.obj); stopSelf(msg.arg1); } } ``` #### **源码可知:** 1)直接把消息交给 onHandleIntent() 方法去执行具体的业务逻辑 2)执行完成之后,立即调用 stopSelf() 方法停止自己 接下来分析start源码 ```java @Override public void onStart(Intent intent, int startId) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; mServiceHandler.sendMessage(msg); } @Override public int onStartCommand(Intent intent, int flags, int startId) { onStart(intent, startId); return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; } ``` #### 源码可知 1)在 onStartCommand() 中直接调用了 onStart() 方法 2)而上面 stopSelf() 方法使用的 startId 来停止当前的此次任务服务。 3)而 Service 如果被启动多次,就会存在多个 startId ,当所有的 startId 都被停止之后,才会调用 onDestory() 自我销毁。 我们在看看HandlerThread启动之后的源码 ```java @Override public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; } ``` #### **源码可知** run方法里面添加了锁,这也解释了为什么多次 start 同一个 IntentService 它会顺序执行,全部执行完成之后,再自我销毁。 ================================================ FILE: docs/android/Android-Interview/Service/README.md ================================================ ## Service相关面试题 - [IntentService源码分析](IntentService源码分析.md) - [Service是否在main thread中执行, service里面是否能执行耗时的操作?](Android面试题-Service.md) - [Android面试题-Service不死之身](Android面试题-Service不死之身.md) ================================================ FILE: docs/android/Android-Interview/book.json ================================================ { "author": "JackChan", "description": "Android面试宝典", "gitbook": "3.2.3", "language": "zh-hans", "title": "Android面试宝典", "pdf": { "fontFamily": "等线" } } ================================================ FILE: docs/android/Android-Interview/开发遇到的问题/Context原理分析.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### 本文配套视频 - [配套视频](https://v.qq.com/x/page/y0396os8vc6.html) ## 谈一下你对Android中的context的理解,在一个应用程序中有多少个context实例? ### 1. 什么是Context? ![img](http://upload-images.jianshu.io/upload_images/4037105-bf283f5324e5a188.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 通过金山词霸解释:上下文环境,什么是环境,这个词只可意会不可言传,为了大家更好的理解,举一个栗子,比如:我想点鸡,我在麦当劳跟服务员说我想点鸡,服务员给端上来一只香喷喷的烤鸡,如下图: ![img](http://upload-images.jianshu.io/upload_images/4037105-3f21cf0410b902fd.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 但是我换一个环境 ,去红灯区点鸡,妈咪就会给带来一只呆萌可爱的失足少女,如下图: ![img](http://upload-images.jianshu.io/upload_images/4037105-24dddb07d9119c2a.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 这就是环境,一样的东西不同地方,就表示不一样的意思。 ### Context,中文直译为“上下文”,SDK中对其说明如下: Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc ### 从上可知一下三点,即: - 它描述的是一个应用程序环境的信息,即上下文。 - 该类是一个抽象(abstract class)类,Android提供了该抽象类的具体实现类(后面我们会讲到是ContextIml类)。 - 通过它我们可以获取应用程序的资源和类,也包括一些应用级别操作,例如:启动一个Activity,发送广播,接受Intent信息 等 首先看它们的继承关系 ![img](http://upload-images.jianshu.io/upload_images/4037105-3f6dfbce58383d95.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ### 2. 什么时候创建Context实例 熟悉了Context的继承关系后,我们接下来分析应用程序在什么情况需要创建Context对象的?应用程序创建Context实例的情况有如下几种情况: 1) 创建Application 对象时, 而且整个App共一个Application对象 2) 创建Service对象时 3) 创建Activity对象时 ### 因此应用程序App共有的Context数目公式为: > 总Context实例个数 = Service个数 + Activity个数 + 1(Application对应的Context实例) 1、创建Application对象的Context: 首先新建一个MyApplication并让它继承自Application,然后在AndroidManifest.xml文件中对MyApplication进行指定,如下所示: ```xml ...... ``` 指定完成后,当我们的程序启动时Android系统就会创建一个MyApplication的实例,通过如下代码获取到它的实例: ```java public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyApplication myApp = (MyApplication) getApplication(); Log.d("TAG", "getApplication is " + myApp); } } ``` 可以看到,代码很简单,只需要调用getApplication()方法就能拿到我们自定义的Application的实例了,打印结果如下所示: ![img](http://upload-images.jianshu.io/upload_images/4037105-bf6808194c852db6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 那么除了getApplication()方法,其实还有一个getApplicationContext()方法,这两个方法看上去好像有点关联,那么它们的区别是什么呢?我们将代码修改一下: ```java public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyApplication myApp = (MyApplication) getApplication(); Log.d("TAG", "getApplication is " + myApp); Context appContext = getApplicationContext(); Log.d("TAG", "getApplicationContext is " + appContext); } } ``` 同样,我们把getApplicationContext()的结果打印了出来,现在重新运行代码,结果如下图所示: ![img](http://upload-images.jianshu.io/upload_images/4037105-308f49c6b7acc8f2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 打印出的结果是一样的呀,连后面的内存地址都是相同的,看来它们是同一个对象。其实这个结果也很好理解,Application本身就是一个Context,所以这里获取getApplicationContext()得到的结果就是MyApplication本身的实例。 那么有的朋友可能就会问了,既然这两个方法得到的结果都是相同的,那么Android为什么要提供两个功能重复的方法呢?实际上这两个方法在作用域上有比较大的区别。getApplication()方法的语义性非常强,一看就知道是用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法了,如下所示: ```java public class MyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { MyApplication myApp = (MyApplication) context.getApplicationContext(); Log.d("TAG", "myApp is " + myApp); } } ``` 也就是说,getApplicationContext()方法的作用域会更广一些,任何一个Context的实例,只要调用getApplicationContext()方法都可以拿到我们的Application对象。 - 欢迎关注微信公众号,长期推荐技术文章和技术视频 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/开发遇到的问题/README.md ================================================ ## 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口?](迭代开发的时候如何向前兼容新旧接口?.md) - [手把手教你如何解决as jar包冲突](手把手教你如何解决as jar包冲突.md) - [Context原理分析](Context原理分析.md) - [解决ViewPager.setCurrentItem中间很多页面切换方案](终极解决ViewPager.setCurrentItem中间页面过多解决方案.md) - [解决字体适配](解决字体适配.md) - [软键盘顶出去解决方案](软键盘顶出去解决方案.md) - [机型适配之痛](机型适配之痛.md) ================================================ FILE: docs/android/Android-Interview/开发遇到的问题/ViewPager和Fragment使用过程中会遇到哪些问题.md ================================================ **ViewPager和Fragment使用过程中会遇到哪些问题** ### 1. 适配器的选择 使用ViewPager加载多个Fragment时,我一般选择FragmentPagerAdapter。 需要大家注意: FragmentPagerAdapter:该类内的每一个生成的 Fragment 都将保存在内存之中,因此适用于那些相对静态的页,数量也比较少的场景 但是,如果需要处理有很多页,并且数据动态性较大、占用内存较多的情况,这时候,我们选择怎么是的适配器呢? 应该使用FragmentStatePagerAdapter FragmentStatePagerAdapter:会把已经创建的Fragment进行保存。会保持Fragment的状态 通过源码分析发现: FragmentStatePagerAdapter这个类抽象出了一个getItem()方法,用于创建对应的Fragment 而FragmentStatePagerAdapter的.instantiateItem()方法实现中,调用了getIitem()方法。 调用该instantiateItem()方法时,判断一下要生成的 Fragment 是否已经生成过了,如果生成过了,就使用旧的,旧的将被 Fragment.attach(); 如果没有,就调用 getItem() 生成一个新的,新的对象将被 FragmentTransation.add()。 FragmentStatePagerAdapter会将所有生成的 Fragment 对象通过 FragmentManager 保存起来备用,以后需要该 Fragment 时,都会从 FragmentManager 读取,而不会再次调用 getItem() 方法。 ### 2. Fragment数据的缓存 Fragment在初始化View对象,把该对象作为一个成员变量进行保存。 再一次初始化Fragment对应的View对象时,判断当前成员变量View对象是否为空。 如果为空,创建新的View对象,否则,不在创建。这样就可以做到对Fragemnt进行数据缓存 这样做的好处:避免了每次加载Fragment都要重新创建View,加载数据了。提高了性能,以及减少了内存的开销。 ### 3. ViewPager预加载 我们知道,系统的ViewPager默认提供预加载机制。但是,根据业务需要,取消掉对应的预加载机制。 可以这样做:替换掉系统原生的Viewpager类。将该类中的一个变量mOffscreenPageLimit 设置为0,不进行预加载 ### 4. Fragment嵌套Fragment 在Fragment中嵌套Fragment时,一定要使用getChildFragmentManager(); 否则,会在ViewPager中出现fragment不会加载的情况,即fragment出现空白页的情况。 ================================================ FILE: docs/android/Android-Interview/开发遇到的问题/手把手教你如何解决as jar包冲突.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) 第一:当出现这个错误就是jar包冲突。 ![img](http://upload-images.jianshu.io/upload_images/4037105-9147f9c33825f794.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 第二:解决办法,双击Shift搜索,会发现如下图片: ![img](http://upload-images.jianshu.io/upload_images/4037105-01ec162adcd79502.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 搜索你会发现我的项目里面添加了okhttp3.6,bomb里面也添加了okhttp3.3.1,这时候系统就不知道该用哪个jar冲突了。 第三:打开项目的扩展库,如下图展示: ![img](http://upload-images.jianshu.io/upload_images/4037105-d952e4f45f66ce3d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 会发现bomb自带了okhttp的jar包,如图片展示。 ![img](http://upload-images.jianshu.io/upload_images/4037105-cee4021279d743d6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 我的项目也再带okhttp的jar包,如图片展示。 ![img](http://upload-images.jianshu.io/upload_images/4037105-c05678f43cb13f4d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 第四:找到我的build.gradle文件,去掉刚刚冲突的jar包,如下展示: ```gradle //bmob-sdk:Bmob的android sdk包,包含了Bmob的数据存储、文件等服务,以下是最新的bmob-sdk: compile ('cn.bmob.android:bmob-sdk:3.5.5'){ // gson-2.6.2 exclude group: 'com.squareup.okhttp3' exclude group: 'com.squareup.okio' exclude group: 'com.google.code.gson' // exclude(module: 'gson') // 防止版本冲突 } // 网络加载 compile ('com.squareup.okhttp3:okhttp:3.6.0'){ exclude group: 'com.squareup.okhttp3' exclude group: 'com.squareup.okio' } ``` 至此解决冲突。 - 欢迎关注微信公众号,长期推荐技术文章和技术视频 - 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/开发遇到的问题/机型适配之痛.md ================================================ 由于开源三方定制系统较多,请大家详细描述场景、机型及解决方案,方便其他朋友参考 [问答]-Android开发中有哪些兼容性问题?都是怎么解决的? [问答] 你在工作中遇到的最复杂的问题或者bug是什么?你是怎么搞定的? #### **华为P6和P7** 场景:使用MIPush,在华为部分手机上无法推送成功。 机型:[华为P6,华为P7] 解决方案:P6和P7是华为的高端机型,不允许推送,防止骚扰用户,无解。 #### **魅族3和魅族4** 场景:魅族手机ListView的Item中的EditText无法编辑,点击EditText弹出软键盘后,软键盘会立即自动隐藏 机型:[魅族3,魅族4] 解决方案: 方法一:将ListView换成RecyclerView 方法二:[https://github.com/Aspsine/EditTextInListView](https://github.com/Aspsine/EditTextInListView) #### **HTC M8** 场景:HTC M8 从一个Activity 使用QQSDK 登陆, 登陆成功后, 返回Activity结果Activity 被销毁了 机型:HTC M8 等某些带有 虚拟 Menu 键盘的手机 解决方案:后来调查发现是这个Activity是全屏,屏蔽了Menu键盘的黑条. 但是跳转到QQ却把那个Menu的黑条显示了出来, 这导致发生了 screenSize 的变化 从而导致我的Activity销毁了. 知道了这个原因, 在manifest中的 configChanges 添加screenSize 解决了这个问题. #### **所有android4.4机型** 场景:Android4.4系统使用了SystemBarTintManager库修改透明状态栏后,会导致根布局从屏幕顶端开始布局,而不是从ActionBar开始布局 机型:所有android4.4机型 解决方案: 方法一:针对4.4创建一套额外的布局,即layout-v19文件夹,并且在根布局外层再套一层LinearLayout,并在LinearLayout中添加一个属性android:fitsSystemWindows="true" 方法二:是为4.4及以上添加了paddingTop去适配,添加layout觉得不好适配。 方法三:在Build.VERSION.SDK_INT <= 18的版本中,通过colorDrawable.setAlpha(alpha);设置actionbar背景色透明度的时候,colorDrawable需要设置callback。 ```java final Drawable.Callback mDrawableCallback = new Drawable.Callback() { @Override public void invalidateDrawable(Drawable who) { getActionBar().setBackgroundDrawable(who); } @Override public void scheduleDrawable(Drawable who, Runnable what, long when) { } @Override public void unscheduleDrawable(Drawable who, Runnable what) { } }; colorDrawable.setCallback(mDrawableCallback); ``` #### **魅族MX3** 场景:Camera拍摄,用setPreviewFormat设置成YV12,预览会变成绿屏,实际用getPreviewFormat显示是支持YV12的 机型:魅族MX3 解决方案:没办法只能设置成NV21了 #### **其代表机型为:三星I8258、华为H30-T00、红米等。** Camera常见问题: 1)Intent调用手机内相机程序 ![img](http://upload-images.jianshu.io/upload_images/4037105-b40e3d256c6f4e90?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 如果我们设置了照片的存储路径,那么很可能会遇到一下三种问题: 问题一:onActivityResult方法中的data返回为空(数据表明,93%的机型的data将会是Null,所以如果我们指定了路径,就不要使用data来获取照片,起码在使用前要做空判断)。 问题二:照片无法存储。 如果自定义存储路径是/mnt/sdcard/lowry/,而手机SD卡下在拍照前没有名为lowry的文件夹,那么部分手机拍照后图片不会保存,导致我们无法获得照片,大多数手机的相机遇到文件夹不存在的情况都会自己创建出不存在的文件夹,而个别手机却不会创建, 解决的方法就是在指定存储路径前先判断路径中的文件夹是否都存在,不存在先创建再调用相机。 问题三:照片可以存储,但是名字不对。 file:///mnt/sdcard/123 1.jpg,由于URI的fromFile方法会将路径中的空格用“%20”取代。 其实对于大多数的手机这都不算事,手机在解析存储路径的时候都会将“%20”替换为空格,这样实际上最终的照片名字还是我们当初指定的名字:123 1.jpg,遗憾的是个别手机(如酷派7260)系统自带的相机没有将“%20”读成空格,拍照后的照片的名字是123%201.jpg,我们用路径“file:///mnt/sdcard/123 1.jpg”能找到照片才怪! ![img](http://upload-images.jianshu.io/upload_images/4037105-67bd68a83d2ee7e8?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![img](http://upload-images.jianshu.io/upload_images/4037105-5f1fa797dfb56447?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 总结: (1)使用onActivityResult中的intent(data)前要做空判断。 (2)指定拍照路径时,先检查路径中的文件夹是否都存在,不存在时先创建文件夹再调用 相机拍照。 (3)指定拍照存储路径时,照片的命名中不要包含空格等特殊符号。 #### **所有手机** PopupWindow中嵌套EditText,会出现EditText长按无法触发“粘贴”选项,可以改成Dialog嵌套EditText,包括DialogFragment。 #### **三星Note系列,S系列** 会调用Activity的onPause和onStop方法.其他手机会保持在onResume状态 #### **酷派8720L** 场景:在获取系统相机拍照然后保存在本地有时候会保存不上,获取不到地址。 问题原因:通过调试发现当拍完照返回的时候自己设的成员变量值会被回收,估计就是内存不足的原因。重启机器后就好了。 解决方案:无方案。 #### **所有手机** 场景:输入法中的emoji适配,Android4.1之前的系统不支持emoji显示 解决方案:所以对于Android4.1之前的系统,我采用了bitmap来显示emoji。 #### **三星手机** 问题: (1) 摄像头拍照后图片数据不一定能返回 ; onActivityResult的data为空 (2) 三星的camera强制切换到横屏 导致Activity重启生命周期 (但是部分机型 配置 android:configChanges 也不能阻止横竖屏切换); (3) APP Activity A调用系统拍照 --> 拍照 --> 在拍好照片的界面做几次横竖屏转换 --> 返回APP界面Activity A ,A 被销毁。 解决方案:如果 activity 的销毁如果无法避免 那么在activity销毁之前调用 onSaveInstanceState 保存图片的路径 当activity重新创建的时候 会将 onSaveInstanceState 保存的文件传递给onCreate()当中 在onCreate当中 检查照片的地址是否存在文件 以此来判定拍照是否成功 Demo 下载地址: [http://download.csdn.NET/detail/aaawqqq/7653475](http://download.csdn.net/detail/aaawqqq/7653475) #### **OPPO 手机** 场景:OPPO 手机启动 Service 报 SecurityException 解决方案:try catch 该异常 #### **HTC Desire 820、Lenovo A320T** 场景:这个问题主要在部分机型的4.X系统上遇见,小图标大小没有按照24dp裁剪,而是采用了桌面图标一样的大小96dp ![img](http://upload-images.jianshu.io/upload_images/4037105-1c84de8e950b9a09?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 解决方案:按照标准来,小图标大小为24dp,大图标为桌面icon图标大小96dp #### **魅族5.X手机,大图显示问题** 场景:Flyme系统对原生Android源码做了修改,采用BigPictureStyle方式显示大图通知栏的时候,消息与大图重合了,如下图。 ![img](http://upload-images.jianshu.io/upload_images/4037105-0e926f46ba301bad?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 解决方案 首先,通过BigPictureStyle来实现大图功能肯定是走不通的,因为事实就摆着行不通的嘛。京东的App肯定是通过RemoteViews来实现的。于是,开始走弯路,尝试通过RemoteViews来展示大图。但是谷歌规定,自定义布局展示的通知栏消息最大高度是64dp。那么,京东的App是怎么实现的?在尝试了各种方法以后,最后又是通过投机取巧的方式解决了问题 ```java private void showBigPictureNotificationWithMZ(Context context) { NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); Notification.Builder builder = new Notification.Builder(context); Notification notification = generateNotification(builder); notification.bigContentView = mRemoteViews; notificationManager.notify(notifyId, notification); } ``` 需要先生成Notification的实例,然后手动给notification.bigContentView赋值,再notify,就可以了 #### **问题二:顶部状态栏(StatusBar)小图标显示异常** 场景:当通知来的时候,如果不在通知栏浏览,会在顶部状态栏出现一个向上翻滚动画的通知消息,这条通知消息左边是一个小图标。部分系统这个小图标显示异常,是一个纯灰色的正方形,如下图。 ![img](http://upload-images.jianshu.io/upload_images/4037105-5b25b99da22ce685?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 解决方案 首先产生灰色图标的原因就是5.0系统引入了材料设计,谷歌强制使用带有alpha通道的图标,并且RGB的alpha值必须是0(实测不为0也是可以的,但系统会忽略所有RGB值)。因此,使用JPG的图片是不行的,最好的代替方案就是一张背景透明的PNG图片。 #### **问题三:Android 7.X机型,通知栏小图标显示成灰色** 问题详情 这个问题跟第二个有点类似,在7.0系统及以上,有部分应用的小图标是灰色的,大图可以正常显示。碰巧的是,显示异常的小图标,颜色都是灰色的。 ![img](http://upload-images.jianshu.io/upload_images/4037105-215d3c7a9e1b8401?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 解决方案 与小图标显示异常解决方案类似,将小图标替换为透明背景的PNG图片。 #### **问题四:RemoteViews显示异常** 问题详情 由于系统提供的通知栏消息类型有时候不能满足要求,部分通知栏消息采用自定义RemoteViews来实现。采用RemoteViews,特别是手动生成Bitmap然后直接传给一个自定义Layout,再通过setContentView方式设置通知栏消息时,会存在各种各样的坑。 Android通知栏的背景色有几种情况,白色、暗色、暗色透明和黑色。如果生成的Bitmap带背景色,这个背景色就很难选择。如果选择黑色背景,那么在白色通知栏的机型上就很难看。因此不能完全在各个系统上面完美展示出来。如果不带背景色,那么字体颜色也面临同样的困惑。试想,如果在白色的背景上显示白色的文字,用户看到白茫茫一片,是什么感受? ![img](http://upload-images.jianshu.io/upload_images/4037105-50ad2e386e45a2ce?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 另一方面,大部分厂商对原生的Android系统都会有各种各样的改造,通知栏的样式也不例外。如果按照原生的样式来设计,那么在大部分国内厂商的机子上显示都和正常的普通通知栏消息不一样。例如华为6.0系统的机子,原生系统的时间线在右上角,华为的在左边,这样会给用户带来错觉。 ![img](http://upload-images.jianshu.io/upload_images/4037105-9f8029ebd65d19d8?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 解决方案 详见RemoteViews适配一节。 #### **问题五:通知栏更新频率** 问题详情 每个应用基本都有自更新的逻辑,App开机的时候提示用户升级,点击升级按钮后在Notification出现一个下载带进度条的通知。应用一般是在开启一个工作线程在后台下载,然后在下载的过程中通过回调更新通知栏中的进度条。我们知道,下载进度的快慢是不可控的,如果每次下载中的回调都去更新通知栏,那么可能几百毫秒、几十毫秒、甚至几毫秒就更新一次通知栏,应用可能就会ANR,甚至崩溃。 解决方案 控制通知栏更新频率,一般控制在0.5s或者1s就可以了。在某一个更新时间间隔内下载的进度回调直接丢弃,需要注意的是下载完成的回调,需要实时回调通知栏消息显示下载完成。 问题六:恶心的后台通知和“守护”通知 问题详情 但凡存在后台通知或者“守护”通知的应用,在7.0系统以后都会原形毕露. ![img](http://upload-images.jianshu.io/upload_images/4037105-4fc6ee347dc92b25?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![img](http://upload-images.jianshu.io/upload_images/4037105-6f2534065f0327a8?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 解决方案:无 #### **小米推送SDK接入问题** 问题详情 为了提升推送到达,考拉接入了小米推送的SDK。小米推送分为通知栏消息和透传消息,通知栏消息属于系统级推送,在MIUI的机子上可以在进程被杀死的情况下也能收到应用推送。然而有个问题,小米认为应用在前台时,不会回调任何方法;小米认为应用在后台的时候,收到通知栏消息的同时,会回调onNotificationMessageArrived方法。这时候就要小心翼翼地处理这条消息了。因为如果你的应用前后台判断逻辑和小米的不一样,那么就有可能小米帮你发了一条通知栏消息,你自己又发了一遍,造成通知栏消息的重复发送(这个坑考拉踩过T_T)。另一方面,在7.0系统的机子上,主标题和小图标的颜色是可以改变的,目前小米推送SDK没有开放这个接口供调用方定制。 解决方案 目前只能解决第一个问题——前后台判断的问题。应用是否在后台可以根据以下代码进行判断。在Android 5.0以上,可以通过ActivityManager.RunningAppProcessInfo判断,Android 5.0及以下版本通过ActivityManager.RunningTaskInfo判断。经测试,这个方案在Android 4.4以上结果是可以完全匹配的。 ```java public static boolean isAppInBackgroundInternal(Context context) { ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { List runningProcesses = manager.getRunningAppProcesses(); if (!ListUtils.isEmpty(runningProcesses)) { for (ActivityManager.RunningAppProcessInfo runningProcess : runningProcesses) { if (runningProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { return false; } } } } else { List task = manager.getRunningTasks(1); if (!ListUtils.isEmpty(task)) { ComponentName info = task.get(0).topActivity; if (null != info) { return !isKaolaProcess(info.getPackageName()); } } } return true; } ``` #### **Android通知栏适配** RemoteViews适配 由于系统自带的通知栏消息样式不能完全满足产品们脑洞大开的需求,有时候我们需要自定义布局样式展示通知栏消息。Android系统可以将自定义布局通过setContent(7.X系统推荐使用setCustomContentView)设置到Notification.Builder中,来实现样式的更变。setContent方法需要传入一个RemoteViews对象,它是一个普通的数据类型,不是View,作用是供其他进程展示视图。RemoteViews只支持4种基本的布局: FrameLayout LinearLayout RelativeLayout GridLayout 这些布局下面只支持几种视图控件: AnalogClock Button Chronometer ImageButton ImageView ProgressBar TextView ViewFlipper ListView GridView StackView AdapterViewFlipper 只能通过上述组合生成一个RemoteViews。 自定义布局与视图 除了上面提到的布局与控件,有没有办法自定义布局与视图呢?我们知道,任何一个View,都可以生成一个Bitmap对象,支持的视图控件里有ImageView,可以通过ImageView.setBitmapResource()将自定义视图设置到一个ImageView中,然后再随便放到一个布局上,就可以实现通知栏消息的任意布局。理想是美好的,但现实是残酷的。使用这种方式自定义的布局,会存在与原生的通知栏消息样式不一致的可能,包括小图标/大图标的大小,字体的大小与颜色,时间的显示方式(不同版本的时间显示位置和样式都不一样)。下面解决一个最关键,也最致命的问题——字体颜色。如果字体颜色和背景颜色一样,那这条通知栏消息就没法看了,如RemoteViews显示异常一节介绍的一样。 解决字体颜色和背景颜色一样的问题有三种解决方案,分别是: 背景色固定不透明,字体颜色与背景色形成反差。(360和京东的做法) 背景色透明,字体颜色采用系统原生的notification_style。 背景色透明,通过特殊方式拿到通知栏字体颜色和字体大小。 ![img](http://upload-images.jianshu.io/upload_images/4037105-3df9aac3a2381b77?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 其中,第一种方案简单,能够兼容所有厂商机型。例如京东固定背景色为黑色,字体为红色。这种方式的唯一缺陷是样式上不能与普通通知栏消息重合,在白色背景的通知栏上极为显眼。第二种方式,通过阅读源码可知,系统的通知栏标题和内容采用的颜色分别是@android:color/primary_text_dark和@android:color/secondary_text_dark,但踩过坑之后发现并非所有的机型默认都是这两个颜色,有可能获取不到值。因此这种方案只能作为参考,不能用于实际环境中。最后详细介绍一下第三种方式。 #### **Android默认字体颜色获取** 这种方案有一点投机取巧,是网上寻找代替方案时在简书上找到的,作者是hackware。思路就是通过Notification.Builder生成一条空的Notification,但不调用notify()方法,然后通过这条Notification想办法获取里面的布局元素,通过遍历,就能拿到对应的字体和颜色了。具体看代码: ```java private static final String NOTIFICATION_TITLE = "notification_title"; public static final int INVALID_COLOR = -1; // 无效颜色 private static int notificationTitleColor = INVALID_COLOR; // 获取到的颜色缓存 /** * 获取系统通知栏主标题颜色,根据Activity继承自AppCompatActivity或FragmentActivity采取不同策略。 * * @param context 上下文环境 * @return 系统主标题颜色 */ public static int getNotificationColor(Context context) { try { if (notificationTitleColor == INVALID_COLOR) { if (context instanceof AppCompatActivity) { notificationTitleColor = getNotificationColorCompat(context); } else { notificationTitleColor = getNotificationColorInternal(context); } } } catch (Exception ignored) { } return notificationTitleColor; } /** * 通过一个空的Notification拿到Notification.contentView,通过{@link RemoteViews#apply(Context, ViewGroup)}方法返回通知栏消息根布局实例。 * * @param context 上下文 * @return 系统主标题颜色 */ private static int getNotificationColorInternal(Context context) { Notification.Builder builder = new Notification.Builder(context); builder.setContentTitle(NOTIFICATION_TITLE); Notification notification = builder.build(); try { ViewGroup root = (ViewGroup) notification.contentView.apply(context, new FrameLayout(context)); TextView titleView = (TextView) root.findViewById(android.R.id.title); if (null == titleView) { iteratorView(root, new Filter() { @Override public void filter(View view) { if (view instanceof TextView) { TextView textView = (TextView) view; if (NOTIFICATION_TITLE.equals(textView.getText().toString())) { notificationTitleColor = textView.getCurrentTextColor(); } } } }); return notificationTitleColor; } else { return titleView.getCurrentTextColor(); } } catch (Exception e) { DebugLog.e(e.getMessage()); return getNotificationColorCompat(context); } } /** * 使用getNotificationColorInternal()方法,Activity不能继承自AppCompatActivity(实测5.0以下机型可以,5.0及以上机型不行), * 大致的原因是默认通知布局文件中的ImageView(largeIcon和smallIcon)被替换成了AppCompatImageView, * 而在5.0及以上系统中,AppCompatImageView的setBackgroundResource(int)未被标记为RemotableViewMethod,导致apply时抛异常。 * * @param context 上下文 * @return 系统主标题颜色 */ private static int getNotificationColorCompat(Context context) { try { Notification.Builder builder = new Notification.Builder(context); Notification notification = builder.build(); int layoutId = notification.contentView.getLayoutId(); ViewGroup root = (ViewGroup) LayoutInflater.from(context).inflate(layoutId, null); TextView titleView = (TextView) root.findViewById(android.R.id.title); if (null == titleView) { return getTitleColorIteratorCompat(root); } else { return titleView.getCurrentTextColor(); } } catch (Exception e) { } return INVALID_COLOR; } private static void iteratorView(View view, Filter filter) { if (view == null || filter == null) { return; } filter.filter(view); if (view instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) view; for (int i = 0; i < viewGroup.getChildCount(); i++) { View child = viewGroup.getChildAt(i); iteratorView(child, filter); } } } private static int getTitleColorIteratorCompat(View view) { if (view == null) { return INVALID_COLOR; } List textViews = getAllTextViews(view); int maxTextSizeIndex = findMaxTextSizeIndex(textViews); if (maxTextSizeIndex != Integer.MIN_VALUE) { return textViews.get(maxTextSizeIndex).getCurrentTextColor(); } return INVALID_COLOR; } private static int findMaxTextSizeIndex(List textViews) { float max = Integer.MIN_VALUE; int maxIndex = Integer.MIN_VALUE; int index = 0; for (TextView textView : textViews) { if (max < textView.getTextSize()) { // 找到字号最大的字体,默认把它设置为主标题字号大小 max = textView.getTextSize(); maxIndex = index; } index++; } return maxIndex; } /** * 实现遍历View树中的TextView,返回包含TextView的集合。 * * @param root 根节点 * @return 包含TextView的集合 */ private static List getAllTextViews(View root) { final List textViews = new ArrayList<>(); iteratorView(root, new Filter() { @Override public void filter(View view) { if (view instanceof TextView) { textViews.add((TextView) view); } } }); return textViews; } private interface Filter { void filter(View view); } ``` #### **RemoteViews适配方案** 获取系统通知标题颜色,如果能够获取到,那么标题、内容和时间的颜色都设置为标题颜色。 获取不到的情况下,遍历系统通知里的所有文字,取字号最大的那条文字的颜色作为标题、内容和时间的颜色。 以上两个步骤的实现在getNotificationColor()方法里。如果还获取不到,那么标题和内容采用Android原生系统提供的,其中标题是@android:color/primary_text_dark,内容是@android:color/secondary_text_dark。 有一点需要说明的是,以上适配只适合在Android 7.0以下系统。Android 7.0+修改了Notification,采用@android:color/primary_text_dark和@android:color/secondary_text_dark已经获取不到颜色值了,考虑到7.0所采用的通知栏主色调是白色,因此目前暂时的解决方案是遇到7.0的系统采用黑色字体。面对众多厂商的源码修改,目前测试有ZUK的7.0系统为暗色背景,暂时的解决方案是根据机型适配。 参考链接:[http://iluhcm.com/2017/03/12/experience-of-adapting-to-android-notifications/](http://iluhcm.com/2017/03/12/experience-of-adapting-to-android-notifications/) ================================================ FILE: docs/android/Android-Interview/开发遇到的问题/终极解决ViewPager.setCurrentItem中间页面过多解决方案.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### 本文配套视频 - [ViewPager.setCurrentItem的bug演示一](https://v.qq.com/x/page/n0501ylwqx1.html) - [ViewPager.setCurrentItem解决方案二](https://v.qq.com/x/page/g05012qi6hs.html) 今天做项目用ViewPager.setCurrentItem 方法,如果两个页面相聚比较远,就会闪瞎我的钛合金双眼,中间切换大概20个页面,如下所示: ![img](http://upload-images.jianshu.io/upload_images/4037105-bab7127dbfb0d444?imageMogr2/auto-orient/strip) setCurrentItem第二个参数设置false,四不四很简单,直接使用如下代码: ``` ViewPager.setCurrentItem(position,false); ``` 很不幸的是,使用上面的代码会出现如下效果,扎心了老铁: ![img](http://upload-images.jianshu.io/upload_images/4037105-5dc7bf8e931596ce?imageMogr2/auto-orient/strip) 从第一题点击切换到第十八题,你会发现页面显示空白,如果从第十个页面切换到第十五个页面没事,平时大家估计没有发现这个bug,一般我们使用ViewPager都是底下5个tab页面,从第一个切换到第五个没事,之前我也以为把第二个参数设置false就行,今天才发现,原来如果当页面比较少的时候,大概十个以内,一般没有问题,如果超过十个页面切换就会出现空白,加载不了数据,扎心了,提出解决方案吧,ViewPager滑动使用的是Scroll,咱们把Scroll的滑动时间duration 设置为0就行。 ### **自定义一个Scroll类,用于控制ViewPager滑动速度:** ```java public class MScroller extends Scroller { private static final Interpolator sInterpolator = new Interpolator() { public float getInterpolation(float t) { t -= 1.0f; return t * t * t * t * t + 1.0f; } }; public boolean noDuration; public void setNoDuration(boolean noDuration) { this.noDuration = noDuration; } public MScroller(Context context) { this(context,sInterpolator); } public MScroller(Context context, Interpolator interpolator) { super(context, interpolator); } @Override public void startScroll(int startX, int startY, int dx, int dy, int duration) { if(noDuration) //界面滑动不需要时间间隔 super.startScroll(startX, startY, dx, dy, 0); else super.startScroll(startX, startY, dx, dy,duration); } } ``` #### **上面代码可知:** 1)动态判断页面是否需要滑动,如果不需要滑动,设置滑动时间为0; ### **为方便使用,定义一个辅助类** ```java public class ViewPageHelper { ViewPager viewPager; MScroller scroller; public ViewPageHelper(ViewPager viewPager) { this.viewPager = viewPager; init(); } public void setCurrentItem(int item){ setCurrentItem(item,true); } public MScroller getScroller() { return scroller; } public void setCurrentItem(int item, boolean somoth){ int current=viewPager.getCurrentItem(); //如果页面相隔大于1,就设置页面切换的动画的时间为0 if(Math.abs(current-item)>1){ scroller.setNoDuration(true); viewPager.setCurrentItem(item,somoth); scroller.setNoDuration(false); }else{ scroller.setNoDuration(false); viewPager.setCurrentItem(item,somoth); } } private void init(){ scroller=new MScroller(viewPager.getContext()); Classcl=ViewPager.class; try { Field field=cl.getDeclaredField("mScroller"); field.setAccessible(true); //利用反射设置mScroller域为自己定义的MScroller field.set(viewPager,scroller); } catch (NoSuchFieldException e) { e.printStackTrace(); }catch (IllegalAccessException e){ e.printStackTrace(); } } } ``` #### **由上面代码可知:** 1)Math.abs(current-item)>1 ,通过数学函数判断页面相隔大于1,就设置页面切换的动画的时间为0。 2)这样每次设置页面的时候,通过 helper 就可以自动选择是否有时间间隔了。 3)但是这样有点麻烦,每次还要手动改,而且使用TabLayout或者ViewPagerIndicator的话,它会自动调用ViewPager的方法,无法使用Helper,所以可以采用自定一个ViewPager,代码如下: ```java public class SuperViewPager extends ViewPager { private ViewPageHelper helper; public SuperViewPager(Context context) { this(context,null); } public SuperViewPager(Context context, AttributeSet attrs) { super(context, attrs); helper=new ViewPageHelper(this); } @Override public void setCurrentItem(int item) { setCurrentItem(item,true); } @Override public void setCurrentItem(int item, boolean smoothScroll) { MScroller scroller=helper.getScroller(); if(Math.abs(getCurrentItem()-item)>1){ scroller.setNoDuration(true); super.setCurrentItem(item, smoothScroll); scroller.setNoDuration(false); }else{ scroller.setNoDuration(false); super.setCurrentItem(item, smoothScroll); } } } ``` 至此完美解决了,ViewPager.setCurrentItem切换页面,效果如下: ![img](http://upload-images.jianshu.io/upload_images/4037105-b2f5486c91c65eae?imageMogr2/auto-orient/strip) - 欢迎关注微信公众号,长期推荐技术文章和技术视频 - 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/开发遇到的问题/解决字体适配.md ================================================ ## [Android面试题-解决字体适配](http://www.jianshu.com/p/33d499170e25) ![img](http://upload-images.jianshu.io/upload_images/4037105-e301f4b8dd0e65c0?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![img](http://upload-images.jianshu.io/upload_images/4037105-90e9a7ed7ea4a2d3?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 做个简单的例子,先验证一下: 同样的布局代码 ```xml ``` 调节设置中显示字体大小 ![img](http://upload-images.jianshu.io/upload_images/4037105-9c7b06ffd4edeaf2?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 运行后显示样式 ![img](http://upload-images.jianshu.io/upload_images/4037105-ce4446f4d89bf902?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 回到标题要解决的问题,如果要像微信一样,所有字体都不允许随系统调节而发生大小变化,要怎么办呢?利用Android的Configuration类中的fontScale属性,其默认值为1,会随系统调节字体大小而发生变化,如果我们强制让其等于默认值,就可以实现字体不随调节改变,在工程的Application或BaseActivity中添加下面的代码: ```java @Override public void onConfigurationChanged(Configuration newConfig) { if (newConfig.fontScale != 1)//非默认值 getResources(); super.onConfigurationChanged(newConfig); } @Override public Resources getResources() { Resources res = super.getResources(); if (res.getConfiguration().fontScale != 1) {//非默认值 Configuration newConfig = new Configuration(); newConfig.setToDefaults();//设置默认 res.updateConfiguration(newConfig, res.getDisplayMetrics()); } return res; } ``` 总结,两种方案解决这个问题: 一是布局宽高固定的情况下,字体单位改用dp表示; 二是通过3中的代码设置应用不能随系统调节,在检测到fontScale属性不为默认值1的情况下,强行进行改变。 ================================================ FILE: docs/android/Android-Interview/开发遇到的问题/软键盘顶出去解决方案.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) - [深刻剖析activity启动模式(一)](http://www.jianshu.com/p/b33fd8c550bf) - [深刻剖析activity启动模式(二)](http://www.jianshu.com/p/e1ea9e542112) - [深刻剖析activity启动模式(三)](http://www.jianshu.com/p/d13e3d552d4b) - [service里面startActivity抛异常?activity不会](http://www.jianshu.com/p/16e880ceb3a4) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) - [字体适配](http://www.jianshu.com/p/33d499170e25) - [软键盘顶出去解决方案](http://www.jianshu.com/p/640bac6f58ab)与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) 错误的打开方式 ![img](http://upload-images.jianshu.io/upload_images/4037105-d3bae424787347e2.gif?imageMogr2/auto-orient/strip) 错误的打开姿势.gif 仔细观察会发现,当软键盘弹出时 **background、recyclerview、toolbar** 被**软键盘**顶上去了!这样的交互简直不能忍,对用户来说也非常突兀。 正确的打开方式 ![img](http://upload-images.jianshu.io/upload_images/4037105-f64f01282733e751.gif?imageMogr2/auto-orient/strip) 正确的打开姿势.gif 软键盘弹出只是遮盖了 **background** 一部分,**background** 没有被**压缩**。 实现 1. AndroidMainifest.xml 配置文件 ```xml ``` 非透明状态栏下使用adjustResize和adjustPan,或是透明状态栏下使用`fitsSystemWindows=true`属性 主要实现方法: 在AndroidManifest.xml对应的Activity里添加`windowSoftInputMode=”adjustPan”`或是`android:windowSoftInputMode=”adjustResize”`属性 推荐一篇软键盘很好的文章:[http://blog.csdn.net/smileiam/article/details/69055963](http://blog.csdn.net/smileiam/article/details/69055963) - 欢迎关注微信公众号,长期推荐技术文章和技术视频 - 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/开发遇到的问题/迭代开发的时候如何向前兼容新旧接口?.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### 本文配套视频。。 - [配套视频](https://v.qq.com/x/page/a0395pv28zm.html) ### 迭代开发的时候如何向前兼容新旧接口? 设计服务器接口时,每一个接口,都带版本号。比如用户登陆接口第 1 版为 ``` /1/user/login ``` 返回 Json 数据。数据结构改动后,假如 Json 数据只是增加字段,这时接口不用修改。当登陆接口改动太大,会删除或者修改字段。就递增版本号,新添接口: ``` /2/user/login ``` 旧的 /1/user/login 接口需要保留,这时旧的客户端使用 /1/user/login,而新的客户端使用 /2/user/login。 在服务端 /1/user/login 和 /2/user/login 进行重构,某些地方调用相同的代码。两个接口并存一段时间后,比如过了 3 个月。估计旧的客户端差不多都升级到新的了,这时旧的 /1/user/login 接口就可以不再维护,直接返回错误码。 比如开源中国开发也是如此,开源中国API接口如下: ![img](http://upload-images.jianshu.io/upload_images/4037105-4fa3bfc810e0305c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![img](http://upload-images.jianshu.io/upload_images/4037105-7043d98ff097e97f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - 欢迎关注微信公众号,长期推荐技术文章和技术视频 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/性能优化/Android应用UI性能分析.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### 本文配套视频。 - [HierarchyViewer配套视频](https://v.qq.com/x/page/y0393sa0jlp.html) - [Lint配套视频](https://v.qq.com/x/page/d039381wbas.html) - [内存抖动现象配套视频](https://v.qq.com/x/page/x0393gf7qp6.html)12-如何对android应用进行内存性能分析 ### Android应用UI性能分析 在使用App时会发现有些界面启动卡顿、动画不流畅、列表等滑动时也会卡顿出现这种情况,可以考虑对UI性能分析。 > 首先要清楚卡顿的原因,有以下几种情况: - 人为在UI线程中做轻微耗时操作,导致UI线程卡顿 - 布局Layout过于复杂,无法在16ms内完成渲染 - 同一时间动画执行的次数过多,导致CPU或GPU负载过重 - View过度绘制,导致某些像素在同一帧时间内被绘制多次,从而使CPU或GPU负载过重 - View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染 - 内存频繁触发GC过多(同一帧中频繁创建内存),导致暂时阻塞渲染操作 - 冗余资源及逻辑等导致加载和执行缓慢 - 臭名昭著的ANR ### 如何分析? > 分析UI卡顿我们一般都借助工具,通过工具一般都可以直观的分析出问题原因,从而反推寻求优化方案,具体如下细说各种强大的工具 ### 使用HierarchyViewer分析UI性能 我们可以通过SDK提供的工具HierarchyViewer来进行UI布局复杂程度及冗余等分析 通过命令启动HierarchyViewer ![img](http://upload-images.jianshu.io/upload_images/4037105-913938b7dd911784.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 接下来Hierarchy window窗口打开: ![img](http://upload-images.jianshu.io/upload_images/4037105-750650883b206a5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 一个Activity的View树,通过这个树可以分析出View嵌套的冗余层级,以及每个View在绘制的使用时长也有表示。 ### 使用Lint进行资源及冗余UI布局等优化 冗余资源及逻辑等也可能会导致加载和执行缓慢,这可以使用Link工具,来发现优化这些问题的 在Android Studio 1.4版本中使用Lint最简单的办法: 就是将鼠标放在代码区点击右键->Analyze->Inspect Code–>界面选择你要检测的模块->点击确认开始检测,等待一下后会发现如下结果: ![img](http://upload-images.jianshu.io/upload_images/4037105-84f3df4cfd1d4c07.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 如果存在冗余的UI层级嵌套,会进行高亮显示, 我们根据提示可以点击跳进去进行优化处理掉的。 ### 使用Memory监测及GC打印与Allocation Tracker进行UI卡顿分析 由于Android系统会依据内存中不同的内存数据类型分别执行不同的GC操作,常见应用开发中导致GC频繁执行的原因主要可能是因为短时间内有大量频繁的对象创建与释放操作,也就是俗称的内存抖动现象,或者短时间内已经存在大量内存暂用介于阈值边缘,接着每当有新对象创建时都会导致超越阈值触发GC操作 ### 如何查看? #### Android Studio 工具提供内存查看器: ![img](http://upload-images.jianshu.io/upload_images/4037105-26fcbce199c511c8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### 根据内存抖动现象,查看log日志进行分析: ![img](http://upload-images.jianshu.io/upload_images/4037105-64af051f5b7d76da.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) > 如何看到,这种不停的大面积打印GC导致所有线程暂停的操作必定会导致UI视觉的卡顿,所以我们要避免此类问题的出现,具体的常见优化方式如下: - 检查代码,尽量避免有些频繁触发的逻辑方法中存在大量对象分配 - 尽量避免在多次for循环中频繁分配对象 - 避免在自定义View的onDraw()方法中执行复杂的操作及创建对象(譬如Paint的实例化操作不要写在onDraw()方法中等) - 对于并发下载等类似逻辑的实现尽量避免多次创建线程对象,而是交给线程池处理。 有了上面说明GC导致的性能后我们就该定位分析问题了, 我们可以通过运行DDMS->Allocation Tracker标签打开一个新窗口,然后点击Start Tracing按钮,接着运行你想分析的代码,运行完毕后点击Get Allocations按钮就能够看见一个已分配对象的列表,如下: ![img](http://upload-images.jianshu.io/upload_images/4037105-8e16375acbfadf55.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 根据内存分配情况,进行优化。 写篇文章和录制视频从选题,思考,组织,写作,编辑至少需要一个多小时,而您点赞和转发只需要0.1秒,就可以带给我更大的动力,何乐而不为呢? - 欢迎关注微信公众号,长期推荐技术文章和技术视频 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/性能优化/App应用启动分析与优化.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### 本文配套视频 - [配套视频](https://v.qq.com/x/page/v0396aro8d1.html) ## Android性能优化之App应用启动分析与优化 ### App启动方式 通常来说, 一个App启动也会分如下二中不同的状态: .1)冷启动 当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。 2.)热启动 当启动应用时,后台已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。热启动因为会从已有的进程中来启动,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application,因为一个应用从新进程的创建到进程的销毁,Application只会初始化一次。 ### 启动时间的测量 关于Activity启动时间的定义 对于Activity来说,启动时,首先执行的是onCreate()、onStart()、onResume()这些生命周期函数,但即使这些生命周期方法回调结束了,应用也不算已经完全启动,还需要等View树全部构建完毕,一般认为,setContentView中的View全部显示结束了,算作是应用完全启动了。 Display Time 从API19之后,Android在系统Log中增加了Display的Log信息,通过过滤ActivityManager以及Display这两个关键字,可以找到系统中的这个Log: ``` $ adb logcat | grep “ActivityManager” ActivityManager: Displayed com.example.launcher/.LauncherActivity: +999ms ``` 抓到的Log如图所示: ![img](http://upload-images.jianshu.io/upload_images/4037105-6c3368ada0ceb402.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 那么这个时间,实际上是Activity启动,到Layout全部显示的过程,但是要注意,这里并不包括数据的加载,因为很多App在加载时会使用懒加载模式,即数据拉取后,再刷新默认的UI。 ### 基于上面的启动流程我们尽量做到如下几点 1) Application的创建过程中尽量少的进行耗时操作 2) 首屏Activity的渲染 当前冷启动效果: ![img](http://upload-images.jianshu.io/upload_images/4037105-d242b508fd0fd4a9.gif?imageMogr2/auto-orient/strip) #### Application Application是程序的主入口,特别是很多第三方SDK都会需要在Application的onCreate里面做很多初始化操作,一般来说我们可以将这些初始化放在一个单独的线程中处理, 为了方便今后管理, 优化的方法,无非是通过以下几个方面: 1)异步初始化 2)后台任务 3)界面预加载 异步初始化 这个很简单,就是让App在onCreate里面尽可能的少做事情,而利用手机的多核特性,尽可能的利用多线程,例如一些第三方框架的初始化,如果能放线程,就尽量的放入线程中,最简单的,你可以直接new Thread(),当然,你也可以通过公共的线程池来进行异步的初始化工作,这个是最能够压缩启动时间的方式 后台任务 因为这个App一般会集成很多三方SDK等服务, 所以Application的onCreate有很多第三方平台的初始化工作… Application代码如下: ``` public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); LitePal.initialize(this.getApplicationContext()); } } ``` 使用IntentService不同于Service, 它是工作在后台线程的. IntentService代码如下: ``` public class InitializeService extends IntentService { private static final String ACTION_INIT_WHEN_APP_CREATE = "com.maweiqi"; public InitializeService(String name) { super(name); } @Override protected void onHandleIntent(Intent intent) { if (intent != null) { final String action = intent.getAction(); if (ACTION_INIT_WHEN_APP_CREATE.equals(action)) { performInit(); } } } private void performInit() { LitePal.initialize(this.getApplicationContext()); } public static void start(Context context) { Intent intent = new Intent(context, InitializeService.class); intent.setAction(ACTION_INIT_WHEN_APP_CREATE); context.startService(intent); } } ``` MyApp的onCreate改成: ``` public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); InitializeService.start(this); } } ``` 最终的效果 ![img](http://upload-images.jianshu.io/upload_images/4037105-78d1dbedbd643c38.gif?imageMogr2/auto-orient/strip) 界面预加载 当系统加载一个Activity的时候,onCreate()是一个耗时过程,那么在这个过程中,系统为了让用户能有一个比较好的体验,实际上会先绘制一些初始界面,类似于PlaceHolder。 系统首先会读取当前Activity的Theme,然后根据Theme中的配置来绘制,当Activity加载完毕后,才会替换为真正的界面。所以,Google官方提供的解决方案,就是通过android:windowBackground属性,来进行加载前的配置,同时,这里不仅可以配置颜色,还能配置图片,例如,我们可以使用一个layer-list来作为android:windowBackground要显示的图: 在drawable文件夹下面 , 做一个logo_splash的背景: start_window.xml ```xml ``` 在style里面弄一个主题: ```xml ``` 在清单文件里面给Activity指定需要预加载的Style: ```xml ``` activity代码如下: ```java public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { setTheme(R.style.AppTheme); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } ``` #### 首屏Activity的渲染 Android系统每隔16ms会发出VSYNC信号重绘我们的界面(Activity). 为什么是16ms, 因为Android设定的刷新率是60FPS(Frame Per Second), 也就是每秒60帧的刷新率, 约合16ms刷新一次.就像是这样的: ![img](http://upload-images.jianshu.io/upload_images/4037105-12fb140cf501a731.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 这就意味着, 我们需要在16ms内完成下一次要刷新的界面的相关运算, 以便界面刷新更新. 然而, 如果我们无法在16ms内完成此次运算会怎样呢? 例如, 假设我们更新屏幕的背景图片, 需要24ms来做这次运算. 当系统在第一个16ms时刷新界面, 然而我们的运算还没有结束, 无法绘出图片. 当系统隔16ms再发一次VSYNC信息重绘界面时, 用户才会看到更新后的图片. 也就是说用户是32ms后看到了这次刷新(注意, 并不是24ms). 这就是传说中的丢帧(dropped frame): ![img](http://upload-images.jianshu.io/upload_images/4037105-4ddbcaee727cbffc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 丢帧给用户的感觉就是卡顿, 而且如果运算过于复杂, 丢帧会更多, 导致界面常常处于停滞状态, 卡到爆. - 欢迎关注微信公众号,长期推荐技术文章和技术视频 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/性能优化/README.md ================================================ ## 与性能优化相关面试题 - [内存泄漏和内存溢出区别](与性能优化相关试题一.md) - [UI优化和线程池实现原理](与性能优化相关试题二.md) - [代码优化](与性能优化相关试题三.md) - [Android应用UI性能分析](Android应用UI性能分析.md) - [内存泄漏监测](内存泄漏监测.md) - [App应用启动分析与优化](App应用启动分析与优化.md) - [与IPC机制相关面试题](与IPC机制相关面试题.md) ================================================ FILE: docs/android/Android-Interview/性能优化/与IPC机制相关面试题.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### 与人事相关面试题 - [人事面试宝典](http://blog.csdn.net/mwq384807683/article/details/71435960) > 现在三四月份,金三银四最好找工作时间,为方便各位找工作,特意收集100道android各个方面的面试题,并且会一一录制视频分享给大家方便大家找工作,面试题分类如下; ![img](http://upload-images.jianshu.io/upload_images/4037105-4437ab22b7af3cc8.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![img](http://upload-images.jianshu.io/upload_images/4037105-22abf62d3d9f68a5.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![img](http://upload-images.jianshu.io/upload_images/4037105-6838fa267298201a.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![img](http://upload-images.jianshu.io/upload_images/4037105-c8d1161109029383.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ### 本文配套视频 - [配套视频](https://v.qq.com/x/page/a03916l1n7h.html) - [配套视频](https://v.qq.com/x/page/m0391pnoyl7.html) - [配套视频](https://v.qq.com/x/page/t0391b2gjm5.html) - [配套视频](https://v.qq.com/x/page/v0391vx3ynb.html) - 欢迎关注微信公众号,长期推荐技术文章和技术视频 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ## 1- Davik进程、linux进程、线程之间的区别? ### Linux进程: - Linux进程,它有独立的内核堆栈和独立的存储空间,它是操作系统中资源分配和调度的最小单位。 - Linux操作系统会以进程为单位,分配系统资源,给程序进行调度。 - Linux操作系统在执行一个程序时,它会创建一个进程,来执行应用程序,并且伴随着资源的分配和释放。 ### Davik进程: - Dalvik虚拟机运行在Linux操作系统之上。 - Davik进程就是Linux操作系统中的一个进程,属于linux进程 - 每个Android应用程序进程都有一个Dalvik虚拟机实例。这样做得好处是Android应用程序进程之间不会互相影响,也就是说,一个Android应用程序进程的意外终止,不会影响到其他的应用程序进程的正常运行。 ### 线程: - 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。 - 线程自己基本上不拥有系统资源,在运行时,只需要必不可少的资源(如程序计数器,一组寄存器和栈)。 - 线程与同属一个进程的其他的线程共享进程所拥有的全部资源。 ### 三者之间的联系: - Davik进程就是Linux操作系统的一个进程。 - 线程就是进程的一个实体,线程是进程的一部分。一个进程中可以提供多个线程执行控制。 ### 进程和线程的区别: - 一个程序至少有一个进程,一个进程至少有一个线程. - 线程的划分尺度小于进程,使得多线程程序的并发性高。 - 进程在执行过程中拥有独立的内存单元,而多个线程共享内存(同属一个进程),从而极大地提高了程序的运行效率。 - 每个独立的进程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 - 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。 # 2-Android 中进程与进程之间如何通信? > aidl机制进程间通信 > AIDL: (Android Interface definition language的缩写)它是一种android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口 AIDL进程间通讯的原理: 通过编写aidl文件来定义进程间通信接口。 编译后会自动生成响应的java文件 服务器将接口的具体实现写在Stub中,用iBinder对象传递给客户端, 客户端bindService的时候,用asInterface的形式将iBinder还原成接口,再调用其接口中的方法来实现通信。 ### 使用Messenger实现进程间通信 > Messenger是基于AIDL实现的。 > AIDL使服务器可以并行处理,而Messenger封装了AIDL之后只能串行运行,所以Messenger一般用作消息传递。 - 需要大家注意: - 区别Messenger和Message。 - Message是消息,承载了要传递的数据。 - Messenger是信使,可以发送消息。并且Messenger对象可以通过getBinder方法获取一个Ibinder对象。 ### Messenger实现原理: > 服务端(被动方)提供一个Service来处理客户端(主动方)连接,维护一个Handler来创建Messenger,在onBind时返回Messenger的binder。 > 双方用Messenger来发送数据,用Handler来处理数据。Messenger处理数据依靠Handler,所以是串行的,也就是说,Handler接到多个message时,就要排队依次处理。 ### 使用Messenger实现进程间通信方法如下: - 首先A应用提供一个Service,创建一个Messenger对象,在onBinder方法里返回messenger.getBinder()生成的IBinder对象; 然后在B应用绑定该Service,在ServiceConnection的onServiceConnected方法获取到IBinder对象; - 最后在B应用使用获取到的binder对象构造出一个新的Messenger对象,使用该Messenger对象的send方法发送的Message数据,都将被Service里的Messenger对象handlerMessage方法接收到。 ### 内容提供者ContentProvider实现进程间通信 系统四大组件之一,底层也是Binder实现,主要用来为其他APP提供数据。 > 自定义的ContentProvider为外界进程访问的时候, > 需要在注册时要提供authorities属性,应用需要访问的时候将属性包装成Uri.parse("content://authorities")。 - 然后实现: ContentProvider的中query,delete,insert等相关方法,进行数据的操作。 - 欢迎关注微信公众号,长期推荐技术文章和技术视频 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/性能优化/与性能优化相关试题一.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### 本文配套视频 - [配套视频](https://v.qq.com/x/page/n0391if5dtb.html) - [配套视频](https://v.qq.com/x/page/q03917e4zk5.html) - [配套视频](https://v.qq.com/x/page/j03927ullcj.html) ### 9-内存泄漏和内存溢出分别是什么?它们有什么关系? - 内存泄露是指保存了不可能再被访问的变量引用,导致垃圾回收器无法回收内存。 也就是说:在Java中有些对象的生命周期是有限的,当它们完成了特定的逻辑后将会被垃圾回收;但是,如果在对象的生命周期本来该被垃圾回收时这个对象还被别的对象所持有引用,那就会导致内存泄漏 - 内存溢出是指虚拟机内存耗尽,无法为新对象分配内存,导致引用崩溃。典型的情况为加载多张大图,导致内存耗尽。 ![img](http://upload-images.jianshu.io/upload_images/4037105-edf3c4ee63ad3c13.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - 当某个界面存在内存泄露,反复进入该界面,将导致一直有新对象创建但是无法回收,最终内存耗尽,产生内存溢出。 ### 10-什么情况下会导致内存泄漏 - 资源释放问题 程序代码的问题,长期保持某些资源,如Context、Cursor、IO流的引用,资源得不到释放造成内存泄露。 - 对象内存过大问题 保存了多个耗用内存过大的对象(如Bitmap、XML文件),造成内存超出限制。 - static关键字的使用问题 static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多),这时就要谨慎对待了。 针对static的解决方案: 1) 应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。 2) Context尽量使用ApplicationContext,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。 3) 使用WeakReference代替强引用。比如可以使用WeakReference mContextRef; - 线程导致内存溢出 线程产生内存泄露的主要原因在于线程生命周期的不可控。 针对这种线程导致的内存泄露问题的解决方案: - 将线程的内部类,改为静态内部类(因为非静态内部类拥有外部类对象的强引用,而静态类则不拥有)。 - 在线程内部采用弱引用保存Context引用。 - 查询数据库没有关闭cursor 程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会出现内存问题,这样就会给以后的测试和问题排查带来困难和风险。 - 构造Adapter没有复用convertview 在使用ListView的时候通常会使用Adapter,那么我们应该尽可能的使用ConvertView。 为什么要复用convertView? 当convertView为空时,用setTag()方法为每个View绑定一个存放控件的ViewHolder对象。当convertView不为空,重复利用已经创建的view的时候,使用getTag()方法获取绑定的ViewHolder对象,这样就避免了findViewById对控件的层层查询,而是快速定位到控件。 - Bitmap不再使用时没有调用recycle()释放内存 有时我们会手工的操作Bitmap对象,如果一个Bitmap对象比较占内存,当它不再被使用的时候,可以调用Bitmap.recycle()方法回收此对象的像素所占用的内存,但这不是必须的,视情况而定。 - 欢迎关注微信公众号,长期推荐技术文章和技术视频 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/性能优化/与性能优化相关试题三.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### 本文配套视频 - [String字符串优化配套视频](https://v.qq.com/x/page/k0393ataw3l.html) - [其他优化配套视频](https://v.qq.com/x/page/j0393gm2p7j.html) ## String字符串优化 最常见的例子就是当你要频繁操作一个字符串时,使用StringBuffer代替String。 还比如:使用int数组而不是Integer数组。 避免创建短命的临时对象,减少对象的创建就能减少垃圾收集,进而减少对用户体验的影响。 ## ListView优化 1. Item布局,层级越少越好,使用hierarchyview工具查看优化。 2. 复用convertView 3. 使用ViewHolder 4. item中有图片时,异步加载 5. 快速滑动时,不加载图片 6. item中有图片时,应对图片进行适当压缩 7. 实现数据的分页加载 ## 减少不必要的全局变量 尽量避免static成员变量引用资源耗费过多的实例,比如Context。 因为Context的引用超过它本身的生命周期,会导致Context泄漏。所以尽量使用Application这种Context类型。 你可以通过调用Context.getApplicationContext()或 Activity.getApplication()轻松得到Application对象。 ## Cursor(游标)回收 Cursor是Android查询数据后得到的一个管理数据集合的类,在使用结束以后。应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理。并且Android明显是倾向于编程者手动的将Cursor close掉,因为在源代码中我们发现,如果等到垃圾回收器来回收时,会给用户以错误提示。 ## Receiver(接收器)回收 调用registerReceiver()后未调用unregisterReceiver(). 当我们Activity中使用了registerReceiver()方法注册了BroadcastReceiver,一定要在Activity的生命周期内调用unregisterReceiver()方法取消注册 也就是说registerReceiver()和unregisterReceiver()方法一定要成对出现,通常我们可以重写Activity的onDestory()方法,在onDestory里进行unregisterReceiver操作 ## Stream/File(流/文件)回收 主要针对各种流,文件资源等等如: InputStream/OutputStream,SQLiteOpenHelper,SQLiteDatabase,Cursor,文件,I/O,Bitmap图片等操作等都应该记得显示关闭。 ## 避免内部Getters/Setters 在Android中,虚方法调用的代价比直接字段访问高昂许多。通常根据面向对象语言的实践,在公共接口中使用Getters和Setters是有道理的,但在一个字段经常被访问的类中宜采用直接访问。 for循环 访问成员变量比访问本地变量慢得多,如下面一段代码: ``` for(int i =0; i < this.mCount; i++) {} ``` 永远不要在for的第二个条件中调用任何方法,如下面一段代码: ``` for(int i =0; i < this.getCount(); i++) {} ``` 对上面两个例子最好改为: ``` int count = this.mCount; / int count = this.getCount(); for(int i =0; i < count; i++) {} ``` - 欢迎关注微信公众号,长期推荐技术文章和技术视频 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/性能优化/与性能优化相关试题二.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### 本文配套视频 - [配套视频](https://v.qq.com/x/page/w0392bn6wto.html) - [配套视频](https://v.qq.com/x/page/j0393ytx9ob.html) - [线程池原理配套视频](https://v.qq.com/x/page/u0393izwfut.html) 11-说一下如何对Android内存优化上集? ## Bitmap OOM(图片优化) ### 1. 图片处理 #### a. 等比缩放 ![img](http://upload-images.jianshu.io/upload_images/4037105-82ef6ff37b935245.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 以上代码可以优化内存溢出,但它只是改变图片大小,并不能彻底解决内存溢出。 #### b. 对图片采用软引用,及时地进行recyle()操作 ![img](http://upload-images.jianshu.io/upload_images/4037105-89cce346d56154a0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### c. 设置图片解码格式 ```java BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.ARGB_4444; ``` #### d. 加载部分图片 ![img](http://upload-images.jianshu.io/upload_images/4037105-5cfa36fe83a5a2e1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### 2. 图片的缓存 a) 网络缓存 b) 内存缓存 c) 磁盘缓存 #### 3. 使用加载图片框架处理图片,如专业处理加载图片的ImageLoader,glide,picasso等图片加载框架。 通过以上方式可以解决图片OOM异常 ## UI Review(UI优化) 减少视图层级 减少视图层级可以有效的减少内存消耗,因为视图是一个树形结构,每次刷新和渲染都会遍历一次。 ### ViewStub标签 此标签可以使UI在特殊情况下,直观效果类似于设置View的不可见性,但是其更大的意义在于被这个标签所包裹的Views在默认状态下不会占用任何内存空间。 ```xml ``` ### include标签 可以通过这个标签直接加载外部的xml到当前结构中,是复用UI资源的常用标签。 ```xml ``` ### merge标签 它在优化UI结构时起到很重要的作用。目的是通过删减多余或者额外的层级,从而优化整个Android Layout的结构。 ```xml ``` ```xml ``` ![img](http://upload-images.jianshu.io/upload_images/4037105-e93176a6e09be825.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 实现一模一样的效果,我们通过SDK自带工具检测android层级关系 - 使用merge标签,布局少一个层级 ![img](http://upload-images.jianshu.io/upload_images/4037105-397ffb39b2ab0bef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - 不使用merge标签,布局多一个层级 ![img](http://upload-images.jianshu.io/upload_images/4037105-ca547716b1b0d7df.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) Fragment优化 直接使用系统APP包下面的Fragment,不要使用V4包里面的Fragment可以减少层级结构。 ## 使用ThreadPool优化代码(线程池实现原理) ### 1. new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? new Thread的弊端如下: - 每次new Thread新建对象性能差。 - 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。 - 缺乏更多功能,如定时执行、定期执行、线程中断。 相比new Thread,Java提供的四种线程池的好处在于: - 重用存在的线程,减少对象创建、消亡的开销,性能佳。 - 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。 - 提供定时执行、定期执行、单线程、并发数控制等功能。 ### 2. Java 线程池 Java通过Executors提供四种线程池,分别为: newCachedThreadPool() 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 newFixedThreadPool() 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 newScheduledThreadPool() 创建一个定长线程池,支持定时及周期性任务执行。 newSingleThreadExecutor() 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 - 欢迎关注微信公众号,长期推荐技术文章和技术视频 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/性能优化/内存泄漏监测.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### 本文配套视频 - [Monitors使用配套视频](https://v.qq.com/x/page/x0393gf7qp6.html) - [DDMS-Heap配套视频](https://v.qq.com/x/page/e03933o0tp7.html) - [leakcanary配套视频](https://v.qq.com/x/page/o0382aiamwt.html) ## 12-如何对android应用进行内存性能分析 #### 如何查看项目中内存泄漏? ![img](http://upload-images.jianshu.io/upload_images/4037105-4a0d70f4ff3c47fa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) > AS的Memory窗口如下,详细的说明这里就不解释了,很简单很直观(使用频率高): ![img](http://upload-images.jianshu.io/upload_images/4037105-303debd142d5a3d4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) > DDMS-Heap内存监测工具窗口如下,详细的说明这里就不解释了,很简单(使用频率不高): ![img](http://upload-images.jianshu.io/upload_images/4037105-340416c85d9db68b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) dumpsys meminfo命令如下(使用频率非常高,非常高效,注意:adb shell dumpsys meminfo不跟参数直接展示系统所有内存状态): ![img](http://upload-images.jianshu.io/upload_images/4037105-bd446bdbe1c0d05b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) Android应用内存泄露leakcanary工具定位分析 leakcanary是一个开源项目,一个内存泄露自动检测工具,是著名的GitHub开源组织Square贡献的,它的主要优势就在于自动化过早的发觉内存泄露。 ![img](http://upload-images.jianshu.io/upload_images/4037105-92ea80d780f88d1a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 关于leakcanary工具的配置使用方式这里不再详细介绍,可以参考官方说明使用: [https://github.com/square/leakcanary](https://github.com/square/leakcanary) > Android应用内存泄露MAT工具定位分析 Eclipse Memory Analysis Tools是一个专门分析Java堆数据内存引用的工具,我们可以使用它方便的定位内存泄露原因,核心任务就是找到GC ROOT位置即可 ![img](http://upload-images.jianshu.io/upload_images/4037105-09c1fc109df0e02d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 写篇文章和录制视频从选题,思考,组织,写作,编辑至少需要一个多小时,而您点赞和转发只需要0.1秒,就可以带给我更大的动力,何乐而不为呢? - 欢迎关注微信公众号,长期推荐技术文章和技术视频 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/源码分析/Android源码编译实现静默安装和静默偷拍.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://blog.csdn.net/mwq384807683/article/details/71305969) - [注解框架实现原理](http://blog.csdn.net/mwq384807683/article/details/70795881) - [okhttp3.0源码分析](http://blog.csdn.net/mwq384807683/article/details/71173442) ### 与XMPP相关面试题 - [与XMPP相关试题一](http://blog.csdn.net/mwq384807683/article/details/70313802) - [与XMPP相关试题二](http://blog.csdn.net/mwq384807683/article/details/70313827) ### 与性能优化相关面试题 - [与性能优化相关面试题一](http://blog.csdn.net/mwq384807683/article/details/70313673) - [与性能优化相关面试题二](http://blog.csdn.net/mwq384807683/article/details/70313685) - [与性能优化相关面试题三](http://blog.csdn.net/mwq384807683/article/details/70313721) - [与性能优化相关面试题四](http://blog.csdn.net/mwq384807683/article/details/70313741) - [与性能优化相关面试题五](http://blog.csdn.net/mwq384807683/article/details/70313763) - [与性能优化相关面试题六](http://blog.csdn.net/mwq384807683/article/details/70670846) - [与IPC机制相关面试题](http://blog.csdn.net/mwq384807683/article/details/70313632) ### 与登录相关面试题 - [oauth认证协议原理](http://blog.csdn.net/mwq384807683/article/details/70313871) - [token产生的意义](http://blog.csdn.net/mwq384807683/article/details/70840226) - [微信扫一扫实现原理](http://blog.csdn.net/mwq384807683/article/details/70313849) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://blog.csdn.net/mwq384807683/article/details/70470403) - [手把手教你如何解决as jar包冲突](http://blog.csdn.net/mwq384807683/article/details/71402900) - [context的原理分析](http://blog.csdn.net/mwq384807683/article/details/70670431) ### 与人事相关面试题 - [人事面试宝典](http://blog.csdn.net/mwq384807683/article/details/71435960) ### 本文配套视频 - [静默安装原理一](https://v.qq.com/x/page/k0501fbjwcv.html) - [静默安装原理二](https://v.qq.com/x/page/f0501jbulwc.html) 很多哥们在后台公众号留言,让讲讲如何编译源码,感觉源码开发很神秘,好吧,今天就手把手教大家安装虚拟机编译源码,文字部分的东西就别看了,毕竟源码或者技术这种东西三言两语也讲不明白,直接看视频吧,通过本文我录制的视频希望对大家能起到抛砖引玉的作用,不过这个视频是我在三年前录制的,讲的是通过源码编译不需要root权限实现静默安装和偷拍,然后也把[Android](http://lib.csdn.net/base/android)系统安装源码给大家分析了,毕竟三年前随堂录制的视频,也比较简单的东西,如果没有收获随便看看就行,有收获点个赞就行,对静默安装感兴趣就从第一个视频开始看起,如果只是单纯对源码编译感兴趣,直接从第二个视频15分钟开始看就行,这样能节约大家的时间,视频时间有点长。 四年前写的静默安装代码,有兴趣看看吧:[http://www.apkbus.com/forum.php?mod=viewthread&tid=120895](http://www.apkbus.com/forum.php?mod=viewthread&tid=120895) # Android源码分析 ### 1. 了解虚拟机的的使用 Android 系统的编译环境目前只支持 [Linux](https://lib.csdn.net/base/linux) 以及 Mac OS 两种[操作系统](https://lib.csdn.net/base/operatingsystem) 虚拟机可以很方便的搭建不同系统的开发环境 虚拟机可以完整的模拟一台电脑 虚拟机中的系统独立于主系统存在,子系统的损坏与否不影响主系统功能 #### 1.1. 了解虚拟机的安装和创建新虚拟机的方法 ![静默安装和静默偷拍](img/静默安装和静默偷拍-1.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-2.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-3.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-4.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-5.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-6.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-7.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-8.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-9.png) #### 1.2. 了解虚拟机里安装Ubuntu系统的方法 ![静默安装和静默偷拍](img/静默安装和静默偷拍-10.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-11.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-12.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-13.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-14.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-15.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-16.png) # Android源码下载 > Android源码下载支持的系统目前只有Ubuntu和Mac OS两种操作系统, 本次以Ubuntu系统为例. > > 官方网站: [https://source.android.com/source/downloading.html](https://source.android.com/source/downloading.html) 1. 下载[Git](https://lib.csdn.net/base/git)([版本控制](https://lib.csdn.net/base/git)工具). 调出命令行: ctrl + alt + T ``` sudo apt-get install git ``` 2. 安装curl(上传和下载数据的工具). ``` sudo apt-get install curl ``` 3. 安装repo(一个基于git的版本库管理工具, 这里用于自动批量下载android整个项目.). ``` // 创建目录 mkdir bin // 下载repo脚本到本地bin文件夹下 curl https://android.git.kernel.org/repo >~/bin/repo // 如果上面下载失败, 采用下面这种方式 curl "https://php.webtutor.pl/en/wp-content/uploads/2011/09/repo" >~/bin/repo // 给所有用户追加可执行的权限 chmod a+x ~/bin/repo // 临时把repo添加到环境变量中, 方便后面执行. // 注意: 每次重启ubuntu之后此环境变量失效, 重新配置就可以了. export PATH=~/bin:$PATH ``` 4. 创建文件夹, 用于存放下载的Android源码. ``` // 创建目录 mkdir android_source // 修改权限 chmod 777 android_source cd android_source ``` 5. 初始化库. ``` // 需要先配置git的用户信息 git config --global user.email "ad_307@yeah.net" git config --global user.name "liyindong" repo init -u httpss://android.googlesource.com/platform/manifest -b android-2.3_r1 // 如果上面初始化失败, 用下面的代码 repo init -u git://codeaurora.org/platform/manifest.git -b gingerbread // 或 repo init -u git://android.git.kernel.org/platform/manifest.git -b gingerbread ``` ###### 出现以下信息成功初始化 ``` repo initialized in /home/liyindong/android_source ``` 6. 开始同步下载. ``` repo sync ``` **注意: 下载过程中, 因为网络问题, 可能会中断下载. 当中断下载时, 继续使用repo sync命令继续下载.** # Android源码编译 ### 在编译源码之前需要做一些准备操作, 详细步骤如下: > #### 1. 安装JDK, google官方要求编译2.3源码需要JDK1.6. - 1). 下载JDK1.6, 下载地址:[https://download.oracle.com/otn/java/jdk/6u45-b06/jdk-6u45-linux-x64.bin](https://download.oracle.com/otn/java/jdk/6u45-b06/jdk-6u45-linux-x64.bin) - 2). 创建目录. ``` sudo mkdir /usr/java ``` - 3). 把下载好的jdk-6u45-linux-x64.bin拷贝到上面创建的目录下. ``` sudo cp /home/liyindong/jdk-6u45-linux-x64.bin /usr/java ``` - 4). 添加可执行权限. ``` sudo chmod 755 /usr/java/jdk-6u45-linux-x64.bin ``` - 5). 解压. ``` cd /usr/java sudo ./jdk-6u45-linux-x64.bin ``` - 6). 配置环境变量. ``` export JAVA_HOME=/usr/java/jdk1.6.0_45 export PATH=$PATH:$JAVA_HOME/bin export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar ``` - 7). 验证是否成功. ``` liyindong@liyindong-VirtualBox:~$ java -version java version "1.6.0_45" Java(TM) SE Runtime Environment (build 1.6.0_45-b06) Java HotSpot(TM) 64-Bit Server VM (build 20.45-b01, mixed mode) ``` > #### 2. 安装其他编译时依赖的软件. > > #### 注意: ubuntu自带的源中速度比较慢, 有些软件找不到, 所以需要修改为国内的源, 修改源步骤如下: - 1). 备份ubuntu自带的源. ``` sudo cp /etc/apt/sources.list /etc/apt/sources.list.old ``` - 2). 修改源文件. ``` sudo gedit /etc/apt/sources.list ``` - 3). 这时会弹出一个文本编辑框, 先删除所有内容, 然后把以下内容拷贝进去, 并保存. ``` deb https://mirrors.163.com/ubuntu/ trusty main restricted universe multiverse deb https://mirrors.163.com/ubuntu/ trusty-security main restricted universe multiverse deb https://mirrors.163.com/ubuntu/ trusty-updates main restricted universe multiverse deb https://mirrors.163.com/ubuntu/ trusty-proposed main restricted universe multiverse deb https://mirrors.163.com/ubuntu/ trusty-backports main restricted universe multiverse deb-src https://mirrors.163.com/ubuntu/ trusty main restricted universe multiverse deb-src https://mirrors.163.com/ubuntu/ trusty-security main restricted universe multiverse deb-src https://mirrors.163.com/ubuntu/ trusty-updates main restricted universe multiverse deb-src https://mirrors.163.com/ubuntu/ trusty-proposed main restricted universe multiverse deb-src https://mirrors.163.com/ubuntu/ trusty-backports main restricted universe multiverse deb https://mirrors.sohu.com/ubuntu/ trusty main restricted universe multiverse deb https://mirrors.sohu.com/ubuntu/ trusty-security main restricted universe multiverse deb https://mirrors.sohu.com/ubuntu/ trusty-updates main restricted universe multiverse deb https://mirrors.sohu.com/ubuntu/ trusty-proposed main restricted universe multiverse deb https://mirrors.sohu.com/ubuntu/ trusty-backports main restricted universe multiverse deb-src https://mirrors.sohu.com/ubuntu/ trusty main restricted universe multiverse deb-src https://mirrors.sohu.com/ubuntu/ trusty-security main restricted universe multiverse deb-src https://mirrors.sohu.com/ubuntu/ trusty-updates main restricted universe multiverse deb-src https://mirrors.sohu.com/ubuntu/ trusty-proposed main restricted universe multiverse deb-src https://mirrors.sohu.com/ubuntu/ trusty-backports main restricted universe multiverse deb https://mirrors.oschina.net/ubuntu/ trusty main restricted universe multiverse deb https://mirrors.oschina.net/ubuntu/ trusty-backports main restricted universe multiverse deb https://mirrors.oschina.net/ubuntu/ trusty-proposed main restricted universe multiverse deb https://mirrors.oschina.net/ubuntu/ trusty-security main restricted universe multiverse deb https://mirrors.oschina.net/ubuntu/ trusty-updates main restricted universe multiverse deb-src https://mirrors.oschina.net/ubuntu/ trusty main restricted universe multiverse deb-src https://mirrors.oschina.net/ubuntu/ trusty-backports main restricted universe multiverse deb-src https://mirrors.oschina.net/ubuntu/ trusty-proposed main restricted universe multiverse deb-src https://mirrors.oschina.net/ubuntu/ trusty-security main restricted universe multiverse deb-src https://mirrors.oschina.net/ubuntu/ trusty-updates main restricted universe multiverse ``` - 4). 保存之后, 更新数据源. ``` sudo apt-get update ``` - #### 执行完上面几步, 数据源就更新完成了, 下面就开始安装编译依赖的软件, 同样, 在终端中以行为单位依次输入以下命令: ``` sudo apt-get install gnupg sudo apt-get install flex sudo apt-get install bison sudo apt-get install gperf sudo apt-get install zip sudo apt-get install curl sudo apt-get install build-essential sudo apt-get install libesd0-dev sudo apt-get install libwxgtk2.6-dev sudo apt-get install libsdl-dev sudo apt-get install lsb-core sudo apt-get install lib32readline-gplv2-dev sudo apt-get install g++-multilib sudo apt-get install lib32z1-dev sudo apt-get install libswitch-perl ``` > #### 3. 开始编译, 在源码的目录下, 执行一下命令: ``` make ``` ### 5. 了解SourceInsight的使用方法 ![静默安装和静默偷拍](img/静默安装和静默偷拍-17.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-18.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-19.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-20.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-21.png) ![静默安装和静默偷拍](img/静默安装和静默偷拍-22.png) - 欢迎关注微信公众号,长期推荐技术文章和技术视频 - 微信公众号名称:Android干货程序员 ![img](https://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/源码分析/README.md ================================================ ## 源码分析相关面试题 - [Volley源码剖析](Volley源码剖析.md) - [注解框架内部实现原理](注解框架内部实现原理.md) - [okhttp内核剖析](okhttp内核剖析.md) - [Android源码编译实现静默安装和静默偷拍](Android源码编译实现静默安装和静默偷拍.md) ================================================ FILE: docs/android/Android-Interview/源码分析/Volley源码剖析.md ================================================ 今天就带大家一层层剥光Volley这位懵懂无知少女(14年出生,应该算无知少女)的外衣,带大家轻轻抚摸Volley每一寸肌肤,进入Volley的身体,为达到完美效果,开头录有激情小视频,观看时请自备纸巾,各位现在需要做的就是搬一把椅子,打开电脑,双击Android studio导入Volley源码,随我一起观看录制的激情小视频带着大家一起飞,观看激情小视频过程中,如能让你内心微微一颤有所收获,感受到满满的爱意,点赞或打赏都是最美的祝福。 ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### 本文配套视频: - [Volley内核分析配套视频一](https://v.qq.com/x/page/s05002geql6.html) - [Volley内核分析配套视频二](https://v.qq.com/x/page/h05002uijux.html) - [Volley内核分析配套视频三](https://v.qq.com/x/page/c05005gcs36.html) 通过一个小栗子开始咱们的源码分析 ```java RequestQueue queue = Volley.newRequestQueue(this); String url ="http://www.baidu.com"; StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener() { @Override public void onResponse(String response) { } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } }); queue.add(stringRequest); ``` ### 第一部分:一行行分析 ```java RequestQueue queue = Volley.newRequestQueue(this); ``` 进入源码分析: ```java public static RequestQueue newRequestQueue(Context context) { return newRequestQueue(context, (HttpStack)null); } ``` 由以上源码分析可知: 1)该方法有两个参数,第一个Context是上下文,第二个参数为null ```java public static RequestQueue newRequestQueue(Context context, HttpStack stack) { File cacheDir = new File(context.getCacheDir(), "volley"); String userAgent = "volley/0"; try { String network = context.getPackageName(); PackageInfo queue = context.getPackageManager().getPackageInfo(network, 0); userAgent = network + "/" + queue.versionCode; } catch (NameNotFoundException var6) { } if(stack == null) { if(VERSION.SDK_INT >= 9) { stack = new HurlStack(); } else { stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); } } BasicNetwork network1 = new BasicNetwork((HttpStack)stack); RequestQueue queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1); queue1.start(); return queue1; } ``` 由以上源码分析可知: 1)初始化缓存文件,名字叫volley。 2)获取到当前应用包名和包的基本信息,并且给userAgent 重新赋值。 3)stack 传递过来为null,所以直接走if判断里面。 4)在这个if中对版本号进行了判断,如果版本号大于等于9就使得HttpStack对象的实例为HurlStack,如果小于9则实例为HttpClientStack。至于这里为何进行版本号判断,实际代码中已经说明了,请看下文。 5) BasicNetwork是Network接口的实现,BasicNetwork实现了performRequest方法,其作用是根据传入的HttpStack对象来处理网络请求。紧接着new出一个RequestQueue对象,并调用它的start()方法进行启动,然后将RequestQueue返回。RequestQueue是根目录下的一个类,其作用是一个请求调度队列调度程序的线程池。这样newRequestQueue()的方法就执行结束了。 > 第四条如何分析出来请看如下代码: ```java public class HurlStack implements HttpStack public class HttpClientStack implements HttpStack ``` 继承httpStack接口 ```java public interface HttpStack { HttpResponse performRequest(Request var1, Map var2) throws IOException, AuthFailureError; } ``` 接口当中有如下方法,performRequest,大家有没有觉得奇怪都继承同一个方法,并且都实现一样的接口同样的方法,实际上子类实现方法不一样,代码如下: #### HttpClientStack代码如下: ```java public HttpResponse performRequest(Request request, Map additionalHeaders) throws IOException, AuthFailureError { HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders); addHeaders(httpRequest, additionalHeaders); addHeaders(httpRequest, request.getHeaders()); this.onPrepareRequest(httpRequest); HttpParams httpParams = httpRequest.getParams(); int timeoutMs = request.getTimeoutMs(); HttpConnectionParams.setConnectionTimeout(httpParams, 5000); HttpConnectionParams.setSoTimeout(httpParams, timeoutMs); return this.mClient.execute(httpRequest); } ``` #### HurlStack代码如下: ```java public HttpResponse performRequest(Request request, Map additionalHeaders) throws IOException, AuthFailureError { ...... URL parsedUrl1 = new URL(url); HttpURLConnection connection = this.openConnection(parsedUrl1, request); ....... return response; } } ``` 由以上得知HttpClientStack走的是httpClient,HurlStack走的是HttpURLConnection ,谷歌在高版本当中已经去掉了httpClient,所以这里必须做版本号的判断。 继续源码分析,现在再来看下RequestQueue队列的start方法,如下所示: ```java public void start() { this.stop(); this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery); this.mCacheDispatcher.start(); for(int i = 0; i < this.mDispatchers.length; ++i) { NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery); this.mDispatchers[i] = networkDispatcher; networkDispatcher.start(); } } ``` 以上源码可知: 1)创建了一个CacheDispatcher的实例,然后调用了它的start()方法 2)for循环里去创建NetworkDispatcher的实例,并分别调用它们的start()方法 3)这里的CacheDispatcher和NetworkDispatcher都是继承自Thread的,而默认情况下for循环会执行四次,也就是说当调用了Volley.newRequestQueue(context)之后,就会有五个线程一直在后台运行,不断等待网络请求的到来,其中一个CacheDispatcher是缓存线程,四个NetworkDispatcher是网络请求线程。 > 第三条如何分析出来for循环会执行四次 , 请看如下代码分析: ```java public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { ...... this.mDispatchers = new NetworkDispatcher[threadPoolSize]; ...... } ``` threadPoolSize大小是4,如何看出来请看如下代码: ```java public RequestQueue(Cache cache, Network network, int threadPoolSize) { this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper()))); } public RequestQueue(Cache cache, Network network) { this(cache, network, 4); } ``` 通过构造方法传递,默认是4,如上已经把第一行代码分析完毕,进入第二部分。 ```java RequestQueue queue = Volley.newRequestQueue(this); ``` ### 第二部分:添加请求到队列 ```java StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener() { @Override public void onResponse(String response) { } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } }); queue.add(stringRequest); ``` 调用RequestQueue的add()方法将Request传入就可以完成网络请求操作了。也就是说add()方法的内部是核心代码了。现在看下RequestQueue的add方法,具体如下: ```java public Request add(Request request) { request.setRequestQueue(this); Set var2 = this.mCurrentRequests; synchronized(this.mCurrentRequests) { this.mCurrentRequests.add(request); } request.setSequence(this.getSequenceNumber()); request.addMarker("add-to-queue"); if(!request.shouldCache()) { this.mNetworkQueue.add(request); return request; } else { Map var7 = this.mWaitingRequests; synchronized(this.mWaitingRequests) { String cacheKey = request.getCacheKey(); if(this.mWaitingRequests.containsKey(cacheKey)) { Object stagedRequests = (Queue)this.mWaitingRequests.get(cacheKey); if(stagedRequests == null) { stagedRequests = new LinkedList(); } ((Queue)stagedRequests).add(request); this.mWaitingRequests.put(cacheKey, stagedRequests); } } else { this.mWaitingRequests.put(cacheKey, (Object)null); this.mCacheQueue.add(request); } return request; } ``` 通过以上源码可知: 1)Request是所有请求的基类,是一个抽象类。request.setRequestQueue(this);的作用就是将请求Request关联到当前RequestQueue。然后同步操作将当前Request添加到RequestQueue对象的mCurrentRequests HashSet中做记录。 2)通过request.setSequence(getSequenceNumber());得到当前RequestQueue中请求的个数,然后关联到当前Request。 3)request.addMarker("add-to-queue");添加调试的队列标记。 4)if (!request.shouldCache())判断当前的请求是否可以缓存,如果不能缓存则直接通过mNetworkQueue.add(request);将这条请求加入网络请求队列,然后返回request;如果可以缓存的话则在通过同步操作将这条请求加入缓存队列。 第二部分分析完毕,进入第三部分。 ### 第三部分:CacheDispatcher中的run()方法,代码如下所示: ```java public void run() { if(DEBUG) { VolleyLog.v("start new dispatcher", new Object[0]); } Process.setThreadPriority(10); this.mCache.initialize(); while(true) { final Request e = (Request)this.mCacheQueue.take(); e.addMarker("cache-queue-take"); if(e.isCanceled()) { e.finish("cache-discard-canceled"); } else { Entry entry = this.mCache.get(e.getCacheKey()); if(entry == null) { e.addMarker("cache-miss"); this.mNetworkQueue.put(e); } else if(entry.isExpired()) { e.addMarker("cache-hit-expired"); e.setCacheEntry(entry); this.mNetworkQueue.put(e); } else { e.addMarker("cache-hit"); Response response = e.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders)); e.addMarker("cache-hit-parsed"); if(entry.refreshNeeded()) { e.addMarker("cache-hit-refresh-needed"); e.setCacheEntry(entry); response.intermediate = true; this.mDelivery.postResponse(e, response, new Runnable() { public void run() { CacheDispatcher.this.mNetworkQueue.put(e); } ``` ### 以上源码可知: 1)首先通过Process.setThreadPriority设置线程优先级 2)mCache.initialize(); 初始化缓存块 3)while(true)循环,表示它一直在等待缓存队列的新请求的出现 4)接着,先判断这个请求是否有对应的缓存结果,如果没有则直接添加到网络请求队列 5)再判断这个缓存结果是否过期了,如果过期则同样地添加到网络请求队列 6) 接下来便是对缓存结果的处理了,我们可以看到,先是把缓存结果包装成NetworkResponse类,然后调用了Request的parseNetworkResponse; ### 小结 CacheDispatcher线程主要对请求进行判断,是否已经有缓存,是否已经过期,根据需要放进网络请求队列。同时对相应结果进行包装、处理,然后交由ExecutorDelivery处理。这里以一张流程图显示它的完整工作流程: ![img](http://upload-images.jianshu.io/upload_images/4037105-a98dfd161eafb6fa?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ### 第四部分:NetworkDispatcher代码如下所示: ```java public void run() { Process.setThreadPriority(10); while(true) { Request request; while(true) { try { request = (Request)this.mQueue.take(); break; } catch (InterruptedException var4) { } try { request.addMarker("network-queue-take"); if(request.isCanceled()) { request.finish("network-discard-cancelled"); } else { ...... NetworkResponse e = this.mNetwork.performRequest(request); request.addMarker("network-http-complete"); if(e.notModified && request.hasHadResponseDelivered()) { request.finish("not-modified"); } else { Response response = request.parseNetworkResponse(e); request.addMarker("network-parse-complete"); if(request.shouldCache() && response.cacheEntry != null) { this.mCache.put(request.getCacheKey(), response.cacheEntry); request.addMarker("network-cache-written"); } request.markDelivered(); this.mDelivery.postResponse(request, response); } ``` ### 由以上代码分析可知: 1)通过mNetwork.performRequest(request);代码来发送网络请求 2)而Network是一个接口,这里具体的实现之前已经分析是BasicNetwork,所以先看下它的performRequest()方法,如下所示: ```java public NetworkResponse performRequest(Request request) throws VolleyError { long requestStart = SystemClock.elapsedRealtime(); while(true) { HttpResponse httpResponse = null; Object responseContents = null; HashMap responseHeaders = new HashMap(); try { HashMap e = new HashMap(); this.addCacheHeaders(e, request.getCacheEntry()); httpResponse = this.mHttpStack.performRequest(request, e); ...... byte[] responseContents1; if(httpResponse.getEntity() != null) { responseContents1 = this.entityToBytes(httpResponse.getEntity()); } else { responseContents1 = new byte[0]; } long requestLifetime = SystemClock.elapsedRealtime() - requestStart; this.logSlowRequests(requestLifetime, request, responseContents1, statusCode2); if(networkResponse1 >= 200 && networkResponse1 <= 299) { return new NetworkResponse(networkResponse1, responseContents1, responseHeaders1, false); } } ``` ### 由上述代码可知: 1)httpResponse = this.mHttpStack.performRequest(request, e)该方法返回了httpResponse 2)把httpResponse 交给 new NetworkResponse对象进行处理,封装成NetworkResponse对象并返回 3)在NetworkDispatcher#run()方法获取返回的NetworkResponse对象后,对响应解析 4)在解析完了NetworkResponse中的数据之后,又会调用ExecutorDelivery(ResponseDelivery接口的实现类)的postResponse()方法来回调解析出的数据,具体代码如下所示: ```java this.mDelivery.postResponse(request, response); ``` ```java public void postResponse(Request request, Response response, Runnable runnable) { request.markDelivered(); request.addMarker("post-response"); this.mResponsePoster.execute(new ExecutorDelivery.ResponseDeliveryRunnable(request, response, runnable)); } ``` 这里可以看见在mResponsePoster的execute()方法中传入了一个ResponseDeliveryRunnable对象,就可以保证该对象中的run()方法就是在主线程当中运行的了,我们看下run()方法中的代码是什么样的: ```java if(this.mResponse.isSuccess()) { this.mRequest.deliverResponse(this.mResponse.result); } else { this.mRequest.deliverError(this.mResponse.error); } ``` ### 以上代码可知 mRequest的deliverResponse或者deliverError将反馈发送到回调到UI线程。这也是你重写实现的接口方法,到目前为止,关于Volley的源码解析完毕。 ### 总结 1)当一个RequestQueue被成功申请后会开启一个CacheDispatcher和4个默认的NetworkDispatcher。 2)CacheDispatcher缓存调度器最为第一层缓冲,开始工作后阻塞的从缓存序列mCacheQueue中取得请求;对于已经取消的请求,标记为跳过并结束这个请求;新的或者过期的请求,直接放入mNetworkQueue中由N个NetworkDispatcher进行处理;已获得缓存信息(网络应答)却没有过期的请求,由Request的parseNetworkResponse进行解析,从而确定此应答是否成功。然后将请求和应答交由Delivery分发者进行处理,如果需要更新缓存那么该请求还会被放入mNetworkQueue中。 3)将请求Request add到RequestQueue后对于不需要缓存的请求(需要额外设置,默认是需要缓存)直接丢入mNetworkQueue交给N个NetworkDispatcher处理;对于需要缓存的,新的请求加到mCacheQueue中给CacheDispatcher处理;需要缓存,但是缓存列表中已经存在了相同URL的请求,放在mWaitingQueue中做暂时处理,等待之前请求完毕后,再重新添加到mCacheQueue中。 4)网络请求调度器NetworkDispatcher作为网络请求真实发生的地方,对消息交给BasicNetwork进行处理,同样的,请求和结果都交由Delivery分发者进行处理。 ================================================ FILE: docs/android/Android-Interview/源码分析/okhttp内核剖析.md ================================================ okhttp源码特别特别复杂,类涉及较多,导致本文非常长,我相信没有几个人能把本文看完,所以特意录制了跟文章同步的视频。 ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### 本文配套视频: - [okhttp内核分析配套视频一](https://v.qq.com/x/page/j050015e4sm.html) - [okhttp内核分析配套视频二](https://v.qq.com/x/page/i05006qtood.html) - [okhttp内核分析配套视频三](https://v.qq.com/x/page/y0500461od9.html) #### 基本使用 从使用方法出发,首先是怎么使用,其次是我们使用的功能在内部是如何实现的.建议大家下载 OkHttp 源码之后,跟着本文,过一遍源码。 官方博客栗子:[http://square.github.io/okhttp/#examples](http://square.github.io/okhttp/#examples) ```java OkHttpClient client = new OkHttpClient(); String run(String url) throws IOException { Request request = new Request.Builder() .url(url) .build(); Response response = client.newCall(request).execute(); return response.body().string(); } ``` ### Request、Response、Call 基本概念 上面的代码中涉及到几个常用的类:Request、Response和Call。下面分别介绍: #### Request 每一个HTTP请求包含一个URL、一个方法(GET或POST或其他)、一些HTTP头。请求还可能包含一个特定内容类型的数据类的主体部分。 #### Response 响应是对请求的回复,包含状态码、HTTP头和主体部分。 #### Call OkHttp使用Call抽象出一个满足请求的模型,尽管中间可能会有多个请求或响应。执行Call有两种方式,同步或异步 ### 第一步:创建 OkHttpClient对象,进行源码分析: ```java OkHttpClient client = new OkHttpClient(); ``` 通过okhttp源码分析,直接创建的 OkHttpClient对象并且默认构造builder对象进行初始化 ```java public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory { public OkHttpClient() { this(new Builder()); } OkHttpClient(Builder builder) { this.dispatcher = builder.dispatcher; this.proxy = builder.proxy; this.protocols = builder.protocols; this.connectionSpecs = builder.connectionSpecs; this.interceptors = Util.immutableList(builder.interceptors); this.networkInterceptors = Util.immutableList(builder.networkInterceptors); this.eventListenerFactory = builder.eventListenerFactory; this.proxySelector = builder.proxySelector; this.cookieJar = builder.cookieJar; this.cache = builder.cache; this.internalCache = builder.internalCache; this.socketFactory = builder.socketFactory; boolean isTLS = false; ...... this.hostnameVerifier = builder.hostnameVerifier; this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner( certificateChainCleaner); this.proxyAuthenticator = builder.proxyAuthenticator; this.authenticator = builder.authenticator; this.connectionPool = builder.connectionPool; this.dns = builder.dns; this.followSslRedirects = builder.followSslRedirects; this.followRedirects = builder.followRedirects; this.retryOnConnectionFailure = builder.retryOnConnectionFailure; this.connectTimeout = builder.connectTimeout; this.readTimeout = builder.readTimeout; this.writeTimeout = builder.writeTimeout; this.pingInterval = builder.pingInterval; } } ``` ### 第二步:接下来发起 HTTP 请求 ```java Request request = new Request.Builder().url("url").build(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { } }); ``` ### 第二步:代码流程分析: ```java Request request = new Request.Builder().url("url").build(); ``` 初始化构建者模式和请求对象,并且用URL替换Web套接字URL。 ```java public final class Request { public Builder() { this.method = "GET"; this.headers = new Headers.Builder(); } public Builder url(String url) { ...... // Silently replace web socket URLs with HTTP URLs. if (url.regionMatches(true, 0, "ws:", 0, 3)) { url = "http:" + url.substring(3); } else if (url.regionMatches(true, 0, "wss:", 0, 4)) { url = "https:" + url.substring(4); } HttpUrl parsed = HttpUrl.parse(url); ...... return url(parsed); } public Request build() { ...... return new Request(this); } } ``` ### 第三步:方法解析: ```java okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { } }); ``` 源码分析: ```java public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory { @Override public Call newCall(Request request) { return new RealCall(this, request, false /* for web socket */); } } ``` RealCall实现了Call.Factory接口创建了一个RealCall的实例,而RealCall是Call接口的实现。 ### 异步请求的执行流程 ```java final class RealCall implements Call { @Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); client.dispatcher().enqueue(new AsyncCall(responseCallback)); } } ``` ### 由以上源码得知: 1) 检查这个 call 是否已经被执行了,每个 call 只能被执行一次,如果想要一个完全一样的 call,可以利用 call#clone 方法进行克隆。 2)利用 client.dispatcher().enqueue(this) 来进行实际执行,dispatcher 是刚才看到的 OkHttpClient.Builder 的成员之一 3)AsyncCall是RealCall的一个内部类并且继承NamedRunnable,那么首先看NamedRunnable类是什么样的,如下: ```java public abstract class NamedRunnable implements Runnable { ...... @Override public final void run() { ...... try { execute(); } ...... } protected abstract void execute(); } ``` 可以看到NamedRunnable实现了Runnbale接口并且是个抽象类,其抽象方法是execute(),该方法是在run方法中被调用的,这也就意味着NamedRunnable是一个任务,并且其子类应该实现execute方法。下面再看AsyncCall的实现: ```java final class AsyncCall extends NamedRunnable { private final Callback responseCallback; AsyncCall(Callback responseCallback) { super("OkHttp %s", redactedUrl()); this.responseCallback = responseCallback; } ...... final class RealCall implements Call { @Override protected void execute() { boolean signalledCallback = false; try { Response response = getResponseWithInterceptorChain(); if (retryAndFollowUpInterceptor.isCanceled()) { signalledCallback = true; responseCallback.onFailure(RealCall.this, new IOException("Canceled")); } else { signalledCallback = true; responseCallback.onResponse(RealCall.this, response); } } catch (IOException e) { ...... responseCallback.onFailure(RealCall.this, e); } finally { client.dispatcher().finished(this); } } ``` AsyncCall实现了execute方法,首先是调用getResponseWithInterceptorChain()方法获取响应,然后获取成功后,就调用回调的onReponse方法,如果失败,就调用回调的onFailure方法。最后,调用Dispatcher的finished方法。 关键代码: responseCallback.onFailure(RealCall.this, new IOException("Canceled")); 和 responseCallback.onResponse(RealCall.this, response); 走完这两句代码会进行回调到刚刚我们初始化Okhttp的地方,如下: ```java okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { } }); ``` ### 核心重点类Dispatcher线程池介绍 ```java public final class Dispatcher { /** 最大并发请求数为64 */ private int maxRequests = 64; /** 每个主机最大请求数为5 */ private int maxRequestsPerHost = 5; /** 线程池 */ private ExecutorService executorService; /** 准备执行的请求 */ private final Deque readyAsyncCalls = new ArrayDeque<>(); /** 正在执行的异步请求,包含已经取消但未执行完的请求 */ private final Deque runningAsyncCalls = new ArrayDeque<>(); /** 正在执行的同步请求,包含已经取消单未执行完的请求 */ private final Deque runningSyncCalls = new ArrayDeque<>(); ``` 在OkHttp,使用如下构造了单例线程池 ```java public synchronized ExecutorService executorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; } ``` 构造一个线程池ExecutorService: ```java executorService = new ThreadPoolExecutor( //corePoolSize 最小并发线程数,如果是0的话,空闲一段时间后所有线程将全部被销毁 0, //maximumPoolSize: 最大线程数,当任务进来时可以扩充的线程最大值,当大于了这个值就会根据丢弃处理机制来处理 Integer.MAX_VALUE, //keepAliveTime: 当线程数大于corePoolSize时,多余的空闲线程的最大存活时间 60, //单位秒 TimeUnit.SECONDS, //工作队列,先进先出 new SynchronousQueue(), //单个线程的工厂 Util.threadFactory("OkHttp Dispatcher", false)); ``` 可以看出,在Okhttp中,构建了一个核心为[0, Integer.MAX_VALUE]的线程池,它不保留任何最小线程数,随时创建更多的线程数,当线程空闲时只能活60秒,它使用了一个不存储元素的阻塞工作队列,一个叫做"OkHttp Dispatcher"的线程工厂。 也就是说,在实际运行中,当收到10个并发请求时,线程池会创建十个线程,当工作完成后,线程池会在60s后相继关闭所有线程。 ```java synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } } ``` 从上述源码分析,如果当前还能执行一个并发请求,则加入 runningAsyncCalls ,立即执行,否则加入 readyAsyncCalls 队列。 #### Dispatcher线程池总结 1)调度线程池Disptcher实现了高并发,低阻塞的实现 2)采用Deque作为缓存,先进先出的顺序执行 3)任务在try/finally中调用了finished函数,控制任务队列的执行顺序,而不是采用锁,减少了编码复杂性提高性能 这里是分析OkHttp源码,并不详细讲线程池原理,如对线程池不了解请参考如下链接 [点我,线程池原理,在文章性能优化最后有视频对线程池原理讲解](http://www.jianshu.com/p/c22398f8587f) ```java try { Response response = getResponseWithInterceptorChain(); if (retryAndFollowUpInterceptor.isCanceled()) { signalledCallback = true; responseCallback.onFailure(RealCall.this, new IOException("Canceled")); } else { signalledCallback = true; responseCallback.onResponse(RealCall.this, response); } } finally { client.dispatcher().finished(this); } ``` 当任务执行完成后,无论是否有异常,finally代码段总会被执行,也就是会调用Dispatcher的finished函数 ```java void finished(AsyncCall call) { finished(runningAsyncCalls, call, true); } ``` 从上面的代码可以看出,第一个参数传入的是正在运行的异步队列,第三个参数为true,下面再看有是三个参数的finished方法: ```java private void finished(Deque calls, T call, boolean promoteCalls) { int runningCallsCount; Runnable idleCallback; synchronized (this) { if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!"); if (promoteCalls) promoteCalls(); runningCallsCount = runningCallsCount(); idleCallback = this.idleCallback; } if (runningCallsCount == 0 && idleCallback != null) { idleCallback.run(); } } ``` 打开源码,发现它将正在运行的任务Call从队列runningAsyncCalls中移除后,获取运行数量判断是否进入了Idle状态,接着执行promoteCalls()函数,下面是promoteCalls()方法: ```java private void promoteCalls() { if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity. if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote. for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCall call = i.next(); if (runningCallsForHost(call) < maxRequestsPerHost) { i.remove(); runningAsyncCalls.add(call); executorService().execute(call); } if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity. } } ``` 主要就是遍历等待队列,并且需要满足同一主机的请求小于maxRequestsPerHost时,就移到运行队列中并交给线程池运行。就主动的把缓存队列向前走了一步,而没有使用互斥锁等复杂编码 ### 核心重点getResponseWithInterceptorChain方法 ```java Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List interceptors = new ArrayList<>(); interceptors.addAll(client.interceptors()); interceptors.add(retryAndFollowUpInterceptor); interceptors.add(new BridgeInterceptor(client.cookieJar())); interceptors.add(new CacheInterceptor(client.internalCache())); interceptors.add(new ConnectInterceptor(client)); if (!forWebSocket) { interceptors.addAll(client.networkInterceptors()); } interceptors.add(new CallServerInterceptor(forWebSocket)); Interceptor.Chain chain = new RealInterceptorChain( interceptors, null, null, null, 0, originalRequest); return chain.proceed(originalRequest); } ``` ![img](http://upload-images.jianshu.io/upload_images/4037105-e7f17417cf6fa30a?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 1)在配置 OkHttpClient 时设置的 interceptors; 2)负责失败重试以及重定向的 RetryAndFollowUpInterceptor; 3)负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的 BridgeInterceptor; 4)负责读取缓存直接返回、更新缓存的 CacheInterceptor; 5)负责和服务器建立连接的 ConnectInterceptor; 6)配置 OkHttpClient 时设置的 networkInterceptors; 7)负责向服务器发送请求数据、从服务器读取响应数据的 CallServerInterceptor。 OkHttp的这种拦截器链采用的是责任链模式,这样的好处是将请求的发送和处理分开,并且可以动态添加中间的处理方实现对请求的处理、短路等操作。 从上述源码得知,不管okhttp有多少拦截器最后都会走,如下方法: ```java Interceptor.Chain chain = new RealInterceptorChain( interceptors, null, null, null, 0, originalRequest); return chain.proceed(originalRequest); ``` 从方法名字基本可以猜到是干嘛的,调用 chain.proceed(originalRequest); 将request传递进来,从拦截器链里拿到返回结果。那么拦截器Interceptor是干嘛的,Chain是干嘛的呢?继续往下看RealInterceptorChain RealInterceptorChain类 下面是RealInterceptorChain的定义,该类实现了Chain接口,在getResponseWithInterceptorChain调用时好几个参数都传的null。 ```java public final class RealInterceptorChain implements Interceptor.Chain { public RealInterceptorChain(List interceptors, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection, int index, Request request) { this.interceptors = interceptors; this.connection = connection; this.streamAllocation = streamAllocation; this.httpCodec = httpCodec; this.index = index; this.request = request; } ...... @Override public Response proceed(Request request) throws IOException { return proceed(request, streamAllocation, httpCodec, connection); } public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { if (index >= interceptors.size()) throw new AssertionError(); calls++; ...... // Call the next interceptor in the chain. RealInterceptorChain next = new RealInterceptorChain( interceptors, streamAllocation, httpCodec, connection, index + 1, request); Interceptor interceptor = interceptors.get(index); Response response = interceptor.intercept(next); ...... return response; } protected abstract void execute(); } ``` 主要看proceed方法,proceed方法中判断index(此时为0)是否大于或者等于client.interceptors(List )的大小。由于httpStream为null,所以首先创建next拦截器链,主需要把索引置为index+1即可;然后获取第一个拦截器,调用其intercept方法。 Interceptor 代码如下: ```java public interface Interceptor { Response intercept(Chain chain) throws IOException; interface Chain { Request request(); Response proceed(Request request) throws IOException; Connection connection(); } } ``` BridgeInterceptor BridgeInterceptor从用户的请求构建网络请求,然后提交给网络,最后从网络响应中提取出用户响应。从最上面的图可以看出,BridgeInterceptor实现了适配的功能。下面是其intercept方法: ```java public final class BridgeInterceptor implements Interceptor { ...... @Override public Response intercept(Chain chain) throws IOException { Request userRequest = chain.request(); Request.Builder requestBuilder = userRequest.newBuilder(); RequestBody body = userRequest.body(); //如果存在请求主体部分,那么需要添加Content-Type、Content-Length首部 if (body != null) { MediaType contentType = body.contentType(); if (contentType != null) { requestBuilder.header("Content-Type", contentType.toString()); } long contentLength = body.contentLength(); if (contentLength != -1) { requestBuilder.header("Content-Length", Long.toString(contentLength)); requestBuilder.removeHeader("Transfer-Encoding"); } else { requestBuilder.header("Transfer-Encoding", "chunked"); requestBuilder.removeHeader("Content-Length"); } } if (userRequest.header("Host") == null) { requestBuilder.header("Host", hostHeader(userRequest.url(), false)); } if (userRequest.header("Connection") == null) { requestBuilder.header("Connection", "Keep-Alive"); } // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing // the transfer stream. boolean transparentGzip = false; if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) { transparentGzip = true; requestBuilder.header("Accept-Encoding", "gzip"); } List cookies = cookieJar.loadForRequest(userRequest.url()); if (!cookies.isEmpty()) { requestBuilder.header("Cookie", cookieHeader(cookies)); } if (userRequest.header("User-Agent") == null) { requestBuilder.header("User-Agent", Version.userAgent()); } Response networkResponse = chain.proceed(requestBuilder.build()); HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers()); Response.Builder responseBuilder = networkResponse.newBuilder() .request(userRequest); if (transparentGzip && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding")) && HttpHeaders.hasBody(networkResponse)) { GzipSource responseBody = new GzipSource(networkResponse.body().source()); Headers strippedHeaders = networkResponse.headers().newBuilder() .removeAll("Content-Encoding") .removeAll("Content-Length") .build(); responseBuilder.headers(strippedHeaders); responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody))); } return responseBuilder.build(); } /** Returns a 'Cookie' HTTP request header with all cookies, like {@code a=b; c=d}. */ private String cookieHeader(List cookies) { StringBuilder cookieHeader = new StringBuilder(); for (int i = 0, size = cookies.size(); i < size; i++) { if (i > 0) { cookieHeader.append("; "); } Cookie cookie = cookies.get(i); cookieHeader.append(cookie.name()).append('=').append(cookie.value()); } return cookieHeader.toString(); } } ``` 从上面的代码可以看出,首先获取原请求,然后在请求中添加头,比如Host、Connection、Accept-Encoding参数等,然后根据看是否需要填充Cookie,在对原始请求做出处理后,使用chain的procced方法得到响应,接下来对响应做处理得到用户响应,最后返回响应。接下来再看下一个拦截器ConnectInterceptor的处理。 ```java public final class ConnectInterceptor implements Interceptor { ...... @Override public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; Request request = realChain.request(); StreamAllocation streamAllocation = realChain.streamAllocation(); // We need the network to satisfy this request. Possibly for validating a conditional GET. boolean doExtensiveHealthChecks = !request.method().equals("GET"); HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks); RealConnection connection = streamAllocation.connection(); return realChain.proceed(request, streamAllocation, httpCodec, connection); } } ``` 实际上建立连接就是创建了一个 HttpCodec 对象,它利用 Okio 对 Socket 的读写操作进行封装,Okio 以后有机会再进行分析,现在让我们对它们保持一个简单地认识:它对 java.io 和 java.nio 进行了封装,让我们更便捷高效的进行 IO 操作。 CallServerInterceptor CallServerInterceptor是拦截器链中最后一个拦截器,负责将网络请求提交给服务器。它的intercept方法实现如下: ```java @Override public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; HttpCodec httpCodec = realChain.httpStream(); StreamAllocation streamAllocation = realChain.streamAllocation(); RealConnection connection = (RealConnection) realChain.connection(); Request request = realChain.request(); long sentRequestMillis = System.currentTimeMillis(); httpCodec.writeRequestHeaders(request); Response.Builder responseBuilder = null; if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) { // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100 // Continue" response before transmitting the request body. If we don't get that, return what // we did get (such as a 4xx response) without ever transmitting the request body. if ("100-continue".equalsIgnoreCase(request.header("Expect"))) { httpCodec.flushRequest(); responseBuilder = httpCodec.readResponseHeaders(true); } if (responseBuilder == null) { // Write the request body if the "Expect: 100-continue" expectation was met. Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength()); BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); request.body().writeTo(bufferedRequestBody); bufferedRequestBody.close(); } else if (!connection.isMultiplexed()) { // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection from // being reused. Otherwise we're still obligated to transmit the request body to leave the // connection in a consistent state. streamAllocation.noNewStreams(); } } httpCodec.finishRequest(); if (responseBuilder == null) { responseBuilder = httpCodec.readResponseHeaders(false); } Response response = responseBuilder .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); int code = response.code(); if (forWebSocket && code == 101) { // Connection is upgrading, but we need to ensure interceptors see a non-null response body. response = response.newBuilder() .body(Util.EMPTY_RESPONSE) .build(); } else { response = response.newBuilder() .body(httpCodec.openResponseBody(response)) .build(); } if ("close".equalsIgnoreCase(response.request().header("Connection")) || "close".equalsIgnoreCase(response.header("Connection"))) { streamAllocation.noNewStreams(); } if ((code == 204 || code == 205) && response.body().contentLength() > 0) { throw new ProtocolException( "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength()); } return response; } ``` 从上面的代码中可以看出,首先获取HttpStream对象,然后调用writeRequestHeaders方法写入请求的头部,然后判断是否需要写入请求的body部分,最后调用finishRequest()方法将所有数据刷新给底层的Socket,接下来尝试调用readResponseHeaders()方法读取响应的头部,然后再调用openResponseBody()方法得到响应的body部分,最后返回响应。 ### 最后总结 OkHttp的底层是通过Java的Socket发送HTTP请求与接受响应的(这也好理解,HTTP就是基于TCP协议的),但是OkHttp实现了连接池的概念,即对于同一主机的多个请求,其实可以公用一个Socket连接,而不是每次发送完HTTP请求就关闭底层的Socket,这样就实现了连接池的概念。而OkHttp对Socket的读写操作使用的OkIo库进行了一层封装。 ![img](http://upload-images.jianshu.io/upload_images/4037105-dc97d1a43aed5334?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - 欢迎关注微信公众号,长期推荐技术文章和技术视频 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/源码分析/注解框架内部实现原理.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### 本文配套视频: - [配套视频](https://v.qq.com/x/page/l0397qgxmkc.html) ## 注解框架实现原理,手写ButterKnife实现自己的注解框架 初级程序员使用别人的框架,中级程序员不仅会使用别人的框架还知道内部的实现原理,高级程序员则按需编写自己的框架。添加该模块的目的就是想提交大家的逼格,让大家养成一个动手编写“自主知识产权”框架的意识。 ### 1. 编写 ButterKnife框架 业界比较出名的基于完全注解方式就可以进行 UI 绑定和事件绑定,无需 findViewById 和 setClickListener 等的IOC(Inverse Of Control 控制反转,就是将 UI 的初始化和事件绑定的“权利”交给框架来完成)框架有: ButterKnife使用如下: ![img](http://upload-images.jianshu.io/upload_images/4037105-2a363e9758530940.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![img](http://upload-images.jianshu.io/upload_images/4037105-101de171c6978d65.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 会出现如下代码: ```java @BindView(R.id.button01) Button mButton01; @BindView(R.id.button02) Button mButton02; @BindView(R.id.button03) Button mButton03; @butterknife.OnClick({R.id.button01, R.id.button02, R.id.button03}) public void onClick(View view) { switch (view.getId()) { case R.id.button01: Toast.makeText(this, "butterknife-button01", Toast.LENGTH_SHORT).show(); break; case R.id.button02: Toast.makeText(this, "butterknife-button02", Toast.LENGTH_SHORT).show(); break; case R.id.button03: Toast.makeText(this, "butterknife-button03", Toast.LENGTH_SHORT).show(); break; } } ``` 点击每个按钮会弹出响应的Toast,如下: ![img](http://upload-images.jianshu.io/upload_images/4037105-4fe5cffabd706277.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 这个就是ButterKnife的用法。 ### 接下来,我们开始编写自己的框架实现 findViewById 和 setOnClickListener 功能。 1. 编写自定义注解类 ViewInject 和 Click; ViewInject 注解类用于添加在 Filed 上。Click 注解类用于添加到 Method 上。 【文件】ViewInject.java ```java /** * @Retention 用于声明该注解生效的生命周期,有三个枚举值可以选择
* 1. RetentionPolicy.SOURCE 注释只保留在源码上面,编译成class的时候自动被编译器抹除 * 2. RetentionPolicy.CLASS 注释只保留到字节码上面,VM加载字节码时自动抹除 * 3. RetentionPolicy.RUNTIME 注释永久保留,可以被VM加载时加载到内存中 * 注意:由于我们的目的是想在VM运行时对Filed上的该注解进行反射操作,因此Retention值必须设置为RUNTIME * * @Target 用于指定该注解可以声明在哪些成员上面,常见的值有FIELD和Method, 由于我们的当前注解类是想声明在Filed上面 * 因此这里设置为ElementType.FIELD。 * 注意:如果@Target值不设置,则默认可以添加到任何元素上,不推荐这么写。 * * @interface 是声明注解类的组合关键字。 */ @Target({java.lang.annotation.ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface ViewInject { public abstract int value(); } ``` 【文件】Click.java ```java @Target({java.lang.annotation.ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface OnClick { public abstract int[] value(); } ``` 编写核心方法 ViewUtilsTest.inject(Ativity); ```java public class ViewUtilsTest { public static void inject(final Activity activity) { /** * 通过字节码获取activity类中所有的字段,在获取Field的时候一定要使用 * getDeclaredFields(), * 因为只有该方法才能获取到任何权限修饰的Filed,包括私有的。 */ Class clazz = activity.getClass(); Field[] declaredFields = clazz.getDeclaredFields(); //一个Activity中可能有多个Field,因此遍历。 for (int i = 0; i < declaredFields.length; i++) { Field field = declaredFields[i]; //设置为可访问,暴力反射,就算是私有的也能访问到 field.setAccessible(true); //获取到字段上面的注解对象 ViewInject annotation = (ViewInject)field.getAnnotation(ViewInject.class); //一定对annotation是否等于null进行判断,因为并不是所有Filed上都有我们想要的注解 if (annotation == null) { continue; } //获取注解中的值 int id = annotation.value(); //获取控件 View view = activity.findViewById(id); try { //将该控件设置给field对象 field.set(activity, view); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } //获取所有的方法(私有方法也可以获取到) Method[] declaredMethods = clazz.getDeclaredMethods(); for (int i = 0; i < declaredMethods.length; i++) { final Method method = declaredMethods[i]; //获取方法上面的注解 OnClick annotation = (OnClick)method.getAnnotation(OnClick.class); if (annotation == null) { //如果该方法上没有注解,循环下一个 continue; } //获取注解中的数据,因为可以给多个button绑定点击事件,因此定义注解类时使用的是int[]作为数据类型。 int[] value = annotation.value(); for (int j = 0; j < value.length; j++) { int id = value[j]; final View button = activity.findViewById(id); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { try { //反射调用用户指定的方法 method.invoke(activity,button); } catch (Exception e) { e.printStackTrace(); } } }); } } } } ``` 咱们自己的注解框架就实现好了,效果如下: ![img](http://upload-images.jianshu.io/upload_images/4037105-ae99d1e8059313de.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - 欢迎关注微信公众号,长期推荐技术文章和技术视频 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/登陆注册/Oauth的实现原理.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### 本文配套视频 - [配套视频](https://v.qq.com/x/page/p03953hoam3.html) ## 腾讯QQ第三方登录的实现原理? > Oauth当中的角色: 1. Service Provider(服务提供方) 服务提供方通常是网站,在这些网站当中存储着一些受限制的资源,如照片、视频、联系人列表等。这些网站通常使用用户名和密码来确认用户的身份。比如新浪微博的开放平台就是Service Provider。 2. User(用户) 存放在服务提供方的受保护的资源的所有者。用户持有可以登录服务提供者网站的用户名和密码。用户不希望把自己的资源公开,但是用户却需要将这些资源共享给其他网站或应用程序(如用户希望使用第三方开发的新浪微博客户端来访问自己的资源,但又不希望第三方应用知道自己的用户名和密码)。 3. 客户端(Client) 要访问服务提供方资源的第三方应用,可以是web应用程序、桌面应用程序或者是手机应用程序。客户端需要得到授权之后才能访问相应的资源。 > Oauth的认证和授权过程: 1. 用户使用第三方的客户端(如访问第三方的网站,或使用第三方的应用),想对存放在服务提供者的某些资源进行操作 2. 第三方网站或应用向服务提供方请求一个临时令牌(Request Token) 3. 服务提供方验证第三方的身份后,授予其一个临时令牌 4. 第三方获得临时令牌后,将用户引导至服务提供方的授权页面请求用户授权。在这个过程中将临时令牌和客户端的回调连接发送给服务提供者 5. 用户在服务提供者的授权页面上输入自己的用户名和密码,然后授权该客户端访问相应的资源 6. 授权成功后,服务提供方引导用户返回第三方网站的的网页 7. 客户端根据临时令牌从服务提供方那里获取访问令牌(Access Token) 8. 服务提供方根据临时令牌和用户的授权情况授予客户端访问令牌 9. 客户端使用获取的访问令牌访问存放在服务提供方上的相应的资源 Oauth简单示意图 ![img](http://upload-images.jianshu.io/upload_images/4037105-b1e90838ef009145.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) Oauth全面的示意图 ![img](http://upload-images.jianshu.io/upload_images/4037105-7931074dfb4fa188.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - 欢迎关注微信公众号,长期推荐技术文章和技术视频 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/登陆注册/README.md ================================================ ## 与登录相关面试题 - [Oauth的实现原理](Oauth的实现原理.md) - [Token的实际意义](token的实际意义.md) - [微信扫码登录内部实现原理](微信扫码登录内部实现原理.md) ================================================ FILE: docs/android/Android-Interview/登陆注册/Token的实际意义.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) 今天文章比较简单,主要是为了录制面试题系列,保证文章的完整性来帮助那些想找工作的哥们,各位高级程序员请勿拍砖,不过把下面三段视频都看完,多多少少会有些收获。。。 ### 本文配套视频: - [为什么需要token配套视频](https://v.qq.com/x/page/c0395s3jd4f.html) - [一分钟登录配套视频](https://v.qq.com/x/page/p0395khlfdz.html) - [一分钟解析登录配套视频](https://v.qq.com/x/page/r03959jvnjm.html) ## 在android开发中,用户登录时,客户端会接收到token值,请描述一下对token的理解? ### Token的引入: Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。 ### Token的定义: Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。 使用Token的目的: Token的目的是为了验证用户登录情况以及减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。 ### Token的应用: 1. 当用户首次登录成功之后, 服务器端就会生成一个 token 值,这个值,会在服务器保存token值(保存在数据库中),再将这个token值返回给客户端 2. 客户端拿到 token 值之后,使用sp进行保存 3. 以后客户端再次发送网络请求(一般不是登录请求)的时候,就会将这个 token 值附带到参数中发送给服务器 4. 服务器接收到客户端的请求之后,会取出token值与保存在本地(数据库)中的token值做对比 5. 如果两个 token 值相同, 说明用户登录成功过!当前用户处于登录状态 6. 如果没有这个 token 值, 没有登录成功 7. 如果 token 值不同: 说明原来的登录信息已经失效,让用户重新登录 ### 使用一分钟利用开源中国接口获取登录token ### 利用一分钟时间解析服务器返回的token数据 懒得写文字了,直接看视频吧,看完会有不小的收获哦 ## 登录和注销 (Cookie, Session) * 登陆过程 1. 先把用户名和密码传给服务器(请求头中没有Cookie) 2. 服务器验证用户名和密码 3. 验证通过->响应头中返回(Cookies)一个或多个key=value;key1=value1 4. 客户端保存这些Cookies到本地文件. * 请求数据过程 1. 如果没有Cookie, 只能获取到公共信息. 2. 把Cookie放到请求头中, 可以获取到Cookie对应的账号的隐私数据. 3. 如果Cookie超时了(一小时, 一周, 一个月), 服务器同样不返回隐私数据. * 注销过程 1. 删除本地的Cookie 2. 以后的请求头中都没有Cookie 3. 服务器不会再知道客户端是谁, 会话结束. - 欢迎关注微信公众号,长期推荐技术文章和技术视频 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/登陆注册/微信扫码登录内部实现原理.md ================================================ ### 源码分析相关面试题 - [Volley源码分析](http://www.jianshu.com/p/ec3dc92df581) - [注解框架实现原理](http://www.jianshu.com/p/20da6d6389e1) - [okhttp3.0源码分析](http://www.jianshu.com/p/9ed2c2f2a52c) - [onSaveInstanceState源码分析](http://www.jianshu.com/p/cbf9c3557d64) - [静默安装和源码编译](http://www.jianshu.com/p/2211a5b3c37f) ### Activity相关面试题 - [保存Activity的状态](http://www.jianshu.com/p/cbf9c3557d64) ### 与XMPP相关面试题 - [XMPP协议优缺点](http://www.jianshu.com/p/2c04ac3c526a) - [极光消息推送原理](http://www.jianshu.com/p/d88dc66908cf) ### 与性能优化相关面试题 - [内存泄漏和内存溢出区别](http://www.jianshu.com/p/5dd645b05c76) - [UI优化和线程池实现原理](http://www.jianshu.com/p/c22398f8587f) - [代码优化](http://www.jianshu.com/p/ebd41eab90df) - [内存性能分析](http://www.jianshu.com/p/2665c31b9c2f) - [内存泄漏检测](http://www.jianshu.com/p/1514c7804a06) - [App启动优化](http://www.jianshu.com/p/f0f73fefdd43) - [与IPC机制相关面试题](http://www.jianshu.com/p/de4793a4c2d0) ### 与登录相关面试题 - [oauth认证协议原理](http://www.jianshu.com/p/2a6ecbf8d49d) - [token产生的意义](http://www.jianshu.com/p/9b7ce2d6c195) - [微信扫一扫实现原理](http://www.jianshu.com/p/a9d1f21bd5e0) ### 与开发相关面试题 - [迭代开发的时候如何向前兼容新旧接口](http://www.jianshu.com/p/cbecadec98de) - [手把手教你如何解决as jar包冲突](http://www.jianshu.com/p/30fdc391289c) - [context的原理分析](http://www.jianshu.com/p/2706c13a1769) - [解决ViewPager.setCurrentItem中间很多页面切换方案](http://www.jianshu.com/p/38ab6d856b56) ### 与人事相关面试题 - [人事面试宝典](http://www.jianshu.com/p/d61b553ff8c9) ### 本文配套视频 - [配套视频](https://v.qq.com/x/page/u03952rbbkc.html) ## 26微信扫码登录内部实现原理? ### 打开网页版微信,可以看到如下的页面: ![img](http://upload-images.jianshu.io/upload_images/4037105-c80fa67b87efba8b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 如果你用我查查、支付宝、新浪微博等软件扫码二维码,你会发现此二维码解析出来是如下的网址: ``` https://login.weixin.qq.com/l/obsbQ-Dzag== ``` 接下来详细介绍一下扫码登录具体的每个步骤: ![img](http://upload-images.jianshu.io/upload_images/4037105-05483767b3c483c7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ① :用户 A 访问微信网页版,微信服务器为这个会话生成一个全局唯一的 UUID二维码,上面的 URL 中 obsbQ-Dzag== 就是这个 UUID,且每次刷新后都会改变。这样可以保证一个UUID只可以绑定一个账号和密码,确定登录用户的唯一性。我刷新三次,扫描结果如下,其中最后面那串数字就是UUID:此时系统并不知道访问者是谁。 ![img](http://upload-images.jianshu.io/upload_images/4037105-91bc93e6ba3c5418.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![img](http://upload-images.jianshu.io/upload_images/4037105-ad2ce3ee1eee2a4b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ② :除了返回唯一的uid,实际上打开这个页面的时候,浏览器跟服务器还创建了一个长连接,请求uid的扫描记录。如果没有,在特定时长后会接到状态码408(请求超时),表示应该继续下一次请求;如果接到状态码201(服务器创建新资源成功),表示客户端扫描了该二维码。 请求超时:返回408 ![img](http://upload-images.jianshu.io/upload_images/4037105-ec68032ffe40ad0f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 扫码成功:返回201 ![img](http://upload-images.jianshu.io/upload_images/4037105-ce5fbb042498a8ef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ③:手机上的微信是登录状态,用户点击确认登录后,手机上的微信客户端将微信账号和这个扫描得到的 ID 一起提交到服务器 ![img](http://upload-images.jianshu.io/upload_images/4037105-71c47fa7f96bbcfc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ④ :服务器将这个 ID 和用户 A 的微信号绑定在一起,并通知网页版微信,这个 ID 对应的微信号为用户 A,网页版微信加载用户 A 的微信信息,至此,扫码登录全部流程完成 总的来说,微信扫码登录核心过程应该是这样的:浏览器获得一个唯一的、临时的UUID,通过长连接等待客户端扫描带有此UUID的二维码后,从长连接中获得客户端上报给服务器的帐号信息进行展示。并在客户端点击确认后,获得服务器授信的令牌,进行随后的信息交互过程。 在超时、网络断开、其他设备上登录后,此前获得的令牌或丢失、或失效,对授权过程形成有效的安全防护,类似的应用还有扫码支付、扫码加公众号等功能. - 欢迎关注微信公众号,长期推荐技术文章和技术视频 - 微信公众号名称:Android干货程序员 ![img](http://upload-images.jianshu.io/upload_images/4037105-8f737b5104dd0b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ================================================ FILE: docs/android/Android-Interview/经验分享/2016年4月某公司面试题及面试流程.md ================================================ ## 2016年4月某公司面试题及面试流程 ### **1. 静态内部类、内部类、匿名内部类,为什么内部类会持有外部类的引用?持有的引用是this?还是其它?** - 静态内部类:使用static修饰的内部类 - 内部类:就是在某个类的内部又定义了一个类,内部类所嵌入的类称为外部类 - 匿名内部类:使用new生成的内部类 - 因为内部类的产生依赖于外部类,持有的引用是类名.this ### **2. ArrayList和Vector的主要区别是什么?** ArrayList在Java1.2引入,用于替换Vector Vector:线程同步,当Vector中的元素超过它的初始大小时,Vector会将它的容量翻倍 ArrayList:线程不同步,但性能很好,当ArrayList中的元素超过它的初始大小时,ArrayList只增加50%的大小 [java集合类框架](http://yuweiguocn.github.io/java-collection/) http://blog.csdn.net/axi295309066/article/details/54089986 ### **3. Java中try catch finally的执行顺序** 先执行try中代码发生异常执行catch中代码,最后一定会执行finally中代码 ### **4. switch是否能作用在byte上,是否能作用在long上,是否能作用在String上?** switch支持使用byte类型,不支持long类型,String支持在java1.7引入 ### **5. Activity和Fragment生命周期有哪些?** - Activity onCreate→onStart→onResume→onPause→onStop→onDestroy - Fragment onAttach→onCreate→onCreateView→onActivityCreated→onStart→onResume→onPause→onStop→onDestroyView→onDestroy→onDetach ### **6. onInterceptTouchEvent()和onTouchEvent()的区别?** onInterceptTouchEvent()用于拦截触摸事件 onTouchEvent()用于处理触摸事件 ### **7. RemoteView在哪些功能中使用** APPwidget和Notification中 ### **8. SurfaceView和View的区别是什么?** SurfaceView中采用了双缓存技术,在单独的线程中更新界面 View在UI线程中更新界面 ### **9. 讲一下android中进程的优先级?** - 前台进程 - 可见进程 - 服务进程 - 后台进程 - 空进程 ### 10. 代码查错题,没记下来 tips:静态变量持有Activity引用会导致内存泄露 ### 11. 一面 service生命周期,可以执行耗时操作吗? JNI开发流程 Java线程池,线程同步 自己设计一个图片加载框架 自定义View相关方法 http ResponseCode 插件化,动态加载 性能优化,MAT AsyncTask原理 65k限制 Serializable和Parcelable 文件和数据库哪个效率高 断点续传 WebView和JS Android基础——Service Android基础——IntentService Android开发指导——Service Android开发指导——绑定Service Android开发指导——进程间通信AIDL Android面试基础知识总结(一) Android面试——APP性能优化 [Android中Java和JavaScript交互](http://droidyue.com/blog/2014/09/20/interaction-between-java-and-javascript-in-android/) [WebView 远程代码执行漏洞浅析](http://jaq.alibaba.com/blog.htm?spm=0.0.0.0.oMsDAl&id=48) [WebView中的Java与JavaScript提供【安全可靠】的多样互通方案](https://github.com/pedant/safe-java-js-webview-bridge) ### 12. 二面 所使用的开源框架的实现原理,源码 没看过,被pass了 去面试之前把用到的开源框架源码分析一定要看看啊 [codekk:开源框架源码解析](http://codekk.com/open-source-project-analysis) [2016Android某公司面试题](http://yuweiguocn.github.io/interview-2016-big-company/) ================================================ FILE: docs/android/Android-Interview/经验分享/2017届实习生招聘面经.md ================================================ [2017届实习生招聘面经(今日头条,腾讯,阿里,360)](http://www.jianshu.com/p/12654b063553) # 1. 阿里内推 在三月的某一天,当我还沉浸在代码世界的时候,突然一声铃声响,拿起手机一看,杭州电话==大三春招第一次面试开始了。 ## 阿里一面 问的问题不多,也就26分钟的样子 你用过哪些集合类?==太多了,随便说了些 那你说说ArrayList,LinkedList的区别(还是挺简单的,一般用过的都说会)。 说说hashMap是怎样实现的(这个之前看过,顺利回答上。还回答了多线程的问题出现的原因,面试官表示很惊讶的样子) 说说可重入锁 说说view绘制过程和事件分发机制,我大概回答了下。然后面试官又问:onTouch和onTouchEvent是什么区别?如果我重写了ontouch和onClick,它们的调用顺序是怎样的?什么时候会不调用onClick? handler的是怎样实现的? 由于项目里面用到了picasso,所以最后问了下picasso实现原理。 一面结束,最后面试官居然问我是不是第一次面试== 估计是帮紧张了。不过一面过程中面试官心情还不错,都是笑着问的。 当天晚上接到二面,面试官太累了,约我第二天面试。 ## 阿里二面 二面气氛一直不对,感觉面试官非常严肃,一来就感觉很有压力 ## 自我介绍 操作系统里面线程和进程的区别(挺基础的) ,接着麻烦就来了;我说完大致区别后,他就问,你说进程里面线程是共享内存的,那么一个进程最大能占多少内存?(懵逼,这是什么意思?考的分页知识?)。然后这里我想了一下,说应该和硬件有关,他继续问,有什么关系?(应该和地址总线有关,当时没想起,他叫我再想想,要是你设计的系统,应该和什么有关,还是没答上)。 你项目中图片是怎么处理的?回答:picasso,顺便说了下picasso原理。然后又问:那么picasso里面有多少个线程来加载图片?要是网络不同,线程数目分别是多少? 布局优化(这里开始说错了一点,然后面试官很生气的样子,自我感觉就要挂了) 项目中有哪些优化? 最后果然挂了(惨痛的经历,不过为后面打下了很好的基础,至少不 怎么紧张了) 然后后面就没有面试了 直到4月腾讯面试 # 2. 腾讯面经 腾讯是走的正常渠道,到成都现场面试 ## 一面 面试场地是在一个宾馆里面,一对一面试,face to face还是有点紧张的 ## 自我介绍 java多态你了解多少? 你说说重写和重载区别,然后拿了纸笔,手写一个能体现多态的例子 说说java在运行main函数之前做了哪些工作? 这个我居然从启动虚拟机→加载类→初始化类一直说到执行Main 你对大尾小尾了解多少? 我反问:您说的是大小端么? 他说对,然后我正准备给他解释的时候,他又拿了一张纸:用java写一个判断大小尾的程序 java静态方法能不能被重写? 答:不能。 问:为什么? 为什么java静态方法不能调用普通方法?普通方法能调用静态方法?(其实还是实例引用问题) java内存模型和GC机制 其实腾讯面试官感觉都很nice,他称呼我 都用您。感觉怪怪的,而且礼仪非常好。最后面完后,我问我面试得怎样?他说你了解的知识还是挺宽的,然后问了我一句要不要去做游戏?当然要啊! 然后就走了。然后就没有然后了,晚上查状态是不适合。 ## 霸面一面 腾讯面试后,感觉不怎么死心,又跑去长沙霸面了,我一去,HR说移动端基本已经招满了,你可以把简历放在这儿,要是有面试机会的话,我会通知你。然后我心情失落地回去了。 当我刚到住处,刚出电梯,HR就来电话了,叫我去面试 我那个开心啊,把平时20分钟的路程当成10分钟不对跑过去,直接一面。 一面面试官也很nice,还惊讶我从重庆来 Hashmap原理 hashcode和equals还有==的关系 用hashmap实现hashset。。我之前看过的,忘记了。然后按照我的想法回答了。(最后面试官告诉了我该怎样实现==) 内部类访问外部类的变量有什么问题? android里面onStop和onPause本质区别。什么时候可以存数据? 两个单链表寻找有没有交点,然后再寻找交点位置 android oom怎么解决 还问了一些项目的问题 其实中间还问了几个算法,忘记是什么了,后面想到了的话会加上的。 ## 二面 二面面试官感觉很牛的样子,一直技术轰炸 告诉我你所直到的所有关于java虚拟机的东西,我说了好久好久。还说了新生代大概什么时候会加入老年代 binder机制 handler原理, Message,loop,messageQueue关系,handler内存泄露问题。 TCP三次握手,用纸画出来 为什么TCP是可靠的,UDP早不可靠的?为什么UDP比TCP快? 面试官看到了我的项目,然后问了我一个用到的框架的原理,还问了我里面的很多细节,估计是以为我直接看的别人的博客了解到的这些知识,还好我是自己看了源码 算法:几百万个QQ号 ,找出前100个消费最高的QQ号。直接小顶堆什么的 android四大组件 ,这里扩展了很多,毕竟非常熟悉,还说了很多坑,很多实现原理(比如activity start原理) 还问了优缺点 (也有一些问题忘记了) 这次面试很久,忘记带水了,出来我直接喝完了一瓶怡宝 这交自我感觉答得不错,然后过了2个小时就收到HR面通知 ## HR面 自我介绍 项目里面怎么解决安全问题的?好可怕,会技术的HR 有没有女朋友? 家在哪里 有没有亲戚在腾讯? 我问了下要是过了的话大概会在哪里实习?HR说在深圳,还问我有什么问题么?我说没有,我爸妈也在那边,然后他在我简历上面记了一下。 为什么要学习android? HR面就10多分钟,很快,和我一起面试的还有几个学生,也都是10来分钟,然后HR叫我等结果 然后等啊等,等到现在还没有结果 # 3. 360面试 360全程视频面试加写代码什么的 ## 一面 写一个adapter,我后面忘记了getView的一个参数,一直在那里想,面试官问我是不是在编译器里面写,我说我在想怎么写。 hashmap原理 java可重入锁 排序算法和稳定性,快排什么时候情况最坏? 一个获全国奖的项目问了我20分钟,特别是service不被杀死的方法,我说了4种才放过我,还问了我具体实现,特别是在JNI里面实现的时候 项目中界面适配,自定义过view没有? NFC读卡,这个是我的项目,我说了具体实现,然后就放过我了 我项目中用了google map 和定位,他问怎么定位的?居然问了我具体API,我还说了里面的坑,国产手机阉割了一部分的问题 一面大概1个半小时,头昏脑涨,然后面试官并不放过我,叫我等等。他去叫二面面试官 ## 二面 http协议了解多少,说说里面的协议头部有哪些字段? https了解多少?为什么百度全部都用了https包括首页 散列表的基础知识,里面也问了hashmap(可见hashmap重要性) 项目问题,几个项目都问了,什么分工啊什么的 问了我很多项目中开发的问题,还好基本都答出来了,二面基础知识基本没多少,都是项目问题 二面接近一个半小时,还好在寝室面试,边面边喝水,二面脑袋都是糊的 二面完后,10分钟打电话通知一周内有HR面 ## HR面 HR面的时候,我正在火车上,HR说只有15分钟,我说当场面了,因为我那个时候正停在一个大站里面,要停半个小时 自我介绍,问了我所有项目的分工问题和设计等问题,好几个项目,这里就花了接近20分钟,然后火车开走了,然后大家都知道,悲催了,没信号 等到我有信号的时候,再给HR打电话约好第二天继续面。 第二天 继续项目分工 中兴实习情况?为什么最后没留下?(要读书啊) 开发的一些规范 投了XX公司和XX公司没有?为什么没投XX公司?哎,这里太年轻别坑了 怎么看待3Q大战(大姐,这个我怎么来说呢?) 问了我实习时间,希望实习的地点,希望做哪方面? 你觉得你一面和二面哪一面成绩更好?每一面大概多少分 优缺点 每个问题都问了很久,因为每个过后都接着往下问了的。整个HR面都1小时13分钟,累啊!!说好的15分钟呢 然后N天后,收到360拒信。 # **4. 今日头条** 今日头条也是我唯一过的公司,一面还好,二面全程技术轰炸,HR面聊得挺好,虽然有点短 今日头条我是内推的,N天后给我发邮件和电话约面试,本来是北京面试的,结果去不了,就电话面试了。 两次技术面试也是接近2个小时 。一面面试官面完后,叫我去吃饭,过会儿继续面,天真的我以为已经二面了,然而并不是,这个时候面试官还是建议去北京面试,过的机会大些,这个时候哪还有心情吃饭,一直等面试官的电话,结果继续面的时候直接写了一个代码就OK了,代码是在一个矩阵是查找有没有某个数,矩阵从左到右依次增大(忘记是增大还是减小了),从上到下也一样。由于电脑问题,我还是翻墙去写代码的,写代码的时候,由于网不稳定,还经常断 写完直接叫我等二面,过了会儿二面面试官马上来了电话 二面面试官感觉很随和,从Java的用法问到了虚拟机,问到了操作系统,最后深入问到了一个编译原理。还问了一些C语言的东西。 还问了排序。 然后可能是因为我所有东西是自学的,面试官在问之前都问了我了解不,不了解就重新换一个。还问了一些图论最短路径问题(还好面试前不久做了一个比赛,华为的未来寻路,就是最短路径问题),这个答得还行,说了一些经典算法,还有一些只能算法,还有一些改进等。 然后就是我项目中的问题,因为我用了rxjava,picasso,retrofit等开源项目,所以面试官问了我retrofit是如何处理注解的,我直接讲了源码,其过我博客里面也写过了这个,然后面试官可能发现我了解,就直接跳过了这个。然后问了我rxjava的东西,我结合博客看了部分源码,还问了rxjava里面用了大量的这些,是什么意思。 过后问我了解github上的一些开源项目不,我说了解一些,然后就是butterknife了,然后我回答错了,以为是注解+反射。后面挂电话后,找了时间分析了源码,还真不是反射==[butterknife分析在这里。](http://www.jianshu.com/p/0f3f4f7ca505) 今日头条感觉是我面试问题水平最高的,不再局限于基础知识,问了很多很深入的东西。感觉面试官都是根据我具体情况问的,随手丢出问题,直到我回答不上。总以为二面会挂。 结果在某天中午接到了HR的电话,由于那个时候有事,重新约了时间== 当时都有HR面恐惧症了,因为前面两次HR面后都没消息了== ## **HR面** 时间很短,几个问题而已,回答完就叫我等结果,说5月中旬会出结果。 ================================================ FILE: docs/android/Android-Interview/经验分享/Andorid-15k+的面试题.md ================================================ andorid开发也做了3年有余了,也面试很多加企业,借此机会分享一下,我们中遇到过的问题以及解决方案吧,希望能够对正在找工作的andoird程序员有一定的帮助。特别献上整理过的50道面试题目 ## 1.listView的优化方式 - 重用convertView - viewHolder - static class viewHolder - 在列表里面有图片的情况下,监听滑动不加载图片 - 多个不同布局,可以创建不同的viewHolder和convertView进行重用 ## 2.listView展示数据几种形式 - 从sqlite拉取数据源显示 - 从xml使用pull解析拉取数据源显示 - 从网络上拉取数据源显示 ## 3.ipc 进程间通信主要包括管道, 系统IPC(Inter-Process Communication,进程间通信)(包括消息队列,信号,共享存储), 套接字(SOCKET). 目的: - 数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间。 - 共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。 - 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。 - 资源共享:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。 - 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。 进程通过与内核及其它进程之间的互相通信来协调它们的行为。Linux支持多种进程间通信(IPC)机制,信号和管道是其中的两种。除此之外,Linux还支持System V 的IPC机制(用首次出现的Unix版本命名)。 ## 4.Parcel的机制 Android中的Parcel机制 实现了Bundle传递对象 使用Bundle传递对象,首先要将其序列化,但是,在Android中要使用这种传递对象的方式需要用到Android Parcel机制,即,Android实现的轻量级的高效的对象序列化和反序列化机制。 JAVA中的Serialize机制,译成串行化、序列化……,其作用是能将数据对象存入字节流当中,在需要时重新生成对象。主要应用是利用外部存储设备保存对象状态,以及通过网络传输对象等。 Android中的新的序列化机制 在Android系统中,定位为针对内存受限的设备,因此对性能要求更高,另外系统中采用了新的IPC(进程间通信)机制,必然要求使用性能更出色的对象传输方式。在这样的环境下,Parcel被设计出来,其定位就是轻量级的高效的对象序列化和反序列化机制。 Android中序列化有以下几个特征: 1. 整个读写全是在内存中进行,所以效率比JAVA序列化中使用外部存储器会高很多; 2. 读写时是4字节对齐的 3. 如果预分配的空间不够时,会一次多分配50%; 4. 对于普通数据,使用的是mData内存地址,对于IBinder类型的数据以及FileDescriptor使用的是mObjects内存地址。后者是通过flatten_binder()和unflatten_binder()实现的,目的是反序列化时读出的对象就是原对象而不用重新new一个新对象。 代码: activity代码: ```java Intent mIntent =newIntent(this,ParcelableDemo.class); Bundle mBundle =newBundle(); mBundle.putParcelable(PAR_KEY, mPolice); mIntent.putExtras(mBundle); ``` 实体类: ```java public class Police implements Parcelable { private String name; private int workTime; public String getName() { returnname; } public void setName(String name) { this.name = name; } public int getWorkTime() { returnworkTime; } public void setWorkTime(int workTime) { this.workTime = workTime; } public static final Parcelable.Creator CREATOR =newCreator() { @Override public Police createFromParcel(Parcel source) { Police police =newPolice(); police.name = source.readString(); police.workTime = source.readInt(); returnpolice; } @Override public Police[] newArray(int size) { returnnewPolice[size]; } }; @Override public int describeContents() { return0; } @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeString(name); parcel.writeInt(workTime); } } ``` ## 5.JNI调用 (1) Eclipse中新建android工程 工程名 JNItest Package名com.ura.test Activity名 JNItest 应用程序名 JNItest (2) 编辑main.xml ```xml ``` (3)编辑java文件 ```java package com.ura.test; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; public class JNITest extends Activity { /** Called when the activity is first created. */ static { System.loadLibrary("JNITest"); } public native String GetTest(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); String str =GetTest(); TextView JNITest = (TextView)findViewById(R.id.JNITest); JNITest.setText(str); } } ``` (4)生成head文件 编译上面工程声称class文件,然后用javah工具生成c/c++ 头文件 javah -classpath bin -d jni com.ura.test.JNItest 生成的头文件如下 ```c /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class com_ura_test_JNITest */ #ifndef _Included_com_ura_test_JNITest #define _Included_com_ura_test_JNITest #ifdef __cplusplus extern "C" { #endif /* * Class: com_ura_test_JNITest * Method: GetTest * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_ura_test_JNITest_GetTest (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif ``` (5)编写c/c++文件如下 ```c include "com_ura_test_JNITest.h" #define LOG_TAG "JNITest" #undef LOG #include JNIEXPORT jstring JNICALL Java_com_ura_test_JNITest_GetTest (JNIEnv * env, jobject obj) { return (*env)->NewStringUTF(env, (char *)"JNITest Native String"); LOGD("Hello LIB!\n"); } ``` (6)编写android.mk文件 ```c LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ com_ura_test_JNITest.c LOCAL_C_INCLUDES := \ $(JNI_H_INCLUDE) LOCAL_SHARED_LIBRARIES := libutils LOCAL_PRELINK_MODULE := false LOCAL_MODULE := libJNITest include $(BUILD_SHARED_LIBRARY) ``` (7)编译生成动态库 新建文件夹 ~/mydroid/external/libJNITest 把上面编写好的头文件,c/c++源文件,make文件拷贝进上面目录中 * 需要注意的是把PRELINK_MOUDULE设置成false 否则需要重新做成img文件再烧入。 在 ubuntu中执行 ``` cd cd mydroid/build/ envsetup.sh cd ~/mydroid cd external/libJNITest/ mm ``` 编译成功的后会在下面目录中生成libJNITest.so文件 ~mydroid/out/target/product/generic/system/lib/ (8)在模拟器中执行程序 首先要把动态库拷进/system/lib中。 启动模拟器 ``` adb shell adb remount adb push libJNITest.so /system/lib ``` 确认拷贝成功 ``` cd /system/lib ls ``` 然后不要关闭模拟器(关掉再开动态库就没了,因为模拟器rom是只读) 执行java程序JNITest 会看到屏幕上打印出 ``` JNITest Native String 6.细谈四大组件 ``` ## activity ### 1.什么是activity? 四大组件之一,一般的,一个用户交互界面对应一个activity setContentView() ,// 要显示的布局 button.setOnclickLinstener{ } , activity 是Context的子类,同时实现了window.callback和keyevent.callback, 可以处理与窗体用户交互的事件. 里面不能进行耗时操作 我开发常用的的有ListActivity , PreferenceActivity ,TabAcitivty等… 如果界面有共同的特点或者功能的时候,还会自己定义一个BaseActivity. ### 2.activity生命周期? Activity生命周期 1 完整生命周期 onCreate() --> onStart() --> onResume() 可以在手机上看见activity ---> onPause() --> onStop() 看不见了 ---> onDestory() 销毁了 2 前台生命周期 onstart() ---> onStop()之间进行切换 onCreate() --> onStart() --> onResume() 现在有一个activity完全覆盖 onPause() ----> onStop() 如果上面的activity关闭 onRestart() ---> onStart() --> onResume() 3 可视生命周期 onResume() ---> onPause()之间进行切换 onCreate() --> onStart() --> onResume() 现在有一个activity没有完全覆盖 onPause() 如果上面的activity关闭 onResume() ### 3.横竖屏切换时候activity的生命周期? 这个生命周期跟清单文件里的配置有关系 1、不设置Activity的android:configChanges时,切屏会重新调用各个生命周期 默认首先销毁当前activity,然后重新加载 2、设置Activity的android:configChanges="orientation|keyboardHidden"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法 游戏开发中, 屏幕的朝向都是写死的. ### 4.如何将一个activity设置成窗口的样式? 可以自定义一个activity的样式,详细见手机卫士的程序详细信息 android:theme="@style/FloatActivity" E:\day9\mobilesafe\res\values\style ### 5.activity启动模式? Activity启动模式任务堆栈 Activity中的任务是与用户交互的一组Activity的集合,Activity会被按打开顺序安排在一个堆栈里。 任务栈:并不是Activity是Activity的引用(内存地址) standard 标准模式 每次激活Activity时都会创建Activity,并放入任务栈中 默认模式 singleTop 独享堆栈顶端 如果在任务的栈顶正好存在该Activity的实例,就重用该实例,否者就会创建新的实例并放入栈顶(即使栈中已经存在该Activity实例,只要不在栈顶,都会创建实例) 浏览器的书签 singleTask 独享任务堆栈 如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的onNewIntent())。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移除栈。如果栈中不存在该实例,将会创建新的实例放入栈中 浏览器的主页 singleInstance单例 在一个新栈中创建该Activity实例,并让多个应用共享该栈中的该Activity实例。一旦该模式的Activity的实例存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例,其效果相当于多个应用程序共享一个应用,不管谁激活该Activity都会进入同一个应用中 通话界面 Singletop:如果重复使用上一次的Activity,就重用。 singleTask:如果使用已经实例化Activity,就重用,并且删除重用Activity前面的Activity,重用的Activity置顶任务栈。 singleInstance:在一个新栈中创建该Activity实例,并让多个应用共享该栈中的该Activity实例。(调用Activity和重用Activity不在一个栈中) singleTop 、singleTask 、singleInstance 优化性能、重用Activity。 6.后台activity被系统回收了怎么办?如果后台activity由于某种原因被系统回收了,如何保存之前状态? 除了在栈顶的activity,其他的activity都有可能在内存不足的时候被系统回收,一个activity越处于栈底,被回收的可能性越大. ```java protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putLong("id", 1234567890); } public void onCreate(Bundle savedInstanceState) { //判断savedInstanceState是不是空. //如果不为空就取出来 super.onCreate(savedInstanceState); } ``` ### 7.如何退出activity,如何安全退出已调用多个activity的application? 退出activity 直接调用 finish () 方法 . //用户点击back键 就是退出一个activity 退出activity 会执行 onDestroy()方法 . 1、抛异常强制退出: 该方法通过抛异常,使程序Force Close。 验证可以,但是,需要解决的问题是,如何使程序结束掉,而不弹出Force Close的窗口。 //安全结束进程 android.os.Process.killProcess(android.os.Process.myPid()); 2、记录打开的Activity: 每打开一个Activity,就记录下来。在需要退出时,关闭每一个Activity即可。 List lists ; 在application 全集的环境里面 lists = new ArrayList(); lists.add(activity); for(Activity activity: lists) { activity.finish(); } 3、发送特定广播: 在需要结束应用时,发送一个特定的广播,每个Activity收到广播后,关闭即可。 //给某个activity 注册接受接受广播的意图 registerReceiver(receiver, filter) //如果过接受到的是 关闭activity的广播 就调用finish()方法 把当前的activity finish()掉 4、递归退出 在打开新的Activity时使用startActivityForResult,然后自己加标志,在onActivityResult中处理,递归关闭。 上面是网上的一些做法. 其实 可以通过 intent的flag 来实现.. intent.setFlag(FLAG_ACTIVITY_CLEAR_TOP)激活一个新的activity,然后在新的activity的oncreate方法里面 finish掉. 8.两activity之间怎么传递数据? 基本数据类型可以通过. Intent 传递数据 在A activity中 ```java Intent intent = new Intent(); intent.putExtra(name, value) Bundle bundle = new Bundle(); bundle.putBoolean(key,value); intent.putExtras(bundle); extras.putDouble(key, value) // 通过intent putExtra 方法 基本数据类型 都传递 Intent i = getIntent(); i.getExtras(); intent.getStringExtra("key","value"); intent.getBooleanExtra("key","value") Bundle bundle = new Bundle(); bumdle.putShort(key, value); intent.putExtras(bumdle); intent.putExtras(bundle) ``` -------------- Application 全局里面存放 对象 ,自己去实现自己的application的这个类, 基础系统的application , 每个activity都可以取到 ----------------- 让对象实现 implements Serializable 接口把对象存放到文件上. 让类实现Serializable 接口,然后可以通过ObjectOutputStream //对象输出流 ```java File file = new File("c:\1.obj"); FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos); Student stu = new Student(); oos.writeObject(stu); //从文件中把对象读出来 ObjectInputStream ois = new ObjectInputStream(arg0); Student stu1 = (Student) ois.readObject(); ``` 文件/网络 intent.setData(Uri) Uri.fromFile(); //大图片的传递 ### 9.讲一讲对activity的理解? 把上面的几点用自己的心得写出来 ## service ### 1.什么是Service以及描述下它的生命周期。Service有哪些启动方法,有什么区别,怎样停用Service? 在Service的生命周期中,被回调的方法比Activity少一些,只有onCreate, onStart, onDestroy, onBind和onUnbind。 通常有两种方式启动一个Service,他们对Service生命周期的影响是不一样的。 1 通过startService Service会经历 onCreate 到onStart,然后处于运行状态,stopService的时候调用onDestroy方法。 如果是调用者自己直接退出而没有调用stopService的话,Service会一直在后台运行。 2 通过bindService Service会运行onCreate,然后是调用onBind, 这个时候调用者和Service绑定在一起。调用者退出了,Srevice就会调用onUnbind->onDestroyed方法。 所谓绑定在一起就共存亡了。调用者也可以通过调用unbindService方法来停止服务,这时候Srevice就会调用onUnbind->onDestroyed方法。 需要注意的是如果这几个方法交织在一起的话,会出现什么情况呢? 一个原则是Service的onCreate的方法只会被调用一次,就是你无论多少次的startService又bindService,Service只被创建一次。 如果先是bind了,那么start的时候就直接运行Service的onStart方法, 如果先是start,那么bind的时候就直接运行onBind方法。 如果service运行期间调用了bindService,这时候再调用stopService的话,service是不会调用onDestroy方法的,service就stop不掉了,只能调用UnbindService, service就会被销毁 如果一个service通过startService 被start之后,多次调用startService 的话,service会多次调用onStart方法。多次调用stopService的话,service只会调用一次onDestroyed方法。 如果一个service通过bindService被start之后,多次调用bindService的话,service只会调用一次onBind方法。 多次调用unbindService的话会抛出异常。 2.service是否在main thread中执行, service里面是否能执行耗时的操作? 默认情况,如果没有显示的指定service所运行的进程, Service和activity是运行在当前app所在进程的main thread(UI主线程)里面 service里面不能执行耗时的操作(网络请求,拷贝数据库,大文件 ) 在子线程中执行 new Thread(){}.start(); 特殊情况 ,可以在清单文件配置 service 执行所在的进程 ,让service在另外的进程中执行 3.怎么让在启动一个Activity是就启动一个service? 在activity的onCreate()方法里面 startService(); 4.Activity怎么和service绑定,怎么在activity中启动自己对应的service? startService() 一旦被创建 调用着无关 没法使用service里面的方法 bindService () 把service 与调用者绑定 ,如果调用者被销毁, service会销毁 bindService() 我们可以使用service 里面的方法 bindService(). 让activity能够访问到 service里面的方法 构建一个intent对象, Intent service = new Intent(this,MyService.class); 通过bindService的方法去启动一个服务, bindService(intent, new MyConn(), BIND_AUTO_CREATE); ServiceConnection 对象(重写onServiceConnected和OnServiceDisconnected方法) 和BIND_AUTO_CREATE. private class myconn implements ServiceConnection { public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub //可以通过IBinder的对象 去使用service里面的方法 } public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub } } 5.不用service,B页面为音乐播放,从A跳转到B,再返回,如何使音乐继续播放? 这个问题问的很山寨.默认不做任何处理,B里面的音乐都能播放. 遇到问题, 可以随机应变,灵活发挥,多考虑些细节,比如说这个题就可以这样说,说说你对startActivityForResult的理解() B的结束的时候 setResult() A会调用到onActivityResult() 就会获取到resultCode A开启B的时候,用startActivityForResult()方法, B返回的时候把播放的状态信息返回给A ,A继续播放音乐. seekTo(resultCode) 6.什么是IntentService?有何优点? 普通的service ,默认运行在ui main 主线程 Sdk给我们提供的方便的,带有异步处理的service类, 可以在OnHandleIntent() 处理耗时的操作 7.什么时候使用Service? 后台操作,耗时操作的时候 拥有service的进程具有较高的优先级 官方文档告诉我们,Android系统会尽量保持拥有service的进程运行,只要在该service已经被启动(start)或者客户端连接(bindService)到它。当内存不足时,需要保持,拥有service的进程具有较高的优先级。 1. 如果service正在调用onCreate, onStartCommand或者onDestory方法,那么用于当前service的进程相当于前台进程以避免被killed。 2. 如果当前service已经被启动(start),拥有它的进程则比那些用户可见的进程优先级低一些,但是比那些不可见的进程更重要,这就意味着service一般不会被killed. 3. 如果客户端已经连接到service (bindService),那么拥有Service的进程则拥有最高的优先级,可以认为service是可见的。 4. 如果service可以使用startForeg round(int, Notification)方法来将service设置为前台状态,那么系统就认为是对用户可见的,并不会在内存不足时killed。 如果有其他的应用组件作为Service,Activity等运行在相同的进程中,那么将会增加该进程的重要性。 1.Service的特点可以让他在后台一直运行,可以在service里面创建线程去完成耗时的操作. new Thread(){ TimerTask // 循环的执行一个定时的任务 }.start(); 2.Broadcast receiver捕获到一个事件之后,可以起一个service来完成一个耗时的操作. ANR new Service() 3.远程的service如果被启动起来,可以被多次bind, 但不会重新create. 索爱手机X10i的人脸识别的service可以被图库使用,可以被摄像机,照相机等程序使用. 画廊 摄像机 照相机 bindService() Ibinder的对象, 访问service 8.如何在让Service杀不死? Android开发的过程中,每次调用startService(Intent)的时候,都会调用该Service对象的onStartCommand(Intent,int,int)方法,然后在onStartCommand方法中做一些处理。 从Android官方文档中,我们知道onStartCommand有4种int返回值,首先简单地讲讲int返回值的作用。 一、onStartCommand有4种返回值: START_STICKY:如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。 START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务。 START_REDELIVER_INTENT:重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。 START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。 二、创建不被杀死的service 1.在service中重写下面的方法,这个方法有三个返回值, START_STICKY(或START_STICKY_COMPATIBILITY)是service被kill掉后自动重写创建 @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY_COMPATIBILITY; //return super.onStartCommand(intent, flags, startId); } 或 @Override public int onStartCommand(Intent intent, int flags, int startId) { flags = START_STICKY; return super.onStartCommand(intent, flags, startId); // return START_REDELIVER_INTENT; } @Override public void onStart(Intent intent, int startId) { // 再次动态注册广播 IntentFilter localIntentFilter = new IntentFilter("android.intent.action.USER_PRESENT"); localIntentFilter.setPriority(Integer.MAX_VALUE);// 整形最大值 myReceiver searchReceiver = new myReceiver(); registerReceiver(searchReceiver, localIntentFilter); super.onStart(intent, startId); } 2.在Service的onDestroy()中重启Service. public void onDestroy() { Intent localIntent = new Intent(); localIntent.setClass(this, MyService.class); // 销毁时重新启动Service this.startService(localIntent); } 3.创建一个广播 public class myReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { context.startService(new Intent(context, Google.class)); } } 4.AndroidManifest.xml中注册广播myReceiver及MyService服务 注:解锁,启动,切换场景激活广播需加权限,如启动完成,及手机机状态等。 亲测ZTE U795手机Android 4.0.4版本adb push到system\app下android:persistent="true" 变成核心程序,在360杀掉进程的时候,myReceiver照样有效,保证service重生。呃 KILL问题: 1. settings 中stop service onDestroy方法中,调用startService进行Service的重启。 2.settings中force stop 应用 捕捉系统进行广播(action为android.intent.action.PACKAGE_RESTARTED) 3. 借助第三方应用kill掉running task 提升service的优先级,程序签名,或adb push到system\app下等 相较于/data/app下的应用,放在/system/app下的应用享受更多的特权,比如若在其Manifest.xml文件中设置persistent属性为true,则可使其免受out-of-memory killer的影响。如应用程序'Phone'的AndroidManifest.xml文件: ... 设置后app提升为系统核心级别 ## Broadcast Receiver ### 1.什么是Broadcast Receiver 下面是Android Doc中关于BroadcastReceiver的概述:①广播接收器是一个专注于接收广播通知信息,并做出对应处理的组件。很多广播是源自于系统代码的──比如,通知时区改变、电池电量低、拍摄了一张照片或者用户改变了语言选项。应用程序也可以进行广播──比如说,通知其它应用程序一些数据下载完成并处于可用状态。 ②应用程序可以拥有任意数量的广播接收器以对所有它感兴趣的通知信息予以响应。所有的接收器均继承自BroadcastReceiver基类。 ③广播接收器没有用户界面。然而,它们可以启动一个activity来响应它们收到的信息,或者用NotificationManager来通知用户。通知可以用很多种方式来吸引用户的注意力──闪动背灯、震动、播放声音等等。一般来说是在状态栏上放一个持久的图标,用户可以打开它并获取消息。 有很多广播接收者 ,系统已经实现了. 广播分两种 有序广播 无序广播 指定接收者的有序广播 . sendOrderedBroadcast(intent,receiverPermission,resultReceiver,scheduler,initialCode,initialData,initialExtras) 接受者一定会获取到 广播的事件 sendStickyBroadcast(intent) //阴魂不散 广播接受者在onReceive 方法获取到广播的事件 Wifi设置 等待wifi状态更新完毕 是不可以被拦截掉的 -1000 - 1000 abortBroadcast(); 代码配置优先级比xml配置优先级的级别高,因为代码运行在内存中,而清单在系统中 手机卫士中自定义一个broadcast receiver sms_received 来获取短信到来的广播, 根据黑名单来判断是否拦截该短信. 画画板生成图片后,发送一个sd挂载的通知,通知系统的gallery去获取到新的图片. Intent intent = newIntent(Intent.ACTION_MEDIA_MOUNTED,Uri.parse("file://"+Environment.getExternalStorageDirectory())); sendBroadcast(intent); 2.什么时候使用Broadcast Receiver 用于接收系统的广播通知, 系统会有很多sd卡挂载,手机重启,广播通知,低电量,来电,来短信等…. 3.如何使用Broadcast Receiver 设置广播接收者的优先级,设置广播接受者的action名字 等… 详细见工程代码. ```xml ``` 可以通过代码 registerReceiver(receiver,filter) ## ContentProvider ### 1.什么是ContentProvider ContentProvider内容提供者 ContentProvider 进程间通讯,进程间数据的访问/对外共享数据用 优点:提供了统一的访问方式 原理分析图 ### 2.什么时候使用ContentProvider 需要访问别人的数据的时候 ### 3.如何使用ContentProvider 1.先是提供的数据类型等数据的类。package org.juetion.cp; ```java import android.net.Uri; import android.provider.BaseColumns; /** * 提供的数据类型等数据。 * Created by juetionke on 13-12-21. */ public class MyProviderMetaData { public static final String AUTHORIY = "org.juetion.cp.MyContentProvider"; /** * 数据库名称 */ public static final String DATABASE_NAME = "MyProvider.db"; /** * 数据库版本 */ public static final int DATABASE_VERSION = 1; /** * 表名 */ public static final String USERS_TABLE_NAME = "users"; /** * 继承了BaseColumns,所以已经有了_ID */ public static final class UserTableMetaData implements BaseColumns { /** * 表名 */ public static final String TABLE_NAME = "users"; /** * 访问该ContentProvider的URI */ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORIY + "/users"); /** * 该ContentProvider所返回的数据类型定义 */ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/org.juetion.user"; public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/org.juetion.user"; /** * 列名 */ public static final String USER_NAME = "name"; public static final String USER_AGE = "age"; /** * 默认的排序方法 */ public static final String DEFAULT_SORT_ORDER = "_id desc"; } } ``` 2,继承ContentProvider的类: ```java package org.juetion.cp; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.text.TextUtils; import android.util.Log; import org.juetion.sqlite3.DatabaseHelper; import java.util.HashMap; /** * Created by juetionke on 13-12-21. */ public class MyContentProvider extends ContentProvider { /** * 定义规则 */ public static final UriMatcher uriMatcher; public static final int USERS_COLLECTION = 1;//用于标记 public static final int USERS_SINGLE = 2;//用于标记 private DatabaseHelper databaseHelper;//这里的数据共享是共享Sqlite里的数据,当然,可以试用其他,如文本数据共享。 static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);//试用一个没有规则的Uri。然后下面自己匹配。 uriMatcher.addURI(MyProviderMetaData.AUTHORIY,"/users",USERS_COLLECTION);//自己定义的规则,有点像路由器,是uri匹配的方案。 uriMatcher.addURI(MyProviderMetaData.AUTHORIY,"/users/#",USERS_SINGLE);//同上。 } /** * 为列定义别名 */ public static HashMap usersMap; static { usersMap = new HashMap(); usersMap.put(MyProviderMetaData.UserTableMetaData._ID, MyProviderMetaData.UserTableMetaData._ID); usersMap.put(MyProviderMetaData.UserTableMetaData.USER_NAME, MyProviderMetaData.UserTableMetaData.USER_NAME); usersMap.put(MyProviderMetaData.UserTableMetaData.USER_AGE, MyProviderMetaData.UserTableMetaData.USER_AGE); } @Override public boolean onCreate() { Log.i("juetion","onCreate"); databaseHelper = new DatabaseHelper(getContext(), MyProviderMetaData.DATABASE_NAME);//这里的实现,常见前篇关于Sqlite的文章。 return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Log.i("juetion","query"); SQLiteQueryBuilder sqLiteQueryBuilder = new SQLiteQueryBuilder();//写入查询条件,有点像Hibernate。 switch (uriMatcher.match(uri)) {//判断查询的是单个数据还是多个数据。 case USERS_SINGLE: sqLiteQueryBuilder.setTables(MyProviderMetaData.UserTableMetaData.TABLE_NAME);//需要查询的表 sqLiteQueryBuilder.setProjectionMap(usersMap);//列的别名定义 sqLiteQueryBuilder.appendWhere(MyProviderMetaData.UserTableMetaData._ID + "=" + uri.getPathSegments().get(1)); //查询条件,uri.getPathSegments().get(1),getPathSegments是将内容根据/划分成list。 break; case USERS_COLLECTION: sqLiteQueryBuilder.setTables(MyProviderMetaData.UserTableMetaData.TABLE_NAME); sqLiteQueryBuilder.setProjectionMap(usersMap); break; } String orderBy;//判断sortOrder是否为空,加入默认。 if (TextUtils.isEmpty(sortOrder)) { orderBy = MyProviderMetaData.UserTableMetaData.DEFAULT_SORT_ORDER; } else { orderBy = sortOrder; } SQLiteDatabase sqLiteDatabase = databaseHelper.getWritableDatabase(); Cursor cursor = sqLiteQueryBuilder.query(sqLiteDatabase, projection, selection, selectionArgs, null, null, sortOrder);//可以使用下面的方法,不过此时sqLiteDatabase将会没有用。 //Cursor cursor = sqLiteDatabase.query(MyProviderMetaData.UserTableMetaData.TABLE_NAME, projection, selection, selectionArgs, null, null, orderBy); cursor.setNotificationUri(getContext().getContentResolver(),uri); return cursor; } /** * 根据传入的URI,返回URI说表示的数据类型 * @param uri * @return */ @Override public String getType(Uri uri) { Log.i("juetion","getType"); switch (uriMatcher.match(uri)) {//匹配uri的规则 case USERS_COLLECTION: return MyProviderMetaData.UserTableMetaData.CONTENT_TYPE; case USERS_SINGLE: return MyProviderMetaData.UserTableMetaData.CONTENT_TYPE_ITEM; default: throw new IllegalArgumentException("Unknown URI" + uri); } } @Override public Uri insert(Uri uri, ContentValues values) { Log.i("juetion","insert"); SQLiteDatabase sqLiteDatabase = databaseHelper.getWritableDatabase(); long rowId = sqLiteDatabase.insert(MyProviderMetaData.UserTableMetaData.TABLE_NAME, null, values); if (rowId > 0) { Uri insertUserUri = ContentUris.withAppendedId(MyProviderMetaData.UserTableMetaData.CONTENT_URI, rowId);//简单来说就是字符串拼凑一下。只不过是uri专用的。 //通知监听器 getContext().getContentResolver().notifyChange(insertUserUri,null); return insertUserUri; }else throw new IllegalArgumentException("Failed to insert row into" + uri); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { Log.i("juetion","delete"); return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { Log.i("juetion","update"); return 0; } } ``` 还有重要的一点,再第二个APP的AndroidManifest.xml里面需要添加 ```xml ``` 以下代码是在第一个APP里面的。 关于使用。只需要将uri的string提供给第一个APP。 例如在第一个APP的Activity调用数据插入: ```java ContentValues contentValues = new ContentValues(); contentValues.put("name","zhangsan"); contentValues.put("age",19); Uri uri = getContentResolver().insert(Uri.parse("content://org.juetion.cp.MyContentProvider/users"),contentValues); Log.i("juetion", "insert uri-->" + uri.toString()); ``` 例如在第一个APP的Activity调用数据的查询: ```java Cursor cursor = getContentResolver().query(Uri.parse("content://org.juetion.cp.MyContentProvider/users"), new String[]{"name", "age"}, null, null, null); while (cursor.moveToNext()) { Log.i("juetion", cursor.getString(cursor.getColumnIndex("name"))); } ``` ### 4.请介绍下ContentProvider是如何实现数据共享的。 ContentProvider 可以屏蔽数据操作的细节 文件 xml MyContentProvider 可以在不同应用程序之间共享数据 sharedpreference db 把自己的数据通过uri的形式共享出去 android 系统下 不同程序 数据默认是不能共享访问 需要去实现一个类去继承ContentProvider ```java public class PersonContentProvider extends ContentProvider{ public boolean onCreate(){ //.. } query(Uri, String[], String, String[], String) insert(Uri, ContentValues) update(Uri, ContentValues, String, String[]) delete(Uri, String, String[]) ``` 联系人的信息 sms的内容content://sms/ } 安全共同点 android安全学习 签名作用 1.sharedUserId 一样并且签名一次 可以实现数据共享 2.升级应用 权限:细粒度的特权管理 权限与操作关联 应用需要显式申请权限 用户对权限可知(不可控) 对特权权限单独控制 四大组件 exported = true 等于public exported = false 等于private 默认组件private 如果该组件设置了intent-filter默认是public 如果同时希望是private,就需要主动设置expoted=false Securing Activities 可知指定权限才能启动activity service同上 BoradcastReceiver可以设置接发的权限 ContentPrivider 可设置读写Permission ## 7.多线程管理 本篇随笔将讲解一下Android的多线程的知识,以及如何通过AsyncTask机制来实现线程之间的通信。 一、Android当中的多线程 在Android当中,当一个应用程序的组件启动的时候,并且没有其他的应用程序组件在运行时,Android系统就会为该应用程序组件开辟一个新的线程来执行。默认的情况下,在一个相同Android应用程序当中,其里面的组件都是运行在同一个线程里面的,这个线程我们称之为Main线程。当我们通过某个组件来启动另一个组件的时候,这个时候默认都是在同一个线程当中完成的。当然,我们可以自己来管理我们的Android应用的线程,我们可以根据我们自己的需要来给应用程序创建额外的线程。 二、Main Thread 和 Worker Thread 在Android当中,通常将线程分为两种,一种叫做Main Thread,除了Main Thread之外的线程都可称为Worker Thread。 当一个应用程序运行的时候,Android操作系统就会给该应用程序启动一个线程,这个线程就是我们的Main Thread,这个线程非常的重要,它主要用来加载我们的UI界面,完成系统和我们用户之间的交互,并将交互后的结果又展示给我们用户,所以Main Thread又被称为UI Thread。 Android系统默认不会给我们的应用程序组件创建一个额外的线程,所有的这些组件默认都是在同一个线程中运行。然而,某些时候当我们的应用程序需要完成一个耗时的操作的时候,例如访问网络或者是对数据库进行查询时,此时我们的UI Thread就会被阻塞。例如,当我们点击一个Button,然后希望其从网络中获取一些数据,如果此操作在UI Thread当中完成的话,当我们点击Button的时候,UI线程就会处于阻塞的状态,此时,我们的系统不会调度任何其它的事件,更糟糕的是,当我们的整个现场如果阻塞时间超过5秒钟(官方是这样说的),这个时候就会出现 ANR (Application Not Responding)的现象,此时,应用程序会弹出一个框,让用户选择是否退出该程序。对于Android开发来说,出现ANR的现象是绝对不能被允许的。 另外,由于我们的Android UI控件是线程不安全的,所以我们不能在UI Thread之外的线程当中对我们的UI控件进行操作。因此在Android的多线程编程当中,我们有两条非常重要的原则必须要遵守: 绝对不能在UI Thread当中进行耗时的操作,不能阻塞我们的UI Thread 不能在UI Thread之外的线程当中操纵我们的UI元素 三、如何处理UI Thread 和 Worker Thread之间的通信 既然在Android当中有两条重要的原则要遵守,那么我们可能就有疑问了?我们既不能在主线程当中处理耗时的操作,又不能在工作线程中来访问我们的UI控件,那么我们比如从网络中要下载一张图片,又怎么能将其更新到UI控件上呢?这就关系到了我们的主线程和工作线程之间的通信问题了。在Android当中,提供了两种方式来解决线程直接的通信问题,一种是通过Handler的机制(这种方式在后面的随笔中将详细介绍),还有一种就是今天要详细讲解的 AsyncTask 机制。 四、AsyncTask AsyncTask:异步任务,从字面上来说,就是在我们的UI主线程运行的时候,异步的完成一些操作。AsyncTask允许我们的执行一个异步的任务在后台。我们可以将耗时的操作放在异步任务当中来执行,并随时将任务执行的结果返回给我们的UI线程来更新我们的UI控件。通过AsyncTask我们可以轻松的解决多线程之间的通信问题。 怎么来理解AsyncTask呢?通俗一点来说,AsyncTask就相当于Android给我们提供了一个多线程编程的一个框架,其介于Thread和Handler之间,我们如果要定义一个AsyncTask,就需要定义一个类来继承AsyncTask这个抽象类,并实现其唯一的一个 doInBackgroud 抽象方法。要掌握AsyncTask,我们就必须要一个概念,总结起来就是: 3个泛型,4个步骤。 3个泛型指的是什么呢?我们来看看AsyncTask这个抽象类的定义,当我们定义一个类来继承AsyncTask这个类的时候,我们需要为其指定3个泛型参数: AsyncTask  Params: 这个泛型指定的是我们传递给异步任务执行时的参数的类型 Progress: 这个泛型指定的是我们的异步任务在执行的时候将执行的进度返回给UI线程的参数的类型 Result: 这个泛型指定的异步任务执行完后返回给UI线程的结果的类型 我们在定义一个类继承AsyncTask类的时候,必须要指定好这三个泛型的类型,如果都不指定的话,则都将其写成Void,例如: AsyncTask 4个步骤:当我们执行一个异步任务的时候,其需要按照下面的4个步骤分别执行 onPreExecute(): 这个方法是在执行异步任务之前的时候执行,并且是在UI Thread当中执行的,通常我们在这个方法里做一些UI控件的初始化的操作,例如弹出要给ProgressDialog doInBackground(Params... params): 在onPreExecute()方法执行完之后,会马上执行这个方法,这个方法就是来处理异步任务的方法,Android操作系统会在后台的线程池当中开启一个worker thread来执行我们的这个方法,所以这个方法是在worker thread当中执行的,这个方法执行完之后就可以将我们的执行结果发送给我们的最后一个 onPostExecute 方法,在这个方法里,我们可以从网络当中获取数据等一些耗时的操作 onProgressUpdate(Progess... values): 这个方法也是在UI Thread当中执行的,我们在异步任务执行的时候,有时候需要将执行的进度返回给我们的UI界面,例如下载一张网络图片,我们需要时刻显示其下载的进度,就可以使用这个方法来更新我们的进度。这个方法在调用之前,我们需要在 doInBackground 方法中调用一个 publishProgress(Progress) 的方法来将我们的进度时时刻刻传递给 onProgressUpdate 方法来更新 onPostExecute(Result... result): 当我们的异步任务执行完之后,就会将结果返回给这个方法,这个方法也是在UI Thread当中调用的,我们可以将返回的结果显示在UI控件上 为什么我们的AsyncTask抽象类只有一个 doInBackground 的抽象方法呢??原因是,我们如果要做一个异步任务,我们必须要为其开辟一个新的Thread,让其完成一些操作,而在完成这个异步任务时,我可能并不需要弹出要给ProgressDialog,我并不需要随时更新我的ProgressDialog的进度条,我也并不需要将结果更新给我们的UI界面,所以除了 doInBackground 方法之外的三个方法,都不是必须有的,因此我们必须要实现的方法是 doInBackground 方法。 五、通过AsyncTask来从网络上下载一张图片 下面我们就通过两个代码示例,来看看如何通过AsyncTask来从网络上下载一张图片,并更新到我们的ImageView控件上。 ①下载图片时,弹出一个ProgressDialog,但是不显示实时进度 我们来看看布局文件: ```xml