Android面试准备
(一)Android基础知识点
四大组件是什么 均需注册使用
Activity
Service startService:service生命周期与启动它的组件无关,需要显示销毁
bindService:生命周期与调用者绑定
ContentProvider 跨应用数据共享 IPC的一种
BroadcastReceiver 静态注册+动态注册(register+unregister)
四大组件的生命周期和简单用法 Activity:
protected void onCreate(Bundle savedInstanceState); protected void onStart(); protected void onRestart(); protected void onResume(); protected void onPause(); protected void onStop(); protected void onDestroy();
Service:
startService():
stopService():
bindService():
unbindService():
startService()
+bindService():
ContentProvider:
ContentProvider只有一个onCreate()生命周期方法且只会被调用一次,当其他应用通过ContentResolver第一次访问该ContentProvider时,onCreate()方法将会被回调 BroadcastReceiver:</br> 动态注册与注册的context同周期</br> 静态注册 onReceive()后被注销
Activity之间的通信方式
- Intent
- SharedPreference\写入本地文件
- SQLite 各种orm
- 静态变量
- ......
Activity各种情况下的生命周期 https://www.jianshu.com/p/e46d449467d5
横竖屏切换的时候,Activity 各种情况下的生命周期 1.不设置 Activity 的 android:configChanges 时,切屏会重新调用各个生命周期默认首先销毁 当前 activity,然后重新加载。
2.设置 Activity android:configChanges="orientation|keyboardHidden|screenSize"时,切 屏不会重新调用各个生命周期,只会执行
onConfigurationChanged
方法。直接设置屏幕方向可以免去这个问题。Activity与Fragment之间生命周期比较
Activity上有Dialog的时候按Home键时的生命周期 按Home:onPause() onStop() 返回App:onRestart() onStart() onResume() dialog出现与否不影响生命周期 尝试时使用的是AlertDialog
两个Activity 之间跳转时必然会执行的是哪几个方法? A跳转B:A的onPause() 若A不可见 onStop() B的onCreate(),onStart(),onResume()
前台切换到后台,然后再回到前台,Activity生命周期回调方法。弹出Dialog,生命值周期回调方法 按Home:onPause() onStop() 返回App:onRestart() onStart() onResume() dialog出现与否不影响生命周期 尝试时使用的是AlertDialog
Activity的四种启动模式对比
- standard 标准模式,每次都新建一个实例对象
- singleTop 如果在任务栈顶发现了相同的实例则重用,且调用原来实例的onNewIntent()方法,否则新建并压入栈顶
- singleTask 如果在任务栈中发现了相同的实例,将其上面的任务终止并移除,重用该实例,且调用原来实例的onNewIntent()方法。否则新建实例并入栈
- singleInstance 在新任务栈中开启,许不同应用,进程线程等共用一个实例,无论从何应用调用该实例都重用,且调用原来实例的onNewIntent()方法
Activity状态保存与恢复
onSaveInstanceState():
屏幕旋转重建会调用onSaveInstanceState()
启动另一个activity当前activity在离开前会调用onSaveInstanceState()
按Home键的情形和启动另一个activity一样, 当前activity在离开前会onSaveInstanceState()
onRestoreInstanceState():
屏幕旋转重建会调用onRestoreInstanceState()
启动另一个activity,返回时如果因为被系统杀死需要重建, 则会从onCreate()重新开始生命周期, 调用onRestoreInstanceState()
按Home键的情形和启动另一个activity一样,用户再次点击应用图标返回时, 如果重建发生, 则会调用onCreate()和onRestoreInstanceState()
屏幕旋转: Home:
Fragment各种情况下的生命周期 hide show:
onHiddenChanged()
viewpager:setUserVisibleHint()
Fragment之间传递数据的方式?
- 拿到fragment对象,直接传参
- 接口回调
- 事件分发
- ......
Activity 怎么和Service 绑定? bindService 方法启动服务, 其它组件可以通过回调获取服务的代理对象和服务交互, 而这两方也进行了绑定, 当启动方销毁时, 服务也会自动进行 unBind 操作, 当发现所有绑定都进行了 unBind 时才会销毁服务.
怎么在Activity中启动自己对应的Service? 在 Activity 中可以通过 startService 和 bindService 方法启动 Service。
service和activity怎么进行数据交互?
- bindService 通过Binder得到Service对象
- 广播
- ......
Service的开启方式 startService && bindService
请描述一下Service 的生命周期
![](https://ws1.sinaimg.cn/large/006tNc79gy1fow5qlw0owj317w05g755.jpg)
谈谈你对ContentProvider的理解 ContentProvider一般为存储和获取数据提供统一的接口,提供了对底层数据存储方式的抽象,可以在不同的应用程序之间共享数据。
说说ContentProvider、ContentResolver、ContentObserver 之间的关系
- ContentProvider:应用提供
- ContentResolver:统一管理与不同ContentProvider间的操作,通过统一ContentResolver访问不同ContentProvider
- ContentObserver 内容监听器, 可以监听数据的改变状态
请描述一下广播BroadcastReceiver的理解
广播的分类 广播接受器,通过action进行匹配,任意一个action匹配则成功。一个广播接收器可以接收多个广播发出者发出的广播,一个广播发出者也可以得到多个广播接收器的回应。 动态注册,多次注册同一个广播只有一个实例,必须显示unregister 静态注册,onReceive后会自己注销,所以每次接收时自动生成一个新的实例 广播底层由Binder实现
广播使用的方式和场景 广播的发送者和接收者事先是不需要知道对方的存在的,这样带来的好处便是,系统的各个组件可以松耦合地组织在一起 监听系统广播 (区别EventBus) 进程间通信 (区别EventBus)
本地广播和全局广播有什么差别? Broadcast是针对应用间、应用与系统间、应用内部进行通信的一种方式 广播底层Binder LocalBroadcast比较轻量,由handler实现,仅在自己的应用内发送接收广播,也就是只有自己的应用能收到,数据更加安全广播只在这个程序里,而且效率更高。
BroadcastReceiver,LocalBroadcastReceiver 区别 LocalBroadcastReceiver不能静态注册,只能采用动态注册的方式。 在发送和注册的时候采用,LocalBroadcastManager的sendBroadcast方法和registerReceiver方法
AlertDialog,popupWindow,DialogFragment区别 AlertDialog是非阻塞线程的,Popupwindow是阻塞线程的。 DialogFragment fragmentManager会自动管理DialogFragment的生命周期.
Application 和 Activity 的 Context 对象的区别 生命周期长短,使用Activity的Context持有某些静态引用会引起内存泄漏
数字1:启动Activity在这些类中是可以的,但是需要创建一个新的task。一般情况不推荐。
数字2:在这些类中去layout inflate是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。
数字3:在receiver为null时允许,在4.2或以上的版本中,用于获取黏性广播的当前值。(可以无视)
Android属性动画特性 与视图动画相对,3.0 API11后出现,可用于任何对象上,在改变视图的同时改变属性,为了兼容可使用开源库
ValueAnimator ObjectAnimator
如何导入外部数据库? http://blog.csdn.net/jing__jie/article/details/51602587
LinearLayout、RelativeLayout、FrameLayout的特性及对比,并介绍使用场景
FrameLayout: 没有过多的测量 所有子view默认排放左上角
LinearLayout:线性布局,支持weight,会测量两次
RelativeLayout:相对布局,两次测量 没有嵌套的情况下,LinearLayout,FrameLayout性能优于RelativeLayout,RelativeLayout的功能比较复杂,它的布局过程需要花费更多的CPU时间
布局调优工具*
hierarchy viewer
lint
Android Device Monitor (左上方截图功能的右边,dump)
谈谈对接口与回调的理解 可以用于实现观察者模式、事件监听
介绍下SurfaceView
1 . View适用于主动更新的情况,而SurfaceView则适用于被动更新的情况,比如频繁刷新界面。
2 . View在主线程中对页面进行刷新,而SurfaceView则开启一个子线程来对页面进行刷新。
3 . View在绘图时没有实现双缓冲机制,SurfaceView在底层机制中就实现了双缓冲机制。(双缓冲技术是游戏开发中的一个重要的技术。当一个动画争先显示时,程序又在改变它,前面还没有显示完,程序又请求重新绘制,这样屏幕就会不停地闪烁。而双缓冲技术是把要处理的图片在内存中处理好之后,再将其显示在屏幕上。双缓冲主要是为了解决 反复局部刷屏带来的闪烁。把要画的东西先画到一个内存区域里,然后整体的一次性画出来。) SurfaceView 详解
RecyclerView的使用 略
序列化的作用,以及Android两种序列化的区别 序列化:对象 --> 字节流 反序列化:字节流-->对象
- Serializable Java 序列化接口 IO读写存储在硬盘上 利于数据持久化
- Parcelable Android 序列化接口 效率高 在内存中读写 使用麻烦 (AS有相关插件 一键生成所需方法)
差值器(TimeInterpolator) 估值器(TypeEvaluator) 属性动画中使用 用于实现非线性动画 TimeInterpolator:根据时间流逝的百分比计算出当前属性值改变的百分比。 TypeEvaluator:根据当前属性改变的百分比来计算改变后的属性值。 具体可参考扔物线的教程
Android中数据存储方式 SOLite SharedPreferences
(二)Android源码相关分析
Android动画框架实现原理
Android各个版本API的区别
Requestlayout,onlayout,onDraw,DrawChild区别与联系
RequestLayout()方法 :责任链模式 子View调用requestLayout方法,会标记当前View及父容器,同时逐层向上提交,直到ViewRootImpl处理该事件,ViewRootImpl会调用三大流程,从measure开始,对于每一个含有标记位的view及其子View都会进行测量、布局、绘制。 requestLayout如果没有改变l,t,r,b,那就不会触发onDraw;但是如果这次刷新是在动画里,mDirty非空,就会导致onDraw。
invalidate() 只执行自身draw方法
onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局)
调用onDraw()方法绘制视图本身
drawChild()去重新回调每个子视图的draw()方法
invalidate和postInvalidate的区别及使用
invalidate:在ui线程刷新view postInvalidate:在工作线程刷新view(底层还是handler)
Activity-Window-View三者的差别
在Activity中调用attach,创建了一个Window
创建的window是其子类PhoneWindow,在attach中创建PhoneWindow
在Activity中调用setContentView(R.layout.xxx)实际上是调用的getWindow().setContentView()
调用PhoneWindow中的setContentView方法
创建ParentView: 作为ViewGroup的子类,实际是创建的DecorView(作为FramLayout的子类)
将指定的R.layout.xxx进行填充 通过布局填充器进行填充【其中的parent指的就是DecorView】
调用到ViewGroup
调用ViewGroup的removeAllView(),先将所有的view移除掉
添加新的view:addView()
谈谈对Volley的理解
如何优化自定义View
降低刷新频率,减少不必要的调用invalidate()方法,尽量调用多参invalidate(),能做到局部刷新而不是整体刷新
使用好硬件加速(targetpi>=11)
初始化时创建对象;不要在onDraw方法内创建绘制对象
做好状态的储存与恢复(onsaveinstance onrestoreinstance)
低版本SDK如何实现高版本api?
在代码中加入版本控制逻辑,添加@targetapi使编译器通过
描述一次网络请求的流程
域名解析、TCP的三次握手、建立TCP连接后发起HTTP请求、服务器响应HTTP请求、浏览器解析html代码,同时请求html代码中的资源(如js、css、图片等)、最后浏览器对页面进行渲染并呈现给用户
HTTP请求格式:
请求行: 请求方法(GET/POST/DELETE/PUT/HEAD)、URI路径、HTTP版本号
请求头: 缓存相关信息(Cache-Control,If-Modified-Since) 客户端身份信息(User-Agent)等键值对信息
消息体: 客户端发给服务端的请求数据,这部分数据并不是每个请求必须的(post put)
HTTP响应格式:
状态行:有HTTP协议版本号,状态码和状态说明三部分构成。
响应头:用于说明数据的一些信息,比如数据类型、内容长度等键值对。
消息体:服务端返回给客户端的HTML文本内容。或者其他格式的数据,比如:视频流、图片或者音频数据。
HttpUrlConnection 和 okhttp关系
从Android4.4开始HttpURLConnection的底层实现采用的是okHttp
Bitmap对象的理解
Bitmap对象的内存分为两部分:
Bitmap对象
Bitmap像素数据
占用内存大,多对象易引发OOM
Bitmap占用内存计算 = 图片最终显示出来的宽 * 图片最终显示出来的高 * 图片品质(Bitmap.Config的值)
减少内存消耗:根据View的大小利用BitmapFactory.Options计算合适的inSimpleSize(>1)来对Bitmap进行相对应的裁剪
Bitmap的优化主要是加快图片的加载速度和降低图片占用内存的大小
looper架构
封装消息循环和消息队列的一个类
(1) Looper类用来为一个线程开启一个消息循环。 默认情况下android中新诞生的线程是没有开启消息循环的。(主线程除外,主线程系统会自动为其创建Looper对象,开启消息循环。) Looper对象通过MessageQueue来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。
(2) 通常是通过Handler对象来与Looper进行交互的。Handler可看做是Looper的一个接口,用来向指定的Looper发送消息及定义处理方法。
ActivityThread,AMS,WMS的工作原理
AMS和WMS都属于Android中的系统服务,被所有的App公用的
AMS(ActivityManagerServices)统一调度所有应用程序的Activity,负责系统中所有Activity的生命周期。 WMS(WindowManagerService)控制所有Window的显示与隐藏以及要显示的位置
自定义View如何考虑机型适配
合理使用warp_content,match_parent. 尽可能的是使用RelativeLayout 针对不同的机型,使用不同的布局文件放在对应的目录下,android会自动匹配。 尽量使用点9图片。 使用与密度无关的像素单位dp,sp 引入android的百分比布局。 切图的时候切大分辨率的图,应用到布局当中。在小分辨率的手机上也会有很好的显示效果。
自定义View的事件
触摸&&绘制
AsyncTask+HttpClient 与 AsyncHttpClient有什么区别?
LaunchMode应用场景
sinngleTop:防抖 singleTask:入口Activity singleInstance:单实例,多应用公用,不常见
AsyncTask 如何使用?
AsyncTask是Android提供的一个助手类,它对Thread和Handler进行了封装,方便我们使用,asyncTask.execute() 只能在UI主线程中调用,多任务时不并发,线程池
AsyncTask有四个重要的回调方法,分别是:onPreExecute(主线程)、doInBackground(工作线程), onProgressUpdate(主线程,在doInBackground中手动调publishProgress) 和 onPostExecute(主线程 执行完毕)。
局限性:
- 在Android 4.1版本之前,AsyncTask类必须在主线程中加载,这意味着对AsyncTask类的第一次访问必须发生在主线程中;在Android 4.1以及以上版本则不存在这一限制,因为ActivityThread(代表了主线程)的main方法中会自动加载AsyncTask
- AsyncTask对象必须在主线程中创建
- AsyncTask对象的execute方法必须在主线程中调用
- 一个AsyncTask对象只能调用一次execute方法
SparseArray原理
当使用HashMap(K, V),如果K为整数类型时,使用SparseArray的效率更高
mKeys = new int[initialCapacity];
mValues = new Object[initialCapacity];
key value 分别为一个数组 key是有序插入的 查找key时使用二分查找法,降低了时间复杂度(O(log2n)),根据找到的key的下标取value
请介绍下ContentProvider 是如何实现数据共享的?
统一了数据访问方式
Android Service与Activity之间通信的几种方式
- bindService 通过Binder得到Service对象
- 广播
- ......
IntentService原理及作用是什么?
IntentService保留了Service原有的特性,并且将耗时的操作都放在的子线程中,通过Handler的回调方式实现了数据的返回。
IntentService继承Service,专门处理异步请求。
客户端通过调用startService(Intent)发起请求,自然数据是通过Intent进行传递的。
一旦Service启动后,对于Intent所传递的数据操作都通过工作线程(worker thread)进行处理。
在完成数据的处理之后,Handler会回调其处理结果。在任务结束后会将自身服务关闭。
通过Handler、Message、Looper在Service中实现的异步线程消息处理的机制。但是由于是通过衍生Service的方式实现的,因此具有Service的生命周期特性。
说说Activity、Intent、Service 是什么关系
Activity Service 四大组件,通过Intent传递消息
ApplicationContext和ActivityContext的区别
生命周期长短,使用Activity的Context持有某些静态引用会引起内存泄漏
和UI相关的方法基本都不建议或者不可使用Application
数字1:启动Activity在这些类中是可以的,但是需要创建一个新的task。一般情况不推荐。
数字2:在这些类中去layout inflate是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。
数字3:在receiver为null时允许,在4.2或以上的版本中,用于获取黏性广播的当前值。(可以无视)
Context数量 = Activity数量 + Service数量 + 1
SP是进程同步的吗?有什么方法做到同步?
不是 使用 contentprovider
谈谈多线程在Android中的使用
避免ANR(UI线程5s 广播10s 服务20s) 防止耗时操作拥堵线程 完成持续性长的耗时操作
thread && runnable:直接继承Thread和实现Runnable接口实现多线程区别 众所周知在Java中类仅支持单继承,当定义一个新的类的时候,它只能扩展一个外部类。假如创建自定义线程类的时候是通过扩展 Thread类的方法来实现的,那么这个自定义类就不能再去扩展其他的类。因此,如果自定义类必须扩展其他的类,那么就可以使用实现Runnable接口的方法来定义该类为线程类,这样就可以避免Java单继承所带来的局限性。但继承Thread和实现Runnable重要区别并不是在于此,更重要的是实现Runnable接口的方式创建的线程可以处理同一资源,从而实现资源的共享。 实现Runnable接口相对于扩展Thread类来说,具有无可比拟的优势。此方式不仅有助于程序的健壮性,使代码能够被多个线程共享,而且代码和数据资源相对独立,从而特别适合多个具有相同代码的线程去处理同一资源的情况。使得线程、代码和数据资源三者有效分离,很好地体现了面向对象程序设计的思想。因此,几乎所有的多线程程序都是通过实现Runnable接口的方式来完成的。
asynctask
intentservice
进程和 Application 的生命周期
进程重要级:前台(foreground)>可视(visible)>服务(service)>背景(background)>空(cache)
application生命周期:
public class App extends Application {
@Override
public void onCreate() {
// 程序创建的时候执行
Log.d(TAG, "onCreate");
super.onCreate();
}
@Override
public void onTerminate() {
// 程序终止的时候执行
Log.d(TAG, "onTerminate");
super.onTerminate();
}
@Override
public void onLowMemory() {
// 低内存的时候执行
Log.d(TAG, "onLowMemory");
super.onLowMemory();
}
@Override
public void onTrimMemory(int level) {
// 程序在内存清理的时候执行
Log.d(TAG, "onTrimMemory");
super.onTrimMemory(level);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
Log.d(TAG, "onConfigurationChanged");
super.onConfigurationChanged(newConfig);
}
}
封装View的时候怎么知道view的大小
调用view宽高时期view.getMeasuredWidth/Height(确保view已经测量完毕):
Activity/View#onWindowFocusChanged
它会被调用多次,当 Activity的窗口得到焦点和失去焦点均会被调用view.post(runnable)
通过post将一个runnable投递到消息队列的尾部,当Looper调用此 runnable的时候,View也初始化好了。ViewTreeObserver.addOnGlobalLayoutListener
RecyclerView原理
AndroidManifest的作用与理解
(三)常见的一些原理性问题
Handler机制和底层实现
在消息接收的线程初始化handler实例,若接收消息的线程非主线程,需要开启looper,主线程默认开启looper,一个线程只有一个looper与一个MessageQueue,可以拥有多个handler。 一个Message经由Handler的发送,MessageQueue的入队,Looper的抽取,又再一次地回到Handler的怀抱
Handler、Thread和HandlerThread的差别
handler:线程间通信 thread:线程 HandlerThread:内部拥有并管理一个looper的thread,减少了开发者的工作量
handler发消息给子线程,looper怎么启动?
手动调用 Looper.prepare():
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
Looper.loop();
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
ThreadLocal原理,实现及如何保证Local属性?
ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名ThreadLocalVariable更容易让人理解一些。 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 可以用于维护单例中的非线程安全对象,从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
实现:在ThreadLocal类中有一个table,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本
请解释下在单线程模型中Message、Handler、Message Queue、Looper之间的关系
在开发Android 应用时必须遵守单线程模型的原则:
不要阻塞UI线程
确保只在UI线程中访问Android UI工具包
关系见上
事件分发中的onTouch 和onTouchEvent 有什么区别,又该如何使用?
事件处理优先级:onTouchListener(若返回true则不向下调用)> onTouchEvent > onClickListener
View刷新机制
View绘制流程
measure->layout->draw onMeasure执行:
forceLayout为true:这表示强制重新布局,可以通过View.requestLayout()来实现;
needsLayout为true,这需要specChanged为true(表示本次传入的MeasureSpec与上次传入的不同),并且以下三个条件之一成立:
- sAlwaysRemeasureExactly为true: 该变量默认为false;
- isSpecExactly为false: 若父View对子View提出了精确的宽高约束,则该变量为true,否则为false
- matchesSpecSize为false: 表示父View的宽高尺寸要求与上次测量的结果不同
如何取消AsyncTask?
cancel方法 --> isCancelled()为true --> 在doInBackground中手动检查决定是否继续运行
我们可以随时调用 cancel(boolean)去取消这个加载任务,调用这个方法会间接调用 iscancelled 并且返回true 。 调用cancel()后,在doInBackground()return后 我们将会调用onCancelled(Object) 不在调用onPostExecute(Object) 为了保证任务更快取消掉,你应该在doInBackground()周期性的检查iscancelled 去进行判断。
为什么不能在子线程更新UI?
Exception:Only the original thread that created a view hierarchy can touch its views
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
在Activity创建完成后(Activity的onResume之前ViewRootImpl实例没有建立),mThread被赋值为主线程(ViewRootImpl),所以直接在onCreate中创建子线程是可以更新UI的
在子线程中添加 Window,并且创建 ViewRootImpl,可以在子线程中更新view
设计思考: Android 的单线程模型,因为如果支持多线程修改 View 的话,由此产生的线程同步和线程安全问题将是非常繁琐的,所以 Android 直接就定死了,View 的操作必须在创建它的 UI 线程,从而简化了系统设计。 有没有可以在其他非原始线程更新 ui 的情况呢?有,SurfaceView 就可以在其他线程更新。
- ANR产生的原因是什么?
来源:github
链接:https://github.com/oo1993448102/CommonDevKnowledge
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
交流请添加微信: qian-qianyu