范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

中级Android研发,面试一般都问些什么?

  作者:小肥羊冲冲冲
  主要收集在面试过程中普遍问到的基础知识(面试收集 主要来自于bilibili 嵩恒 蚂蚁金服等互联网公司)。  一、启动
  启动模式
  1、standard 标准模式。
  2、singleTop 栈顶复用模式 。(例如:推送点击消息界面)
  3、singleTask 栈内复用模式 。(例如:首页)
  4、singleInstance 单例模式 。(单独位于一个任务栈中,例如:拨打电话界面)
  App启动流程
  启动流程
  app启动交互逻辑
  在Android 层 第一步就是 fork Zygote 进程  创建服务端Socket,为后续创建进程通信做准备 加载虚拟机 fork了System Server进程,负责启动和管理Java Framework层,包括ActivityManagerService,PackageManagerService,WindowManagerService、binder线程池等
  还有一个经典的问题 :Activity启动流程中,大部分都是用Binder通讯,为啥跟Zygote通信的时候要用socket呢?  ServiceManager (初始化binder线程池的地方)不能保证在zygote起来的时候已经初始化好,所以无法使用Binder。  Binder工作依赖于多线程,但是fork的时候是不允许存在多线程的,多线程情况下进程fork容易造成死锁,所以就不用Binder了。
  Binder直观的说,Binder是一个类,实现了IBinder接口。
  从IPC(进程间通信)角度来说,Binder是Android中一种跨进程通信方式。
  还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder。
  从Android FrameWork角度来说,Binder是ServiceManager连接各种Manager(ActivityManager,WindowManager等等)和响应ManagerService的桥梁。
  从Android应用层来说,Binder是客户端和服务端进行通信的媒介。
  以AIDL为例子,客户端在请求服务端通信的时候,并不是直接和服务端的某个对象联系,而是用到了服务端的一个代理对象,通过对这个代理对象操作,然后代理类会把方法对应的code、传输的序列化数据、需要返回的序列化数据交给底层,也就是Binder驱动。然后Binder驱动把对应的数据交给服务器端,等结果计算好之后,再由Binder驱动把数据返回给客户端。
  ServiceManager
  ServiceManager其实是为了管理系统服务而设置的一种机制,每个服务注册在ServiceManager中,由ServiceManager统一管理,我们可以通过服务名在ServiceManager中查询对应的服务代理,从而完成调用系统服务的功能。所以ServiceManager有点类似于DNS,可以把服务名称和具体的服务记录在案,供客户端来查找。
  ServiceManager本身也运行在一个单独的线程,本身也是一个服务端,客户端其实是先通过跨进程获取到ServiceManager的代理对象,然后通过ServiceManager代理对象再去找到对应的服务。所以每个APP程序都可以通过binder机制在自己的进程空间中创建一个ServiceManager代理对象。
  所以通过ServiceManager查找系统服务并调用方法的过程是进行了两次跨进程通信。
  序列化Serializable :Java 序列化方式,适用于存储和网络传输,serialVersionUID 用于确定反序列化和类版本是否一致,不一致时反序列化会失败。 Parcelable :Android 序列化方式,适用于组件通信数据传递,性能高。
  java中的序列化方式Serializable效率比较低,主要有以下原因: Serializable在序列化过程中会创建大量的临时变量,这样就会造成大量的GC。 Serializable使用了大量反射,而反射操作耗时。 Serializable使用了大量的IO操作,也影响了耗时。
  所以Android就像重新设计了IPC方式Binder一样,重新设计了一种序列化方式,结合Binder的方式,对上述三点进行了优化,一定程度上提高了序列化和反序列化的效率。
  二、进程
  IPC 进程通讯方式Intent 、Bundle :要求传递数据能被序列化,实现 Parcelable、Serializable ,适用于四大组件通信。 文件共享 :适用于交换简单的数据实时性不高的场景。 AIDL:AIDL 接口实质上是系统提供给我们可以方便实现 BInder 的工具。 Messenger:基于 AIDL 实现,服务端串行处理,主要用于传递消息,适用于低并发一对多通信。 ContentProvider:基于 Binder 实现,适用于一对多进程间数据共享。(通讯录 短信 等) Socket:TCP、UDP,适用于网络数据交换
  进程保活
  进程被杀原因: 切到后台内存不足时被杀; 切到后台厂商省电机制杀死; 用户主动清理。
  保活方式: Activity 提权:挂一个 1像素 Activity 将进程优先级提高到前台进程。 Service 提权:启动一个前台服务。(API>18会有正在运行通知栏) 广播拉活 。(监听 开机 等系统广播) Service 拉活。 JobScheduler 定时任务拉活 。(android 高版本不行) 双进程拉活。 监听其他大厂广播。(tx baidu 全家桶互相拉) 三、Hook
  Hook 的选择点:静态变量和单例,因为一旦创建对象,它们不容易变化,非常容易定位。
  Hook 过程: 寻找 Hook 点,原则是静态变量或者单例对象,尽量 Hook public 的对象和方法。 选择合适的代理方式,如果是接口可以用动态代理。 偷梁换柱——用代理对象替换原始对象。
  多数插件化 也使用的 Hook技术
  四、内存泄漏构造单例的时候尽量别用Activity的引用; 静态引用时注意应用对象的置空或者少用静态引用; 使用静态内部类+软引用代替非静态内部类; 及时取消广播或者观察者注册;耗时任务、属性动画在Activity销毁时记得cancel; 文件流、Cursor等资源及时关闭; Activity销毁时WebView的移除和销毁。 五、View
  Window WindowManager WMSWindow :抽象类 不是实际存在的,而是以 View 的形式存在,通过 PhoneWindow 实现。(PhoneWindow = DecorView = Title + ContentView) WindowManager:外界访问 Window 的入口 管理Window 中的View , 内部通过 Binder 与 WMS IPC 进程交互。 WMS:管理窗口 Surface 的布局和次序,作为系统级服务单独运行在一个进程。 SurfaceFlinger:将 WMS 维护的窗口按一定次序混合后显示到屏幕上。
  View 工作流程
  通过 SetContentView(),调用 到PhoneWindow ,后实例DecorView ,通过 LoadXmlResourceParser() 进行IO操作 解析xml文件 通过反射 创建出View,并将View绘制在 DecorView上,这里的绘制则交给了ViewRootImpl 来完成,通过performTraversals() 触发绘制流程,performMeasure 方法获取View的尺寸,performLayout 方法获取View的位置 ,然后通过 performDraw 方法遍历View 进行绘制。
  View.post
  想要在 onCreate 中获取到View宽高的方法有: ViewTreeObserver 监听界面绘制事件,在layout时调用,使用完毕后记得removeListener。 就是View.post
  源码分析: public boolean post(Runnable action) {      final AttachInfo attachInfo = mAttachInfo;     if (attachInfo != null) {         return attachInfo.mHandler.post(action);     }     getRunQueue().post(action);     return true; }
  View.post(runable) 通过将runable 封装为HandlerAction对象,如果attachInfo为null 则将Runnable事件 添加到等待数组中, attachInfo初始化是在 dispatchAttachedToWindow 方法,置空则是在detachedFromWindow方法中,所以在这两个方法生命周期中,调用View.post方法都是直接让 mAttachInfo.handler 执行。 ViewRootImpl.class   mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,                 context);  final ViewRootHandler mHandler = new ViewRootHandler();
  通过查找 mAttachInfo.handler 是在主线程中声明的,没有传参则 Looper 为主线程Looper,所以在View.post中可以更新UI。
  但是为什么可以在View.post()中获取控件尺寸呢?
  android 运行是消息驱动,通过源码 可以看到 ViewRootImpl 中 是先将 TraversalRunnable添加到 Handler 中运行的 之后 才是 View.post()。 ViewRootImpl.class   final class TraversalRunnable implements Runnable {     @Override     public void run() {         doTraversal();     } }  void doTraversal() {     if (mTraversalScheduled) {         mTraversalScheduled = false;         mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);         // 该方法之后才有 view.post()          performTraversals();         ...     } }
  因此,这个时候Handler正在执行着TraversalRunnable这个Runnable,而我们post的Runnable要等待TraversalRunnable执行完才会去执行,而TraversalRunnable这里面又会进行measure,layout和draw流程,所以等到执行我们的Runnable时,此时的View就已经被measure过了,所以获取到的宽高就是measure过后的宽高。 六、动画
  帧动画 :AnimationDrawable 实现,在资源文件中存放多张图片,占用内存多,容易OOM。
  补间动画 :作用对象只限于 View 视觉改变,并没有改变View 的 xy 坐标,支持 平移、缩放、旋转、透明度,但是移动后,响应时间的位置还在 原处,补间动画在执行的时候,直接导致了 View 执行 onDraw() 方法。补间动画的核心本质就是在一定的持续时间内,不断改变 Matrix 变换,并且不断刷新的过程。
  属性动画 :ObjectAnimator、ValuetAnimator、AnimatorSet 可以是任何View,动画选择也比较多,其中包含 差速器,可以控制动画速度,节奏。类型估值器 可以根据当前属性改变的百分比计算改变后的属性值 。因为ViewGroup 在 getTransformedMotionEvent方法中通过子 View 的 hasIdentityMatrix() 来判断子 View 是否经过位移之类的属性动画。调用子 View 的 getInverseMatrix() 做「反平移」操作,然后判断处理后的触摸点是否在子 View 的边界范围内。
  提升动画 可以打开 硬件加速,使GPU 承担一部分CPU的工作。 七、Android 进程通讯方式
  a、bundle :由于Activity,Service,Receiver都是可以通过Intent来携带Bundle传输数据的,所以我们可以在一个进程中通过Intent将携带数据的Bundle发送到另一个进程的组件。(bundle只能传递三种类型,一是键值对的形式,二是键为String类型,三是值为Parcelable类型)
  b、ContentProvider :ContentProvider是Android四大组件之一,以表格的方式来储存数据,提供给外界,即Content Provider可以跨进程访问其他应用程序中的数据。
  c、文件 :两个进程可以到同一个文件去交换数据,我们不仅可以保存文本文件,还可以将对象持久化到文件,从另一个文件恢复。要注意的是,当并发读/写时可能会出现并发的问题。
  d、Broadcast :Broadcast可以向android系统中所有应用程序发送广播,而需要跨进程通讯的应用程序可以监听这些广播。
  e、AIDL :AIDL通过定义服务端暴露的接口,以提供给客户端来调用,AIDL使服务器可以并行处理。
  f、Messager :Messenger封装了AIDL之后只能串行运行,所以Messenger一般用作消息传递。
  g、Socket 八、Android 线程通信
  Handler 和 AsyncTask (AsyncTask:异步任务,内部封装了Handler)
  Handler线程间通信
  作用:
  线程之间的消息通信
  流程:
  主线程默认实现了Looper (调用loop.prepare方法 向sThreadLocal中set一个新的looper对象, looper构造方法中又创建了MsgQueue) 手动创建Handler ,调用 sendMessage 或者 post (runable) 发送Message 到 msgQueue ,如果没有Msg 这添加到表头,有数据则判断when时间 循环next 放到合适的 msg的next 后。Looper.loop不断轮训Msg,将msg取出 并分发到Handler 或者 post提交的 Runable 中处理,并重置Msg 状态位。回到主线程中 重写 Handler 的 handlerMessage 回调的msg 进行主线程绘制逻辑。
  问题: Handler 同步屏障机制:通过发送异步消息,在msg.next 中会优先处理异步消息,达到优先级的作用。 Looper.loop 为什么不会卡死:为了app不挂掉,就要保证主线程一直运行存在,使用死循环代码阻塞在msgQueue.next()中的nativePollOnce()方法里 ,主线程就会挂起休眠释放cpu,线程就不会退出。Looper死循环之前,在ActivityThread.main()中就会创建一个 Binder 线程(ApplicationThread),接收系统服务AMS发送来的事件。当系统有消息产生(其实系统每 16ms 会发送一个刷新 UI 消息唤醒)会通过epoll机制 向pipe管道写端写入数据 就会发送消息给 looper 接收到消息后处理事件,保证主线程的一直存活。只有在主线程中处理超时才会让app崩溃 也就是ANR。 Messaage复用:将使用完的Message清除附带的数据后, 添加到复用池中 ,当我们需要使用它时,直接在复用池中取出对象使用,而不需要重新new创建对象。复用池本质还是Message 为node 的单链表结构。所以推荐使用Message.obation获取 对象。 九、Android 和WebView 通信
  js调用android // myObj 为在js中使用的对象名称 JavaScriptInterfaces 是我们自定义的一个类。  webView.addJavascriptInterface(new JavaScriptInterfaces(), "myObj");  myObj.xx() //js方法
  android 调用js
  无参数: mWebView.loadUrl("javascript:wave()");
  有参数: webView.evaluateJavascript(String.format("javascript:callH5Re("测试数据")"), new ValueCallback() {          @Override         public void onReceiveValue(String value) {             Log.e("Test", "onReceiveValue: "+value );         } });十、app优化 (项目中处理的一些难点)
  主要分为 启动优化,布局优化 ,打包优化 等。
  启动优化闪屏页 优化,设置theme 默认欢迎背景。 懒加载 第三方库,不要都放在application 中初始化。 如果项目中有 webview ,可以提前在app空闲时间加载 webview 的内核,如果多处使用 可以创建缓存池,缓存webview。 如果android 5.0- 在applicaton 的 attchbaseContext() 中加载MultiDex.install 会更加耗时,可以采用 子线程(子线程加载 需要担心ANR 和ContentProvider 未加载报错的问题)或者单独开一个进程B,进程B开启子线程运行MultiDex.install ,让applicaton 进入while 循环等待B进程加载结果。
  MultiDex 优化,apk打包分为 android 5.0 + 使用 ART虚拟机 不用担心
  布局UI优化
  看过布局绘制源码流程后,可以知道 setContextView中 在ViewRootImpl 中使用 pull 的方法(这里可以扩展xml读取方式 SAX :逐行解析、dom:将整个文件加载到内存 然后解析,不推荐、pull:类似于 SAX 进行了android平台的优化,更加轻量级 方便)迭代读取 xml标签,然后对view 进行 measure,layout 和draw 的时候都存在耗时。通常优化方式有: 减少UI层级、使用merge、Viewstub标签 优化重复的布局。 优化 layout ,尽量多使用ConstraintLayout,因为 relalayout 和 linearlayout 比重的情况下都存在多次测量。 recyclerView 缓存 。( 可扩展 说明 rv的缓存原理 ) 比较极端的 将 measure 和 layout 放在子线程,在主线程进行draw。或者 子线程中 加载view 进行IO读取xml,通过Handler 回调主线程 加载view。(比如android 原生类 AsyncLayoutInflate ) 将xml直接通过 第三方工具(原理 APT 注解 翻译xml)直接将xml 转为 java代码。
  打包优化
  Analyze APK 后可以发现代码 和 资源其实是 app包的主要内存。
  1、res 文件夹下 分辨率下的图片 国内基本提供 xxhdpi 或者 xhdpi 即可,android 会分析手机分辨率到对应分辨率文件夹下加载资源。
  2、res中的 png 图片 都可以转为 webg 或者 svg格式的 ,如果不能转 则可以通过 png压缩在减少内存。
  3、通过在 build.gradle 中配置 minifyEnabled true(混淆)shrinkResources true 。(移除无用资源)
  4、Assests 中的 mp4 /3 可以在需要使用的时候从服务器上下载下来,字体文件 使用字体提取工具FontZip 删除不用的文字格式,毕竟几千个中文app中怎么可能都使用。
  5、lib 包如果 适配机型大多为高通 RAM ,可以单独引用abiFilters "armeabi-v7a"。
  6、build文件中 resConfigs "zh" 剔除掉 官方中或者第三方库中的 外国文字资源。
  十一、第三方库 源码总结
  LeakCanary 原理
  通过 registerActivityLifecycleCallbacks 监听Activity或者Fragment 销毁时候的生命周期。(如果不想那个对象被监控则通过 AndroidExcludedRefs 枚举,避免被检测) public void watch(Object watchedReference, String referenceName) {    if (this == DISABLED) {    return;   }   checkNotNull(watchedReference, "watchedReference");   checkNotNull(referenceName, "referenceName");   final long watchStartNanoTime = System.nanoTime();   String key = UUID.randomUUID().toString();   retainedKeys.add(key);   final KeyedWeakReference reference =      new KeyedWeakReference(watchedReference, key, referenceName, queue);    ensureGoneAsync(watchStartNanoTime, reference); }
  然后通过弱引用和引用队列监控对象是否被回收。(弱引用和引用队列ReferenceQueue联合使用时,如果弱引用持有的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。即 KeyedWeakReference持有的Activity对象如果被垃圾回收,该对象就会加入到引用队列queue) void waitForIdle(final Retryable retryable, final int failedAttempts) {    // This needs to be called from the main thread.   Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {     @Override public boolean queueIdle() {       postToBackgroundWithDelay(retryable, failedAttempts);       return false;     }   }); }
  IdleHandler,就是当主线程空闲的时候,如果设置了这个东西,就会执行它的queueIdle()方法,所以这个方法就是在onDestory以后,一旦主线程空闲了,就会执行一个延时五秒的子线程任务,任务:检测到未被回收则主动 gc ,然后继续监控,如果还是没有回收掉,就证明是内存泄漏了。通过抓取 dump文件,在使用 第三方 HAHA 库 分析文件,获取到到达泄露点最近的线路,通过 启动另一个进程的 DisplayLeakService 发送通知 进行消息的展示。
  OkHttp
  同步和异步 网络请求使用方法 // 同步get请求      OkHttpClient okHttpClient=new OkHttpClient();     final Request request=new Request.Builder().url("xxx").get().build();     final Call call = okHttpClient.newCall(request);     try {         Response response = call.execute();     } catch (IOException e) {     }   //异步get请求      OkHttpClient okHttpClient=new OkHttpClient();     final Request request=new Request.Builder().url("xxx").get().build();     final Call call = okHttpClient.newCall(request);     call.enqueue(new Callback() {         @Override         public void onFailure(Call call, IOException e) {         }         @Override         public void onResponse(Call call, Response response) throws IOException {         }     });   // 异步post 请求     OkHttpClient okHttpClient1 = new OkHttpClient();     RequestBody requestBody = new FormBody.Builder()             .add("xxx", "xxx").build();     Request request1 = new Request.Builder().url("xxx").post(requestBody).build();     okHttpClient1.newCall(request1).enqueue(new Callback() {         @Override         public void onFailure(Call call, IOException e) {         }         @Override         public void onResponse(Call call, Response response) throws IOException {         }     });
  同步请求流程:
  通过OkHttpClient new生成call实例 Realcall。
  Dispatcher.executed() 中 通过添加realcall到runningSyncCalls队列中。
  通过 getResponseWithInterceptorChain() 对request层层拦截,生成Response。
  通过Dispatcher.finished(),把call实例从队列中移除,返回最终的response。
  异步请求流程:
  生成一个AsyncCall(responseCallback)实例(实现了Runnable)。
  AsyncCall通过调用Dispatcher.enqueue(),并判断maxRequests (最大请求数)maxRequestsPerHost(最大host请求数)是否满足条件,如果满足就把AsyncCall添加到runningAsyncCalls中,并放入线程池中执行;如果条件不满足,就添加到等待就绪的异步队列,当那些满足的条件的执行时 ,在Dispatcher.finifshed(this)中的promoteCalls();方法中 对等待就绪的异步队列进行遍历,生成对应的AsyncCall实例,并添加到runningAsyncCalls中,最后放入到线程池中执行,一直到所有请求都结束。
  责任链模式 和 拦截器
  责任链:
  源码跟进 execute() 进入到 getResponseWithInterceptorChain() 方法。 Response getResponseWithInterceptorChain() throws IOException {  //责任链 模式     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, this, eventListener, client.connectTimeoutMillis(),         client.readTimeoutMillis(), client.writeTimeoutMillis());     return chain.proceed(originalRequest); }
  chain.proceed() 方法核心代码。每个拦截器 intercept()方法中的chain,都在上一个 chain实例的 chain.proceed()中被初始化,并传递了拦截器List与 index,调用interceptor.intercept(next),直接最后一个 chain实例执行即停止。 //递归循环下一个 拦截器      RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,         connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,         writeTimeout);     Interceptor interceptor = interceptors.get(index);     Response response = interceptor.intercept(next);//递归循环下一个 拦截器      RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,         connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,         writeTimeout);     Interceptor interceptor = interceptors.get(index);     Response response = interceptor.intercept(next);  @Override public Response intercept(Chain chain) throws IOException {      Request request = chain.request();     RealInterceptorChain realChain = (RealInterceptorChain) chain;     Call call = realChain.call();     EventListener eventListener = realChain.eventListener();     StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),         createAddress(request.url()), call, eventListener, callStackTrace);     while (true) {         ...         // 循环中 再次调用了 chain 对象中的 proceed 方法,达到递归循环。         response = realChain.proceed(request, streamAllocation, null, null);         releaseConnection = false;         ...     } }
  拦截器: RetryAndFollowUpInterceptor :重连并跟踪 拦截器。 BridgeInterceptor : 将用户请求构建为网络请求(hander cooker content-type 等) 并发起请求 。 CacheInterceptor :缓存拦截器 负责从缓存中返回响应和把网络请求响应写入缓存。 ConnectInterceptor :与服务端 建立连接,并且获得通向服务端的输入和输出流对象。
  OkHttp 流程 采用责任链方式的拦截器,实现分成处理网络请求,可更好的扩展自定义拦截器。(采用GZIP压缩,支持http缓存) 采用线程池(thread pool)和连接池(Socket pool)解决多并发问题,同时连接池支持多路复用。(http2才支持,可以让一个Socket同时发送多个网络请求,内部自动维持顺序.相比http只能一个一个发送,更能减少创建开销)) 底层采用socket和服务器进行连接.采用okio实现高效的io流读写。
  ButterKnife
  butterKnife 使用的是 APT 技术 也就是编译时注解,不同于运行时注解(在运行过程中通过反射动态地获取相关类,方法,参数等信息,效率低耗时等缺点),编译时注解 则是在代码编译过程中对注解进行处理(annotationProcessor技术),通过注解获取相关类,方法,参数等信息,然后在项目中生成代码,运行时调用,其实和直接手写代码一样,没有性能问题,只有编辑时效率问题。
  ButterKnife在Bind方法中 获取到DecorView,然后通过Activity和DecorView对象获取xx_ViewBinding类的构造对象,然后通过构造方法反射实例化了这个类 Constructor。
  在编写完demo之后,需要先build一下项目,之后可以在build/generated/source/apt/debug/包名/下面找到 对应的xx_ViewBinding类,查看bk 帮我们做的事情。 # xx_ViewBinding.java   @UiThread public ViewActivity_ViewBinding(ViewActivity target, View source) {   this.target = target;    target.view = Utils.findRequiredView(source, R.id.view, "field "view""); }  # Utils.java  public static View findRequiredView(View source, @IdRes int id, String who) {   View view = source.findViewById(id);   if (view != null) {     return view;   }   String name = getResourceEntryName(source, id);   throw new IllegalStateException("Required view ....") }
  通过上述上述代码 可以看到 注解也是帮我们完成了 findviewbyid 的工作。
  butterknife 实现流程 扫描Java代码中所有的ButterKnife注解。 发现注解, ButterKnifeProcessor会帮你生成一个Java类,名字<类名>$ViewBinding.java,这个新生成的类实现了Unbinder接口,类中的各个view 声明和添加事件都添加到Map中,遍历每个注解对应通过JavaPoet生成的代码。
  未来
  Gradle插件升级到5.0版本之后ButterKnife将无法再被使用,R文件中的 id将添加final标识符,虽然 jake大神通过生成R2文件的方式,尝试避开版本升级带来的影响。但是随着官方ViewBinding等技术的出现。
  我们这里说下 被final修饰的基础类型和String类型为什么不能被反射?
  答:由于JVM 内联优化的机制,编译器将指定的函数体插入并取代每一处调用该函数的地方(就是在方法编译前已经进行了赋值),从而节省了每次调用函数带来的额外时间开支。 最后
  在这里我分享一份由多位大佬亲自收录整理的Android学习PDF+架构视频+面试文档+源码笔记,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料
  这些都是我现在闲暇时还会反复翻阅的精品资料。里面对近几年的大厂面试高频知识点都有详细的讲解。相信可以有效地帮助大家掌握知识、理解原理,帮助大家在未来面试取得一份不错的答卷。
  当然,你也可以拿去查漏补缺,提升自身的竞争力。
  如果你有需要的话,只需私信我【进阶】即可获取

用科技成果造福世界优必选机器人亮相迪拜世博会中国馆来源读特日前开幕的迪拜世博会上,深圳本土企业优必选科技自主研发的熊猫机器人优悠和大型仿人服务机器人WalkerX正式亮相中国馆,向世界展示中国智造与科技创新的最新成果,以创新科技助六大助听器的品牌的差别是什么?六大助听器品牌的差别不是很大,各有各的亮点,最主要是适合自己听力,生活,工作环境的要求,对助听器的期望值,建议去门店试听效果。每个品牌的助听器都有其特点,不存在哪个品牌就比其它品牌扫地机器人竟已如此先进?科沃斯扫地机器人DEEBOTX1OMNI使用分享Hello,大家好,今天给大家分享一款超级强的扫地机器人,这款扫地机器人绝对称得上解放双手之利器,孝敬父母讨好岳母之神器,这就是科沃斯的第三代扫地机器人DEEBOTX1OMNI!先真实测评耳机和各品牌手机适配度相信大家都知道,在互联网信息时代,人们的日常生活,都是离不开智能手机的,平时网购点外卖,或者是网络社交时,都是需要手机的,而曾经购买手机的时候,往往有一些附赠品,包括了耳机钢化膜手现在做什么生意是趋势?有什么政策扶持?谢谢邀请,问题太大了,不好回答。两个问题看起来相关性很强,但没有必然的联系。建议把问题琢磨明白了,其实做啥生意也就明白了。可以做跨境电商,当地都设有自贸区,跨境电商利润相对国内来说黑名单警告?一文带你读懂各国最新加密监管政策摘要各国加密监管政策一览。加密市场自问世以来,一度经历过引爆流量震荡冷却拥抱过因2017年区块链技术带来的新纪元现又在2021年凭借着NFT链游等火爆赛道,掀起另一轮热潮。但随着市从骁龙855缠斗到骁龙888Plus时代,小米iQOO谁领先了?关注手机圈的朋友都知道,iQOO和小米之间的竞争是非常有意思的。记得当初小米9首发价为2999。没多久之后iQOO一代发布,首发价定在了2998。由于便宜了1元,某些程度上还解决了市场新宠?小米新手机产品怎么看?好销量意味着什么?手机市场小米在手机市场上的市场表现可以说是非常的不错的了,随着这两年的市场空缺,小米也是借机进入了高端手机市场。在如今的国内手机市场上安全,小米在市场上的份额排行虽然还不是很突出,闯入千亿校服市场,新人飒美特如何一年半内成为行业第二名?导语常熟,波司登集团最大的华东智能物流配送中心里,智能机器人秩序运转从运输包装,到捡货发货,自动化程度超过90。产能峰值,这里日均处理订单能力超100万件。波司登华东智能物流配送中国内市场,小米被荣耀反超,这是什么原因?上一个为了压住小米,让经销商大量压货的格力已经越来越没落了,荣耀也快了,经销商压货只能一时,时间长了会完蛋的小米除了印度人喜欢,还有谁喜欢?小米冬天用安逸,热和归根到底还是品质品控基于SSM框架的编程语言学习与检测系统开发工具(eclipseideavscode等)eclipseidea数据库(sqlitemysqlsqlserver等)mysql开发语言java(jdk1。8)功能模块(请用文
市场监管总局禁止虎牙与斗鱼合并,下架滴滴企业版等25款App国家网信办掌握超100万用户个人信息的运营者赴国外上市必须申报网络安全审查10日,国家互联网信息办公室发布关于网络安全审查办法(修订草案征求意见稿)公开征求意见的通知。办法提出,掌新房装修,家里WiFi怎么弄?现在,Wifi网络在家庭中要比有线网络更重要。目前,常用的Wifi网络搭建方案有两种,一种方法是无线路由器组网另外一种方法是无线AP组网。无论哪种组网方式,都需要在装修前确定好,因为什么有些WIFI不能用万能钥匙搜索到?怎么才能破解邻居家的WIFI密码?在探讨这个问题前,我们先了解一下Wifi万能钥匙为何能够破解别人密码。严格来说,诸如Wifi万能钥匙,还有Wifi共享精灵这类的App,并不具备破解别人Wifi密码的能力,而是偷偷realmeQ3Pro千元里的水桶机?还是智商检测机?近几年随着科技的发展,越来越多的交互服务,都集成在手机这个终端设备中,遥想数十年前,手机不过是通讯工具之一,甚至使用手机的成本极高。而如今手机已经成为了生活中必不可少的数码产品,大iPhone13售价基本确认,比iPhone12更香,果粉买定了iPhone13售价基本确认在下半年即将发布的几款手机中,苹果新一代的iPhone13无疑是最被期待的,因为历年的秋季发布会,苹果都会给广大果粉带来一些意想不到的惊喜。就比如说去年RealmePad被曝光!平板市场迎来新成员,但这尺寸太夸张今年对于一些手机品牌来说,似乎是重回平板市场的黄道吉日。其实这两年一直经营平板产品的品牌已经获得了大丰收,对于当前这种趋势,只能说现在还来得及。此前真我Realme品牌的笔记本平板阳光电源(300274)投资价值分析一阳光电源专注于太阳能风能储能电动汽车等新能源电源设备研发生产销售和服务。主导产品有光伏逆变器风能变流器储能系统新能源汽车驱动系统水面光伏浮体智慧能源运维服务等,并致力于提供全球一10bit屏3200万像素7nm,跌至1898元,全球首款屏下摄像重回底价如今很多用户对屏下摄像头手机已经翘首以待了,今年小米MIX4可能会采用屏下镜头方案,因为在去年小米就公布了第三代屏下摄像头技术,然而小米MIX4这次没能全球首发屏下镜头手机了,因为iPhone12坑果粉,aohi20w充电头来救场自从iPhone12不附赠充电头之后,国内很多厂家也跟着用环保这个借口来宣布不附赠充电头。说实话,这无疑是社会发展的倒退,即使家里的确有充电头。但是换了一个手机,自然不同的手机支持滴滴出啥事了,下架都不能用了?出事了,滴滴下架了!什么情况?我们经常出行的打车软件下架了?这瓜我得去瞧瞧,虽然我现在都在用共享单车,但说不定以后就用上滴滴打车了,我得关注下。听到这消息时候我还不太信?滴滴这是干任正非和雷军都在抢人,华为抛出百万年薪,雷军直接每人2000万任正非出生于贵州安顺地区镇宁县一个贫困山区的小村庄。他是华为技术有限公司的创始人。雷军于1969年12月16日出生湖北省仙桃市,是中国大陆著名天使投资人。他们两个都是我国知名的企业