# Android之进阶知识
# 存储
# 0. Android中数据存储的方式有哪些
- File
- SharedPreferences
- SQlite
- 网络
- ContentProvider
# 1. SharedPreference是进程同步的嘛,有没有什么方法进程同步
- 默认不是
- 可以设置模式MODE_MULTI_PROCESS做到进程同步,但因为该模式有很多坑,已经被Google弃用
- 官方建议使用ContentProvider
# 2. SharedPreferences commit和apply的区别
- commit是同步的提交,这种方式很常用,在比较早的SDK版本中就有了。这种提交方式会阻塞调用它的线程,并且这个方法会返回boolean值告知保存是否成功(如果不成功,可以做一些补救措施)。
- apply是异步的提交方式,目前Android Studio也会提示大家使用这种方式
# 3. 文件存储路径与权限 权限
文件存储分为内部存储和外部存储
内部存储
- Environment.getDataDirectory() = /data //这个方法是获取内部存储的根路径
- getFilesDir().getAbsolutePath() = /data/user/0/packname/files //这个方法是获取某个应用在内部存储中的files路径
- getCacheDir().getAbsolutePath() = /data/user/0/packname/cache //这个方法是获取某个应用在内部存储中的cache路径
- getDir(“myFile”, MODE_PRIVATE).getAbsolutePath() = /data/user/0/packname/app_myFile
外部存储
- Environment.getExternalStorageDirectory().getAbsolutePath() = /storage/emulated/0 //这个方法是获取外部存储的根路径
- Environment.getExternalStoragePublicDirectory(“”).getAbsolutePath() = /storage/emulated/0 这个方法是获取外部存储的根路径 3. getExternalFilesDir(“”).getAbsolutePath() = /storage/emulated/0/Android/data/packname/files 这个方法是获取某个应用在外部存储中的files路径 4. getExternalCacheDir().getAbsolutePath() = /storage/emulated/0/Android/data/packname/cache 这个方法是获取某个应用在外部存储中的cache路径
清楚数据和卸载APP时, 内外存储的file和cache都会被删除
内部存储file和cache不需要权限;外部存储低版本上(19以下)file和cache需要权限,高版本不需要权限;Environment.getExternalStorageDirectory()需要权限
# 4. SQLite
- SQLite每个数据库都是以单个文件(.db)的形式存在,这些数据都是以B-Tree的数据结构形式存储在磁盘上。
- 使用SQLiteDatabase的insert,delete等方法或者execSQL方法默认都开启了事务,如果操作顺利完成才会更新.db数据库。事务的实现是依赖于名为rollback journal文件,借助这个临时文件来完成原子操作和回滚功能。在/data/data//databases/目录下看到一个和数据库同名的.db-journal文件。
db.beginTransaction();
try {
...
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
# 5. 数据库的升级
- Android中提供了SqLiteOpenHelper类,当版本更新时,会自动调用onUpgrade方法,我们在此方法中升级
- 如果修改已有表,可以才有临时表的方法:
- 将已有表重命名成一个临时表
- 创建新表
- 拷贝
- 删除临时表
# 6. 如何导入外部数据库
- 把数据库db文件放在res/raw下打包进apk
- 通过FileInputStream读取db文件,通过FileOutputStream将文件写入/data/data/包名/database下
# View
# 0. android:gravity与android:layout_gravity的区别
- gravity是控制当前View内布局的位置
- layout_gravity是控制View在父布局中的位置
# 1. View的绘制流程
从ViewRootImpl的performTraversals开始,经过measure,layout,draw 三个流程。draw流程结束以后就可以在屏幕上看到view了。
# 2. View的measureSpec 由谁决定?顶级view呢
- View的MeasureSpec由父容器的MeasureSpec和其自身的LayoutParams共同确定,
- 而对于DecorView是由它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同确定。
# 3. View和ViewGroup的基本绘制流程
# View
- measure -> onMeasure
- layout(onLayout方法是空的,因为他没有child了)
- draw -> ondraw
# ViewGroup
- measure -> onMeasure (onMeasure中需要调用childView的measure计算大小)
- layout -> onLayout (onLayout方法中调用childView的layout方法)
- draw -> onDraw (ViewGroup一般不绘制自己,ViewGroup默认实现dispatchDraw去绘制孩子)
# 4. draw方法 大概有几个步骤
- drawbackground
- 如果要视图显示渐变框,这里会做一些准备工作
- draw自身内容
- drawChild
- 如果需要, 绘制当前视图在滑动时的边框渐变效果
- 绘制装饰,如滚动条
# 5. 怎么控制另外一个进程的View显示
RemoteView:RemoteViews实现了Parcelable接口,通过binder机制传递给远程进程,进程间view的显示
# 6. 两指缩放
- 为了解决多点触控问题,android在MotionEvent中引入了pointer概念
- 通过ACTION_DOWN、ACTION_POINTER_DOWN、ACTION_MOVE、ACTION_POINTER_UP、ACTION_UP来检测手机的动作
- 每个手指的位置可以通过getX(pointIndex)来获得,这样我们就能判断出滑动的距离
- 缩放有多种实现: 1. ImageView可以通过setImageMatrix(martix)来实现 2. 自定义View可以缩放Canvas的大小 3. 还可以设置LayoutParams来改变大小
# 7. Scroller
- Scroller 通常用来实现平滑的滚动
- 实现平滑滚动:
- 新建Scroller,并设置合适的插值器
- 在View的computeScroll方法中调用scroller,查看当前应该滑动到的位置,并调用view的scrollTo或者scrollBy方法滑动
- 调用Scroller的start方法开始滑动
# 8. ScrollView时候滑动到底部
- 滑动时会调用onScrollChange方法,在该方法中监听状态
- 判断childView.getMeasureHeight(总高度) == getScrollY(滑动的高度) + chilView.getHeight(可见高度)
# 9. View事件的分发
- 思想:委托子View处理,子View不能处理则自己处理
- 委托过程:activity -> window -> viewGroup -> view
- 处理事件方法的优先级:onTouchListener > onTouchEvent > onClickListener
// 伪代码
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
if(onInterceptTouchEvent(ev)){
consume = onTouchEvent(ev)
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
- 完整的事件通常包括Down、Move、Up,当down事件被拦截下来以后,move和up就不再走intercept方法,而是直接被传递给当前view处理
# 10. 什么时候执行ACTION_CANCEL
- 一个点击或者活动事件包含ACTION_DOWN,ACTION_MOVE,ACTION_UP等
- 当子View处理了ACTION_DOWN事件之后,后续的ACTION_MOVE,ACTION_UP都会直接交由这个子View处理
- 如果此时父View拦截了ACTION_MOVE,ACTION_UP,那么子View会收到一个ACTION_CANCEL
- 场景举例:点击一个控件,并滑动到控件外,此时次控件会收到一个ACTION_CALNCEL
# 11. 滑动冲突
外部拦截:重写onInterceptTouchEvent方法 内部拦截:重写dispatchTouchEvent方法,同时配合requestDisAllowInterceptTouchEvent方法
# 12. ListView怎么优化(举例)
- 复用convertView,对convetView进行判空,当convertView不为空时重复使用,为空则初始化,从而减少了很多不必要的View的创建、减少findViewById的次数,
- 采用ViewHolder模式缓存item条目的引用
- 避免在getview方法中做耗时操作
- 避免使用半透明或者在活动中取消半透明
- 图片异步加载,待滑动停止后再加载
- 局部刷新
# 13. RecyclerView
RecyclerView 与 ListView 类似,都是通过缓存view提高性能,但是RecyclerView有更高的可定制性。下面是使用时的一些设置,通过这些设置来达到对view样式的自定义:其中1、2是必须设置的,3、4可选
- 想要控制其item们的排列方式,请使用布局管理器LayoutManager
- 如果要创建一个适配器,请使用RecyclerView.Adapter (Adapter通过范型的方式,帮助我们生成ViewHolder)
- 想要控制Item间的间隔,请使用RecyclerView.ItemDecoration
- 想要控制Item增删的动画,请使用RecyclerView.ItemAnimator 扩展:RecyclerView可以很方便的进行局部刷新(notifyItemChanged())
# 14. RecyclerView绘制流程
RecyclerView的Measure和Layout是委托LayoutManager进行的
# 15. RecyclerView的局部刷新
- 调用notifyItemChange方法
- 如果想刷新某个item的局部,可以有两种方法
# 16. RecyclerView的缓存
- RecyclerView采用四级缓存,ListView采用两级缓存
- 四级缓存:
- mAttachedScrap:用于屏幕内的itemView快速复用,不需要create和bind
- mCacheViews:用于屏幕外的itemView快速复用,默认为两个,通过postion查找,不需要create和bind
- mViewCacheExtentsion:需要用户定制,默认不实现
- mRecyclerPool:默认上限5个;不需要create,需要bind;多个RecyclerView可以共用一个pool
- 总结:缓存方面和ListView最大区别是mCacheViews可以缓存屏幕外的item,并且不需要重新bind
# 17. RecyclerView 自定义LayoutManager
- 重写onLayoutChildren方法
- 调用detachAndScrapAttachedViews方法,缓存View
- 计算并设置每个children的位置
- 调用fill方法
- 重写fill方法进行布局
- 重写canScrollVertically和scrollVerticallyBy方法,支持滑动
# 18. SurfaceView与View的区别
- View主要适用于主动更新的情况下,而SurfaceView主要适用于被动更新,例如频繁地刷新;
- View在主线程中对画面进行刷新,而SurfaceView通常会通过一个子线程来进行页面刷新
- 而SurfaceView在底层实现机制中就已经实现了双缓冲机制
# 19. view 的布局
布局全都继承自ViewGroup
- FrameLayout(框架布局) :没有对child view的摆布进行控制,这个布局中所有的控件都会默认出现在视图的左上角。
- LinearLayout(线性布局):横向或竖向排列内部View
- RelativeLayout(相对布局):以view的相对位置进行布局
- TableLayout(表格布局):将子元素的位置分配到行或列中,一个TableLayout由许多的TableRow组成
- GridLayout:和TableLayout类似
- ConstraintLayout(约束布局):和RelativeLayout类似,还可以通过GuideLine辅助布局,适合图形化操作推荐使用
- AbsoluteLayout(绝对布局):已经被废弃
# 20. 线性布局 相对布局 效率哪个高
相同层次下,因为相对布局会调用两次measure,所以线性高 当层次较多时,建议使用相对布局
# 21. View 的invalidate\postInvalidate\requestLayout方法
- invalidate 会调用onDraw进行重绘,只能在主线程
- postInvalidate 可以在其他线程
- requestLayout会调用onLayout和onMeasure,不一定会调用onDraw
# 22. 更新UI方式
- Activity.runOnUiThread(Runnable)
- View.post(Runnable),View.postDelay(Runnable,long)
- Handler
# 23. postDelayed原理
- 不管是view的postDelayed方法,还是Handler的post方法,通过包装后最终都会走Handler的sendMessageAtTime方法
- 随后会通过MessageQueue的enqueueMessage方法将message加入队列,加入时按时间排序,我们可以理解成Message是一个有序队列,时间是其排序依据
- 当Looper从MessageQueue中调用next方法取出message时,如果还没有到时间,就会阻塞等待
- 2中当有新的message加完成后,会检查当前有没有3中设置的阻塞,需不需要唤起,如果需要唤起则唤起
# 24. 当一个TextView的实例调用setText()方法后执行了什么
- setText后会对text做一些处理,如设置AutoLink,Ellipsize等
- 在合适的位置调用TextChangeListener
- 调用requestLayout和invalidate方法
# 25. 自定义View执行invalidate()方法,为什么有时候不会回调onDraw()
- View 的draw方法会根据很多标识位来决定是否需要调用onDraw,包括是否绑定在当前窗口等
# 26. View 的生命周期
- 构造方法
- onFinishInflate:该方法当View及其子View从XML文件中加载完成后会被调用。
- onVisibilityChanged
- onAttachedToWindow
- onMeasure
- onSizeChanged
- onLayout
- onDraw
- onWindowFocusChanged
- onWindowVisibilityChanged
- onDetachedFromWindow
# 27. ListView针对多种item的缓存是如何实现的
- 维护一个缓存view的数组,数组长度是 adapter的getViewItemTypeCount
- 通过getItemViewType获得缓存view 的数组,取出缓存的view
# 28. Canvas.save()跟Canvas.restore()的调用时机
save:用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。 restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。
# WebView
# 0. WebView 优化
- 替换内核:不同的rom厂商对webview的优化不同,可能出现兼容性和速度问题,可以替换成X5内核等来解决兼容性问题,同时提高加载速度
- WebView初始化提前:WebView初始化是一个耗时的过程,我们可以预先将WebView初始化好,例如使用全局webview
- 开启缓存,可以明显提升第二次加载的速度
- 优化dns解析速度:使用和api一样的域名,这样dns不用二次解析,可以提高速度
- 预加载:将需要的文件资源通过native方法提前加载好或者打包进apk,需要使用时直接使用
- 延迟加载:延迟加载部分资源,在界面要显示的数据加载完成后再加载,如图片资源,js等
- 对于webview内存泄漏:单独开一个进程,Activity销毁时手动回收资源
# 1. WebView与Native交互
WebView中拦截网址:设置setWebViewClient,重写shouldOverrideUrlLoading
js与native交互
:
- mWebView.getSettings().setJavaScriptEnabled(true);
- mWebView.addJavascriptInterface(new JSInterface(), "jsInterface");
- 其他js调用Native方案:通过prompt, alert等,在webClient中重写拦截相应的方法
# 性能相关
# 0. Android中ViewHolder,Handler等为什么要被声明成静态内部类(static)
非静态内部类会持有外部类的引用,在一些情况下很可能造成内存泄漏,所以一般声明为静态内部类,但并不是说一定要生命成静态内部类。
# 1. Android中捕获 App崩溃和重启
- 实现Thread.UncaughtExceptionHandler()接口,在uncaughtException方法中完成对崩溃的上报和对App的重启。
- 实现自定义Application,并在Application中注册1中Handler实例。
# 2. LruCache实现原理
最近最少使用算法:内部通过LinkedHashMap来实现
# 3. Parcelable和Serializable
- 都是序列化的方式
- Serializable只需实现Serializable接口即可
- Parcelable需要实现Parcelable接口,并重写writeToParcel和describeContents方法,并且实现一个Creator
- Serializable虽然操作简单,但是需要大量IO操作,效率慢;Parcelable自已实现封送和解封(marshalled &unmarshalled)操作不需要用反射,数据也存放在Native内存中,效率要快很多,在Android中更推荐使用Parcelable
- 由于不同版本Parcelable可能存在不同,在网络和磁盘存储时,推荐使用Parcelable
# 4. 加载合适比例的Bitmap到内存中
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// 首先只解析图片资源的边界
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
//计算缩放值
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 用计算出来的缩放值解析图片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// 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;
}
}
return inSampleSize;
}
# 5. 图片的三级缓存
- 首次加载 Android App 时,肯定要通过网络交互来获取图片,之后我们可以将图片保存至本地SD卡和内存中
- 之后运行 App 时,优先访问内存中的图片缓存
- 若内存中没有,则加载本地SD卡中的图片
# 6. App保活
随着Google对Android系统的更新,以及国内厂商堆Room的定制,一些保活的手段已经失效或者不再适用,这里列举一些保活手段,实际中常常是多个方式并用
- 联系Room厂商加入白名单(或者引导用户手动加入白名单)
- 利用系统漏洞root进行提权,或者直接把本应用变成系统应用
- Service设置成START_STICKY:kill 后会被重启(等待5秒左右),重传Intent,保持与重启前一样
- 提升service优先级:通过android:priority = "1000"这个属性设置最高优先级(可能已经失效)
- onDestroy方法里重启service(效果不好)
- 监听广播(除了监听系统广播外,还可以利用友盟等第三方sdk做到应用相互唤起)
- 启动两个进程service相互监听,互相唤起(大部分room上失效)
- 在屏幕上保留1像素
- 注册系统同步服务
- 创建Native进程,双进程互相监听保活(5.0 以上失效)
- 利用JobSheduler保活
# 7. 如何判断APP被强杀
- 在Application中定义一个static常量,赋值为-1
- 在欢迎界面改为0
- 在BaseActivity判断该常量的值
# 8. 常见的内存泄漏(举例)
- 不恰当的使用static变量(或者向static集合中添加数据却忘了必要时移除数据)
- 忘记关闭各种连接,如IO流等
- 不恰当的内部类:因为内部类持有外部类的引用,当内部类存活时间较长时,导致外部类也不能正确的回收(常发生在使用Handler的时候)
- 不恰当的单例模式:例如错误的将某个Activity给单例持有,或者在不该使用单例的地方使用了单例
- 使用错误的Context:Application 和 Activity的context生命周期不一样
- webview造成的内存泄漏
# 9. OOM异常是否可以被try...catch捕获
- 在发生地点可以捕获
- 但是OOM往往是由于内存泄漏造成的,泄漏的部分多数情况下不在try语句块里,所以catch后不久就会再次发生OOM
- 对待OOM的方案应该是找到内存泄漏的地方以及优化内存的占用
# 10. 什么是ANR,如何避免它
- ANR是Application Not Responding 的缩写,当应用程序无响应时,会弹出ANR窗口,让用户选择继续等待还是关闭应用。
- 处理超过时间会造成ANR的地方:触摸操作等(5s);BroadCast(10s);Service(20s)
- 避免:不要在主线程中做耗时操作
# 11. 什么情况会导致Force Close ?如何避免?能否捕获导致其的异常?
- 出现运行时异常(如nullpointer/数组越界等),而我们又没有try catch捕获,可能造成Force Close
- 避免:需要我们在编程时谨慎处理逻辑,提高代码健壮性。如对网络传过来的未知数据先判空,再处理;此外还可以通过静态代码检查来帮助我们提高代码质量
- 此外,我们还可以在Application初始化时注册UncaultExceptionHandler,来捕捉这些异常重启我们的程序
# 12. DDMS和TraceView
- DDMS是一个程序执行查看器,在里面可以看见线程和堆栈等信息
- TraceView是一个性能分析器 扩展: Android Studio 3 中用Profiler代替了DDMS,可以监视分析CPU,网络,内存的实时情况,更加方便
# 13. LRUCache原理
- 通过LinkedHashMap实现的
- LinkedHashMap的特性:LinkedHashMap是一个双向链表,当构造函数中accessOrder为true时:调用put()方法,会在集合头部添加元素时,会调用trimToSize()判断缓存是否已满,如果满了就用LinkedHashMap的迭代器删除队尾元素,即近期最少访问的元素。当调用get()方法访问缓存对象时,就会调用LinkedHashMap的get()方法获得对应集合元素,同时会更新该元素到队头
# 14. 模块化的好处
- 不同模块间解偶,方便复用
- 单个模块支持独立编译,并行开发,提高开发和测试效率
# 15. 组件化
- 组件化是已复用为目的的
- 多个团队可能公用一个组件
# 16. SparseArray
- SparseArray通过两个数组实现,key为int型,value为Object型
- 适用于数据量较小时
- 通过二分法插入和删除数据
# 17. APP启动耗时
- 启动分为冷启动(包含初始化Application)和热启动,冷启动又可以分为第一次启动(会有额外的初始化数据库等操作)和不是第一次启动
- 开发时本地可以用adb命令获取启动时常:adb shell am start -w packagename/activity
- 线上可以在各个关键地方埋点统计时常
# 18. Android启动优化
- 启动优化的目的是提高用户感知的启动速度
- 可以采用TraceView等分析耗时,找出优化方向
- 优化的思路:
- 利用提前展示出来的Window,设置Theme,快速展示出来一个界面,给用户快速反馈的体验(启动后再改变回来)
- 异步初始化一些第三方SDK
- 延迟初始化
- 针对性的优化算法,减少耗时
# 19. 性能调优
- 性能调优包括很多方面:包括代码执行效率、网络、布局、内存、数据库、业务逻辑,打包速度等
- 需要针对具体问题具体分析,找到性能瓶颈:
- 善于借助工具,例如使用traceview跟踪,或者打点统计,profiler,leak canary等
# 网络优化:
- 不用域名,用ip直连
- 请求合并与拆分
- 请求数据的缩小:删除无用数据,开启Gzip压缩
- 精简的数据格式:json/webp/根据设备和网络环境的不同采用不同分辨率图片
- 数据缓存
- 预加载
- 连接的复用:使用http2.0 (效率提升30%) 扩展:JD Android客户端网络性能调优之HTTP/2协议升级
# 布局优化
- 合理的使用include/merge/viewstub等
- 减少布局层次,减少不必要的view和节点
- 使用布局缓存,避免重复inflate 其他:可以用hierarchy viewer等工具进行分析
# 代码优化
- 降低执行时间:如优化算法和数据结构/利用多线程/缓存(包括对象缓存、线程池、IO 缓存、网络缓存等)/JNI/需求优化
- 同步改异步:例如使用surfaceview
- 错峰:提前或者延迟操作,比如启动中第三方sdk的加载
# APK瘦身
- 分析APK
- 使用Android Studio的分析器分析apk结构
- 使用lint分析无用代码和资源
- 或者使用第三方工具,如NimbleDroid/ClassShark
- 删除无用资源
- 对无用资源和代码删除
- 优化结构,对重复资源去重
- 对依赖去重,依赖多个功能类似的sdk时,只保留一个
- 去除不需要的依赖,如语言support包可能包含多种语言,配置只保留我们需要的资源等
- 开启gradle的ProGuard/Code shrinking/minifyEnabled等,自动去除不需要的资源和代码】
- 压缩已用资源
- 选取适当的图片格式,如webp
- 对图片进行压缩
- 选取合适的依赖包,而不是直接依赖V7包等
- 使用微信的打包插件AndResGuard对图片进行压缩
- 使用facebook的ReDex对dex文件进行压缩
- 通过网络按需加载
# 20. 有什么提高编译速度的方法
- 提高电脑配置
- 优化Android studio配置
- 加大内存;
- 使用守护进程;
- 开启instant Run
- 使用离线gradle
- 使用新版的gradle
- 优化项目
- 使用第三方编译如[阿里的Freeline](https://www.freelinebuild.com/docs/zh_cn/###
- debug模式下可以不开启混淆等
- 模块化,多模块时使用aar包避免反复编译
# 21. 延迟加载的实现
- myHandler.postDelayed(mLoadingRunnable, DEALY_TIME);
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
myHandler.post(mLoadingRunnable);
}
});
// 拿到主线程的MessageQueue
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
// 在这里去处理你想延时加载的东西
delayLoad();
// 最后返回false,后续不用再监听了。
return false;
}
});
# 22. ArrayMap
- ArrayMap 是Android中的一种用时间换空间的容器,用来替代HashMap
- 不适合大量数据或者有大量删除
- 采用二分查找
- 原理: 0. 使用两个数组进行存储
- 数组1储存key的HashCode
- 数组2储存key和value;value位置为key位置左移1位再加1
- hash冲突的解决:寻找下一个空位,因为查询时会比较hashcode和key,所以不会有问题
# 系统实现及原理
# 0. AsyncTask
- 为我们封装了thread和handler,并在内部实现了线程池,是一个轻量级的线程方案
- 缺点:不同版本实现方式可能不一样,例如线程池有可能是串行的(最新版本是并行的)
- 缺点:可定制化程度不高,例如我们不能很方便地cancel线程
# 1. AsyncTask是顺序执行么,for循环中执行200次new AsyncTask并execute,会有异常吗
- 不同版本的AsyncTask 不一样,最新版本中是串行的
- 不会,因为串行执行时用的是一个ArrayDeque来存放Runnable
- 扩展:AsyncTask内部有并行的ThreadPoolExecutor,储存队列用的是LinkedBlockingQueue,大小是128;如果我们并行执行,会抛出运行时异常
# 2. Android 渲染机制
- CPU对视图进行必要的计算:measure等
- 通过OpenGL 将CPU 处理过的数据交给GPU
- GPU进行栅格化并存入缓存
- Android 系统每隔16.6ms发出一个垂直同步信号,通知渲染
# 3. Android中Application和Activity的Context对象的区别
- 生命周期不一样
- Application 不能showDialog
- Application startActivity时必须new一个Task
- Application layoutInflate直接使用默认主题,可能与当前主题不一样
# 4. Zygote的启动过程
- 注册Zygote的socket监听端口,应用接收启动应用程序的消息
- 调用preload()方法加载系统资源,包括预加载类,Framework资源等
- 调用startSystemServer()方法启动SystemServer进程
- 调用runSelectLoop()方法进入监听和接收消息循环
# 5. Handler、Looper、Message、MessageQueue
它们的存在主要是为了线程间通信,通信的方式为:
- 在线程中调用Looper.prepare方法,生成一个looper与当前线程绑定(在生成looper的过程中,其构造方法在其内部创建了一个MessageQueue)
- 调用looper.loop方法,使当前线程循环读取MessageQueue中的message,并调用 msg.target.dispatchMessage方法,交由target处理(这里target是一个Handler实例)
- new 一个Handler,在创建Handler时,需要为他指定looper(若不指定则是当前线程的looper),Handler会取出looper中的MessageQueue也作为自己的MessageQueue。
- 调用Handler的sendMessage方法发送Message信息(这个方法将Message的target设置成当前handler,并把它加入到MessageQueue中)
# 6. 为什么主线程Looper.loop不会ANR
- ANR是应用程序无响应,原因是有事件在主线程运行时间过长造成新的事件无法处理,或者当前事件运行时间太长
- Looper.loop会循环处理到来的Message,当MessageQueue为空是,线程处于阻塞状态,释放cpu资源
# 7. Messenger 和 AIDL
都是进城间通信的方式,AIDL使用Binder通信,Messenger通过AIDL实现
Messenger原理:Handler内部持有一个IMessenger实例,IMssenger时一个aidl的接口,Messenger初始化时这个IMssenger实例也传给了Messenger
区别:1.Messenger是对AIDL的封装,使用简单;2.Messenger通过Handler处理传过来的Message,只能运行在一个线程中,我们在AIDL中可以运行多个线程;3.Messenger客户端调取服务方法的结果只能异步回调给客户端,AIDL可以同步回调
# 8. Binder
瞎jj写点凑个数吧
- Binder是Android中的一种进程间通信方式
- Binder通信是通过驱动来实现的,原理是在内核空间中进行内存映射,以为只有一次内存拷贝,所以速度较快
- Binder会验证权限,鉴定UID/PID来验证身份,保证了进程通信的安全性
- 系统的Service会想ServiceManager注册,使用时向ServiceManager获取
- Service/Client同ServiceManager通信的过程本身也是通过binder驱动实现的
- android中Service的bind通信是通过ActivityManagerService实现的
- Binder中的代理模式:
- 客户端代理对象和服务端实现统一接口
- 客户端获取服务端的引用,如果位于同一进程,那么获取的是服务端本身,如果是不同进程,获取到的是服务端的代理
- 通过代理请向服务端请求
- 服务端接收请求并处理,返回给客户端
# 9. AIDL
- 全称:Android Interface Define Language(Android接口定义语言)
- 目的是为了进行进程间通信
- 使用方式:定义aidl文件,编译器编译时会帮我们生成对应的JAVA代码;通过调用生成的java代码,来进行进程间通信
- 原理:通过Binder方式进行通信
# 10. Window、WindowManager以及Activity
- Window 0. Window有三类:系统Window、应用Window、子Window
- Window是接口,具体实现类是PhoneWindow
- Window 是一个抽象概念,我们并不能直接操作window
- Activity在创建的时候attach方法中会创建Window并使之与Activity关联
- Window中会创建Decorview,并通过ViewRootImpl与View交互
- WindowManager 0. 在Activity启动时,handleResumeActivity方法中启动activity的时候,会将主窗口加入到WindowManager中
- 我们并不能直接操作window,而是通过WindowManager
- WindowManagerImpl是其实现类,他将view增删改的操作交给 WindowManagerGlobal处理
- WindowManagerGlobal 中会调用 ViewRootImpl的方法
- ViewRootImpl通过IWindowSession与WindowManagerService交互
# 11. 理解Window和WindowManager
- Window用于显示View和接收各种事件,Window有三种类型:应用Window(每个Activity对应一个Window)、子Window(不能单独存在,附属于特定Window)、系统window(Toast和状态栏)
- Window分层级,应用Window在1-99、子Window在1000-1999、系统Window在2000-2999.WindowManager提供了增删改View三个功能。
- Window是个抽象概念:每一个Window对应着一个View和ViewRootImpl,Window通过ViewRootImpl来和View建立联系,View是Window存在的实体,只能通过WindowManager来访问Window。
- WindowManager的实现是WindowManagerImpl其再委托给WindowManagerGlobal来对Window进行操作,其中有四个List分别储存对应的View、ViewRootImpl、WindowManger.LayoutParams和正在被删除的View
- Window的实体是存在于远端的WindowMangerService中,所以增删改Window在本端是修改上面的几个List然后通过ViewRootImpl重绘View,通过WindowSession(每个应用一个)在远端修改Window。
- Activity创建Window:Activity会在attach()中创建Window并设置其回调(onAttachedToWindow()、dispatchTouchEvent()),Activity的Window是由Policy类创建PhoneWindow实现的。然后通过Activity#setContentView()调用PhoneWindow的setContentView。
# 12. Window添加的过程
- ActivityThread做为APP的入口,会执行 handleLaunchActivity -> performLaunchActivity
- performLaunchActivity中通过ClassLoader创建Activity对象 -> 调用Activity.attach方法 -> callActivityOnCreate
- 在Activity.attach方法中会创建一个Window实例PhoneWindow,并将activity作为callback传递给window
- 在Activity的onCreate方法中,会调用setContentView,setContentView会调用PhoneWindow 的 setContentView
- PhoneWindow 的 setContentView会创建DecorView,并把我们自己设置的ContentView和DecorView绑定
- performLaunchActivity至此走完,之后的performResumeActivity会调用handleResumeActivity方法
- 在handleResumeActivity中,会调用Activity的getWindowManager()获取一个WindowManager,接着调用WindowManager的addView方法
- addView实际执行的是WindowManagerGlobal的addView(),这里会创建一个ViewRootImpl,并调用ViewRootImpl的setView方法
- 在setView这个方法内部,会通过跨进程的方式向WMS(WindowManagerService)发起一个调用,从而将DecorView最终添加到Window上
# 13. APK的安装流程
- 解压文件到data/app目录下
- 资源管理器加载资源文件
- 解析解析AndroidManifest文件,并在/data/data/目录下创建对应的应用数据目录。
- 然后对dex文件进行优化,并保存在dalvik-cache目录下。
- 将AndroidManifest文件解析出的四大组件信息注册到PackageManagerService中。
- 安装完成后,发送广播。
# 14. 双亲委托模式类加载的过程
- 调用当前类加载器的loadClass加载类
- loadClass中先调用findLoadedClass查看类是否已加载,已加载->over
- 还没有加载,调用父类加载器的loadClass,父类加载器加载完成 -> over
- 如果父类加载器没有加载,调用本类加载器的findClass,并在findClass中调用·defineClass方法加载
# 15. Android中ClassLoader
- Android中加载的是Dex文件
- ClassLoader 是个抽象类,其具体实现的子类有 BaseDexClassLoader 和SecureClassLoader,(SecureClassLoader 的子类是 URLClassLoader ,其只能用来加载 jar 文件,这在 Android 的 Dalvik/ART 上没法使用的)
- BaseDexClassLoader 的子类是 PathClassLoader 和 DexClassLoader 。
- PathClassLoader 在应用启动时创建,从 data/app/… 安装目录下加载 apk 文件。
- DexClassLoader 则没有此限制,可以从 SD 卡或网络加载包含 class.dex 的 .jar 和 .apk 文件,这也是插件化和热修复的基础
# 项目构建
# 0. 点击AndroidStudio的build按钮后发生了什么
build过程即执行gradle task 打包生成apk的过程:
- 通过appt工具,将资源文件生成R.java文件;将aild文件转换成对应的java文件
- 编译java文件,生成.class文件
- 将.class文件转换成Android虚拟机支持的.dex文件
- 通过apkbuilder将dex文件和编译后的资源文件生成apk文件
- 对apk进行签名和对齐
# 1. Android Debug和Release状态的不同
调试模式允许我们为优化代码而添加许多额外的功能,这些功能在Release时都应该去掉;Release包可以为了安全等做一些额外的优化,这些优化可能比较耗时,在Debug时是不需要的
- log日志只在debug时输入,release时应该关掉(为了安全)
- 签名/混淆/压缩等在debug编译时可以加入,减少打包时间
- 可以在debug包中加入一些额外的功能辅助我们开发,如直接打印网络请求的控件,内存泄漏检测工具LeakCanary等
- 在打Release包时,除了混淆等操作,往往还需要加固操作,保证APP的安全
# 2. Dalvik和Art
- Dalvik 是 Android 中使用的虚拟机,执行dex字节码
- Dalvik 与 JVM 相比
- JVM执行class字节码文件,Dalvik执行dex字节码文件,dex文件做了优化,提及更小
- Dalvik是基于寄存器的,VM基于栈
- Art是Dalvik虚拟机的升级版,Dalvik是解释型的,Art是翻译型的
- Dalvik虚拟机执行的是dex字节码,ART虚拟机执行的是本地机器码
- 相对于Dalvik,Art安装占用内存更大,安装时间更长,但是运行速度会有提升
# 3. 解决方法数超过65535的方法
- 代码混淆(原理是减小方法数)
- sdk裁减(原理是减小方法数)
- multi-dex(原理是打包成多个dex)
# 4. multi-dex问题
引入multi-dex后,在5.0以下手机上,第一次安装后启动速度可能变慢甚至anr,需要进行优化:如单独开一个线程;修改keep文件等
# 5. App签名
- 原理:先计算出hash值,再对hash值进行非对称加密
- V1版本签名生成的APK中与签名有关的文件:
- MANIFEST.MF: jar 包的文件清单,在 apk 中我们用来记录所有非目录文件的 数据指纹
- CERT.SF:根据MANIFEST.MF生成的文件
- CERT.RSA:这里会把之前生成的CERT.SF文件,用私钥计算出签名, 然后将签名以及包含公钥信息的数字证书一同写入CERT.RSA 中保存
- V1版本存在安全漏洞,google推出了v2版
# 6. 常用的adb 和 adb shell命令
查看当前连接的设备:adb devices 结束adb连接: adb kill-server 安装apk: adb install test.apk 从手机获取文件和推送文件到手机:adb push <本地文件> <远程路径> ; adb pull <远程路径> <本地路径> 获取log信息:adb logcat > log.txt
启动Activity: adb shell am start -n 包名/包名+类名 显示系统Activity栈信息:adb shell dumpsys activity 发送广播:adb shell am broadcast -a "android.intent.action.AdupsFota.WriteCommandReceiver" 查看进程信息:adb shell ps <package_name|PID> 杀掉某个进程:adb shell kill pidNumber 查看内存占用:adb shell dumpsys meminfo <package_name|PID>
# 7. jar和aar的区别
Jar包里面只有代码,aar里面不光有代码还包括代码还包括资源文件,比如 drawable 文件,xml 资源文件。对于一些不常变动的 Android Library,我们可以直接引用 aar,加快编译速度
# 8. 不同的CPU架构对APP的影响
- cpu的架构有armeabi、armeabi-v7a、x86等
- 针对不同的CPU,使用不同的so包,可以使性能最大化
- 如果a.so提供了armeabi、armeabi-v7a、x86格式,那么b.so也要提供这几个格式,否则可能崩溃
- 当没有对应cpu的so时,会选择其他so,但执行速度会变慢:当一个应用安装在设备上,只有该设备支持的CPU架构对应的.so文件会被安装。在x86设备上,libs/x86目录中如果存在.so文件的话,会被安装,如果不存在,则会选择armeabi-v7a中的.so文件,如果也不存在,则选择armeabi目录中的.so文件(因为x86设备也支持armeabi-v7a和armeabi)
# 9. compileSdkVersion,minSdkVersion,targetSdkVersion都是啥
- compileSdkVersion :编译所依赖的版本,它可以让我们在写代码时调用最新的api,告知我们过时的api
- minSdkVersion:最小的可安装此App的版本,意味着我们不用做低于此版本的兼容
- targetSdkVersion: 目标版本,可以让我们虽然运行在最新的手机上,但是行为和target版本一致,比如:如果targetSdkVersion小于Android 6.0,那么即使我们的app运行在6.0系统上,也不需要运行时权限
# 10. 低版本SDK实现高版本api
- 使用@TargetApi 来标明api版本,这样编译器就不会报错了
- 在代码逻辑中判断版本,在低版本上调用替代api或自己实现的算法。
# 部分功能的实现
# 0. 怎样退出终止自己的APP
- 记录启动的activity
- 需要退出时调用存活activity的finish方法,并调用System.exit(0)方法
# 1. Android中启动线程的几种方式
- java中可以用实现Runnable接口、实现Callable接口、继承Thread类三种方式
- Android中还可以用AsyncTask、HandlerThread、IntendService
# 2. 长链接+心跳包
- 长连接:App 与服务器建立一个生命周期很长的连接,服务器通过push向App推送消息
- 心跳包:App 每隔一段时间就会向服务器查询是否有新的消息
- 长连接可能因为各种原因被打断,心跳包接收消息可能不及时,所以我们可以采取长连接+心跳包的方式:通过Socket建立一个长连接,并通过心跳包检测这个长连接是否存活,长连接中断的话则重新建立
# 3. Android 中XML的解析
Androi中主要有DOM,SAX,PULL三种方式 DOM将文件都加载到内存中,比较消耗内存;SAX和PULL节省内存,PULL使用比SAX更简单
# 4. 系统上安装了多种浏览器,能否指定某浏览器访问指定页面?请说明原由。
- 指定浏览器:intent.setClassName(“com.android.browser”,”com.android.browser.BrowserActivity”);
- 指定网址: Uri uriBrowsers = Uri.parse(“http://www.sina.com.cn”); intent.setData(uriBrowsers);
# 5. java中如何引用本地语言
可以用JNI(java native interface java 本地接口)接口
# 6. JNI的使用方式
下载NDK,配置环境变量,配置gradle文件
JAVA中声明native 方法如private native String printJNI(String inputStr);
生成或写对应的头文件
编写对应文件实现代码
编译成so文件
使用
扩展:
native调用java代码
- 获取你需要访问的Java对象的类
jclass cls = (*env)->GetObjectClass(env, obj); //使用GetObjectClass方法获取obj对应的jclass。
class cls = (*env)->FindClass(“android/util/log”) //直接搜索类名,需要是static修饰的类。
- 获取MethodID:
methodID mid = (*env)->GetMethodID(env, cls, "callback", "(I)V"); //GetStaticMethodID(…),获取静态方法的ID使用GetMethdoID方法获取你要使用的方法的MethdoID
- 调用:
(*env)->CallVoidMethod(env, obj, mid, depth);// CallStaticIntMethod(….) , 调用静态方法
# 7. NDK是什么
NDK 是Native Development Kit 的缩写,是一些列工具的集合,帮助开发者迅速的开发C/C++的动态库
# 8. 什么是JVM
- JVM是JAVA虚拟机,保证了java语言的跨平台性,是编译后的 Java 程序(.class文件)和硬件系统之间的接口
- JVM = 类加载器 classloader + 执行引擎 execution engine + 运行时数据区域 runtime data area
# 9. 视频加密
视频加密根据场景的不同有很多种方式
- 如仅对地址加密,可以起到防盗链的目的,可以与其他方法一起使用
- 对整个文件加密,加解密时间长,不实用
- 对文件的头中尾加密,播放器可以直接跳过,破解简单,不实用
- 对视频流加密(基于苹果HLS协议的加密 基于RTPE协议)
- 关键帧加密
# 10. 绘制印章
- 创建一个bitmap,拿到canvas
- 在canvas上绘制圆,绘制五角星,绘制文字,返回bitmap
# 11. 文字阴影和描边的实现
- 阴影:shadow属性
- 描边:两个TextView叠加;或者重写onDraw方法
# 12. Android生成设备唯一标识符
选取 DeviceId,AndroidId,Serial Number,Mac,蓝牙地址等中的一个或者几个作为种子,生成UUID。
# 13. 实现客户端的自动登录
- 第一次登录时保存两个token,一个长效一个短效
- 短效token用于每次网络请求的用户标识
- 长效token用于当短效token失效时自动登录,重新获取token
# 14. Android如何在不压缩的情况下加载高清大图
使用BitmapRegionDecoder
# 15. SSL证书的验证
在使用Https时,我们需要对SSL证书做验证以确保有效
证书需要验证证书有效性,时效,域名等
Android中WebView可以重写onReceivedSslError方法来处理ssl证书不对时的情况
OkHttp设置证书验证
:
- 验证可以是双向的,也可以是单向的
- 单向:将服务器对应的Server.cer文件打包进Apk中,通过cer文件生成SSLSocketFactory,并将其设置给okHttpClient
- 双向:用Server.cer和Client.key生成SSLSocketFactory,并将其设置给okHttpClient
# 16. 计算一张100px*100px的图片在内存中会占用多大内存
- 内存大小 = 100100像素点大小
- 像素点大小和编码方式有关:ARGB_8888占8+8+8+8=32bit;ARGB_4444占4+4+4+4 = 16bit;
# 17. 如何实现动态代理
- 创建一个实现InvocationHandler接口的类,它必须实现invoke方法
- 调用Proxy的静态方法newProxyInstance,创建一个代理类
# 18. Android中有哪些方法实现定时和延时任务?它们的适用场景是什么?
- 倒计时类:用CountDownTimer
- 延迟类: 1. CountDownTimer,可巧妙的将countDownInterval设成和millisInFuture一样,这样就只会调用一次onTick和一次onFinish 2. handler.sendMessageDelayed,可参考CountDownTimer的内部实现,简化一下,个人比较推荐这个 3. TimerTask,代码写起来比较乱 4. Thread.sleep,感觉这种不太好
- 定时类: 1. 参照延迟类的,自己计算好要延迟多少时间 2. handler.sendMessageAtTime 3. AlarmManager,适用于定时比较长远的时间,例如闹铃
# 概念
# 0. Json有什么优势
有比较才会有优势,我们通常将Json与Xml进行比较,Json更加轻量。我觉得在某些程度上讲,这是一个仁者见仁智者见智的问题。例如有些人认为Json相比Xml更易读,有些人责认为不然,这里大致列举几条,仅供参考
- 结构简单,可读性更强,读写更加容易
- 格式是压缩的,占用带宽小
- 支持多种语言
- 因为JSON格式能够直接为服务器端代码使用,大大简化了服务器端和客户端的代码开发量
# 1. MVC
MVC是model,view,controller的缩写
- 模型(model)对象:是应用程序的主体部分,所有的业务逻辑都应该写在该层;
- 视图(view)对象(对应Android中的布局xml文件):是应用程序中负责生成用户界面的部分。也是在整个mvc架构中用户唯一可以看到的一层,接收用户的输入,显示处理结果;
- 控制器(control)对象(对应Android中的Activity):是根据用户的输入,控制用户界面数据显示及更新model对象状态的部分,控制器更重要的一种导航功能,响应用户出发的相关事件,交给m层处理。 扩展:和MVP最大的区别是View和Model可以直接交互
# 2. 什么是控制反转(IOC Inversion of Control)
- 在Java开发中,IoC意 味着将你设计好的类交给系统去控制,而不是在你的类内部控制
- 是框架的重要特征
- Android中Activity 的生命周期都是框架控制的,是一种控制反转
# 3. Android中的IOC(控制反转)框架
- 控制反转:将对象的创建交给框架去做
- 常用的框架:ButterNife/Android Annotation
- 2中两个框架都是通过java的注释框架实现的,并且都是作用在编译期
# 4. 请解释下Android程序运行时权限与文件系统权限的区别
- 运行时权限是APP启动后由Android虚拟机(如Dalvik)控制的
- 文件系统权限是Linux内核授权
# 5. AOP 面向切面编程
- 面向切面编程是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
- 例如很多功能需要先登陆,登陆在这里就是一个切面。
# 6. MVP 架构中 Presenter 定义为接口有什么好处
- 在goole官方的demo中,通过一个Contract把将View和Presenter管理起来,强化其一一对应的关系,便于操作
- [但是也有人认为不应该将Presenter定义为接口]http://www.infoq.com/cn/articles/do-not-give-the-prensenter-in-mvp-interface),因为这并不会方便测试,还会增加复杂性
# 其他
# 0. Android中的几种动画
- 普通动画(视图动画、补间动画)
- 属性动画
- 帧动画
- 物理动画(Android 8.0)
# 1. HttpClient和HttpURLConnection的区别
# HttpClient
- Apache公司提供的库
- 拥有丰富的API,但也因为这个原因,在不破坏兼容性的前提下,其庞大的API也使人难以改进
- Android 6.0中抛弃了Http Client,替换成OkHttp
# HttpURLConnection
- Sun公司提供的库
- 功能比较简单,可拓展性强
- 直接支持GZIP压缩,并且在Android 4.0 以上支持cache缓存,提高了网络效率
# 2. Intent
- Intent是Android中的信使,可以启动Activity,Service等
- Intent可以设置的几项值:Action, Category, Data/Type,Component
- 当设定Component时,是显式调用,其余是隐式调用
# 3. IntentFilter
- IntentFilter 在AndroidMainifest中注册,用来帮助系统选出用户定义的隐式Intent对应的Activity /Service等
- Android是通过Intent的action、data、category这三个属性来进行匹配判断的
- action:隐式启动需要给Intent设置Action,如果没有设置这条匹配则自动通过;必须给IntentFilter设置一个action
- data:如果Intent没有提供type,系统将从data中得到数据类型。同action类似,只要Intent的data只要与Intent Filter中的任一个data声明完全相同,data方面就完全匹配成功。
- category:Intent的Category可以有多个,每一个都需要和IntentFilter匹配才能算匹配上,不设置Intent的category时是默认的(DEFAULT)
- 总结:只有一个Intent的action、data、category匹配上IntenFilter中的一组数据时,才算匹配成功。
# 4. Intent/Bundle支持传送哪种类型的数据
- 基本类型及其数组
- 实现了Serializable或者Parcelable的类型及其数组
# 5. Manifest.xml文件中主要包括哪些信息
- manifest:根节点,描述了包名,版本号等。
- application:包含package中application级别组件声明的根节点。
- activity:Activity是用来与用户交互的主要工具。
- receiver:IntentReceiver能使的application获得数据的改变或者发生的操作,即使它当前不在运行。
- service:Service是能在后台运行任意时间的组件。
- provider:ContentProvider是用来管理持久化数据并发布给其他应用程序使用的组件。
- uses-permission:请求你的package正常运作所需赋予的安全许可。
- permission: 声明了安全许可来限制哪些程序能你package中的组件和功能。
- uses-feature:使用到的硬件信息,如nfc
- upports-screens:支持的屏幕类型
- meta-data:data数据
- instrumentation:声明了用来测试此package或其他package指令组件的代码。
# 6. dp, dip, dpi, px, sp是什么意思
- dp = dip(device independent pixels),是设备独立像素
- sp:scaled pixels(放大像素),主要用于字体显示。
- px(pixel):像素
- dpi(dot per inch)
# 7. dp与px的换算公式
- px = dp*像素密度/160
public static int dp2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
# 8. layout-sw400dp, layout-w400dp分别代表什么意思
- layout-w400dp:当屏幕相对宽度大于400dp时,来这里取layout(与横竖屏有关)
- layout-sw400dp:当屏幕绝对宽度大于400dp时,来这里取layout(与横竖屏无关)
# 9. Android 样式和主题
- 样式(Styles):可以理解成是针对View或者窗口(Window)设置外观或者格式的一个属性集合
- 主题(Themes):主题相比单个视图而言,是应用到整个 Activity 或者 application 的样式
- 区别:
- Theme作用域是Activity或者Application,Stytle针对View或者窗口(Window)
- 某些主题样式不可以在View中使用,例如"@android:style/Theme.NoTitleBar" 等 扩展: 属性(Attributes):你也可以将单个属性应用到 Android 样式上,通常会在自定义View 的时候,自定义属性。
# 10. Android P的新特性(2018/5/5)
- Goole 下个Android版本的预览已经放出,代号p
- 支持wifi室内定位
- 适配刘海屏
- 通知栏改进:可以显示对话,附加照片和表情等
- 多摄像头API
- 神经网络API 1.1
# 11. 热修复原理
热修复的原理是让我们的新类替换掉原来类的加载,从而达到修复的目的,以下是一种思路:
- java中通过PathClassLoader和DexClassLoader来加载类,类加载的方式是双亲委派模式
- PathClassLoader和DexClassLoader都继承自BaseDexClassLoader
- BaseDexClassLoader中维护了一个dex的数组
- 我们可以通过DexClassLoader加载类,然后通过反射的机制将加载进来的数组添加到path数组的前面
- 加载的时候找到我们需要的class后,就不再继续向后找了,所以可以达到修复的目的
# 12. Android中的进程间通信(IPC)
- Bundle : 只支持四大组件
- 文件共享:不适合并发
- Messenger:封装了AIDL
- AIDL:通过binder实现
- ContentProvider:共享数据
- Socket:适用于网络等
# 13. 解决Android7.0 更新安装包时不能自动安装问题
- Android 7.0中私有目录访问会被限制,导致不能自动安装
- 可以使用FileProvider来解决
# 如何开启多进程?应用是否可以开启N个进程?
- 通过在AndroidManifest中给Activity设置process属性开启新的进程
- 可以开启N个进程,例如给webview单独开启一个进程,但要处理多进程间通信和多次初始化Handler问题
# Service先start再bind如何关闭service
先unbind,再stop
# 为什么bindService可以跟Activity生命周期联动
- 在Activity退出时调用unbind方法,service会销毁
- 如果不调用unbind方法,service也会销毁,但是会抛出leaked serviceConnection 异常 (参考2)
# 子线程中如何使用Handler
- 使用HandlerThread,新建Handler时通过调用HandlerThread 的 getLooper方法拿到looper
- 原理:HandlerThread在run时会为我们生成一个looper,getLooper方法会阻塞等待直到 looper!=null 才返回。
# 如何进行单元测试
- Junit:不需要依赖android环境,适合于逻辑测试
- Instrumentation:依赖android环境,可以启动Activity,模拟内存回收,获取组件等,模拟点击等。需要在AndroidManifest中进行配置,适合于更复杂的测试
# TabLayout如何设置指示器的宽度
通过反射拿到对应的指示器,设置LayoutParams
# Android中如何查看一个对象的回收情况
- 外部:通过adb shell 命令导出内存,借助工具分析
- 内部:通过将对象加入WeakReference,配合RefernceQueue观察对象是否被回收,被回收的对象会被加入到RefernceQueue中
# 回形打印二维数组
思路:递归实现,分别打印每一圈
# APK内容
# class文件如何转化成dex
使用build tools 下的dx工具
class 和dex文件对比:
1. 都是二进制文件
2. class文件存在容与,dex文件将整个工程的类信息整合到了一起,去掉了冗余
# 硬件加速
- 随便写点凑个数吧=-=
- 硬件加速的四个级别:Application/Activity/Window/View
# 为什么选择Binder作为通信方式
- binder效率更高:socket是一个通用接口,效率低;管道和队列内存拷贝两次,效率低;共享内存控制复杂
- binder更加安全:binder可以建立私有通道,通过uid/pid验证身份
# 参考资料
https://www.nowcoder.com/discuss/3043
http://weixin.niurenqushi.com/article/2017-03-17/4790406.html
https://blog.csdn.net/vfush/article/details/51481127
https://blog.csdn.net/vfush/article/details/51790079