以下五个问题本人面试的时候都被问到过,也作为面试官考察过别人,算是比较有代表性的题目。
Activity之间的通信方式
1)通过Intent方式传递参数跳转
2)通过广播方式
3)通过接口回调方式
4)借助类的静态变量或全局变量
5)借助SharedPreference或是外部存储,如数据库或本地文件
请介绍下 Android 的数据存储方式
使用 SharedPreferences 存储数据;文件存储数据;SQLite 数据库存储数据;使用 ContentProvider 存储数据;网络存储数据。
Preference,File, DataBase 这三种方式分别对应的目录是 /data/data/Package Name/Shared_Pref, /data/data/Package Name/files, /data/data/Package Name/database 。
- 使用 SharedPreferences 存储数据
首先说明 SharedPreferences 存储方式,它是 Android 提供的用来存储一些简单配置信息的一种机制,例如:登录用户的用户名与密码。其采用了 Map 数据结构来存储数据,以键值的方式存储,可以简单的读取与写入,具体实例如下:
void ReadSharedPreferences(){String strName,strPassword;SharedPreferences user = getSharedPreferences(“user_info”,0);strName = user.getString(“NAME”,””);strPassword = user getString(“PASSWORD”,””);}void WriteSharedPreferences(String strName,String strPassword){SharedPreferences user = getSharedPreferences(“user_info”,0);uer.edit();user.putString(“NAME”, strName);user.putString(“PASSWORD” ,strPassword);user.commit();}
数据读取与写入的方法都非常简单,只是在写入的时候有些区别:先调用 edit() 使其处于编辑状态,然后才能修改数据,最后使用 commit() 提交修改的数据。实际上 SharedPreferences 是采用了 XML 格式将数据存储到设备中,在 DDMS 中的 File Explorer 中的 /data/data//shares_prefs 下。使用 SharedPreferences 是有些限制的:只能在同一个包内使用,不能在不同的包之间使用。
文件存储方式是一种较常用的方法,在 Android 中读取/写入文件的方法,与 Java 中实现 I/O 的程序是完全一样的,提供了 openFileInput() 和openFileOutput() 方法来读取设备上的文件。具体实例如下:
String fn = “moandroid.log”;FileInputStream fis = openFileInput(fn);FileOutputStream fos = openFileOutput(fn,Context.MODE_PRIVATE);
网络存储方式,需要与 Android 网络数据包打交道,关于 Android 网络数据包的详细说明,请阅读 Android SDK 引用了 Java SDK 的哪些package?。
activity的启动模式有哪些?是什么含义?
在 Android 里,有 4 种 activity 的启动模式,分别为:
“standard” (默认)
“singleTop”
“singleTask”
“singleInstance”
它们主要有如下不同:
“standard” 和 ”singleTop” 的 activity 的目标 task,和收到的 Intent 的发送者在同一个 task 内,除非 intent 包括参数 FLAG_ACTIVITY_NEW_TASK。
如果提供了 FLAG_ACTIVITY_NEW_TASK 参数,会启动到别的 task 里。
“singleTask” 和 ”singleInstance” 总是把 activity 作为一个 task 的根元素,他们不会被启动到一个其他 task 里。
“standard” 和 ”singleTop” 可以被实例化多次,并且存在于不同的 task 中,且一个 task 可以包括一个 activity 的多个实例。
“singleTask” 和 ”singleInstance” 则限制只生成一个实例,并且是 task 的根元素。 singleTop 要求如果创建 intent 的时候栈顶已经有要创建的 Activity的实例,则将 intent 发送给该实例,而不发送给新的实例。
- 是否允许其它 activity 存在于本 task 内
“singleInstance” 独占一个 task,其它 activity 不能存在那个 task 里;如果它启动了一个新的 activity,不管新的 activity 的 launch mode 如何,新的activity 都将会到别的 task 里运行(如同加了 FLAG_ACTIVITY_NEW_TASK参数)。
而另外三种模式,则可以和其它 activity 共存。
“standard” 对于没一个启动 Intent 都会生成一个 activity 的新实例。
“singleTop” 的 activity 如果在 task 的栈顶的话,则不生成新的该 activity 的实例,直接使用栈顶的实例,否则,生成该 activity 的实例。
比如现在 task 栈元素为 A-B-C-D(D在栈顶),这时候给 D 发一个启动 intent,如果 D 是 “standard” 的,则生成 D 的一个新实例,栈变为 A-B-C-D-D。
如果 D 是 singleTop 的话,则不会生产 D 的新实例 ,栈状态仍为 A-B-C-D。
如果这时候给 B 发 Intent 的话,不管 B 的 launchmode 是 ”standard” 还是 “singleTop” ,都会生成 B 的新实例,栈状态变为 A-B-C-D-B。
“singleInstance” 是其所在栈的唯一 activity,它会每次都被重用。
“singleTask” 如果在栈顶,则接受 intent,否则,该 intent 会被丢弃,但是该 task 仍会回到前台。
当已经存在的 activity 实例处理新的 intent 时候,会调用 onNewIntent() 方法 如果收到 intent 生成一个 activity 实例,那么用户可以通过 back 键回到上一个状态;如果是已经存在的一个 activity 来处理这个 intent 的话,用户不能通过按 back 键返回到这之前的状态。
说说ContentProvider、ContentResolver、ContentObserver 之间的关系
ContentProvider实现各个应用程序间数据共享,用来提供内容给别的应用操作。如联系人应用中就使用了ContentProvider,可以在自己应用中读取和修改联系人信息,不过需要获取相应的权限。它也只是一个中间件,真正的数据源是文件或SQLite等。 ContentResolver内容解析者,用于获取内容提供者提供的数据,通过ContentResolver.notifyChange(uri)发出消息 ContentObserver内容监听者,可以监听数据的改变状态,观察特定Uri引起的数据库变化,继而做一些相应的处理,类似于数据库中的触发器,当ContentObserver所观察的Uri发生变化时,便会触发它。
注册广播有几种方式,这些方式有何优缺点?请谈谈 Android 引入广播机制的用意。
首先写一个类要继承 BroadcastReceiver
第一种:在清单文件中声明,添加
第二种使用代码进行注册如
两种注册类型的区别是:
-
第一种不是常驻型广播,也就是说广播跟随程序的生命周期; -
第二种是常驻型,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。
目前主流框架的分享,如何快速上手项目
=============================================================================
搭建项目的主流框架集
Dagger2+RxJava+Retrofit+MVP 是本人目前用的框架集,目前使用的很广泛, 四个相结合,组成项目的优美整体架构。 需要导入的包
dependencies { // 网络请求 compile ‘com.squareup.retrofit2:retrofit:2.1.0’ compile ‘com.squareup.retrofit2:adapter-rxJava:2.0.1’ compile ‘com.squareup.retrofit2:converter-gson:2.0.0-beta4’ compile ‘com.squareup.retrofit2:converter-scalars:2.0.0-beta4’ compile ‘com.squareup.okhttp3:okhttp:3.2.0’ // 注解 compile ‘com.google.dagger🗡2.0.2’ apt ‘com.google.dagger:dagger-compiler:2.0.2’ provided ‘org.glassfish:Javax.annotation:10.0-b28’ compile ‘com.jakewharton:butterknife:7.0.1’ // Rx compile ‘io.reactivex:rxandroid:1.1.0’ compile ‘io.reactivex:rxJava:1.1.5’ }
下面是 MVP 的架构图
如上图所示
View 与 Model 并不直接交互,而是使用 Presenter 作为 View 与 Model 之间的桥梁。
其中 Presenter 中同时持有 view 层以及 Model 层的 Interface 的引用,View 层持有 Presenter 层 Interface 的引用。当 View 层某个界面需要展示某些数据的时候,首先会调用 Presenter 层的某个接口,然后 Presenter 层会调用 Model 层请求数据。
当 Model 层数据加载成功之后会调用 Presenter 层的回调方法通知 Presenter 层数据加载完毕。
最后 Presenter 层再调用 View 层的接口将加载后的数据展示给用户。这就是 MVP 模式的整个核心过程,如果你是面试初级安卓开发,面试官应该只会让你阐述下整个调用过程,只要你能流畅的说完整个过程应该差不多了。
Dagger2 的流程图
什么是 Dagger2
Dagger2 是一个依赖注入框架,butterknife 也是一个依赖注入框架。不过 butterknife,最多叫奶油刀,Dagger2 被叫做利器啊,他的主要作用,就是对象的管理,其目的是为了降低程序耦合。
Dagger2 的优点
和 ButterKnife 库定义了view,事件处理以及资源的引用一样,Dagger2 提供全局对象引用的简易访问方式。声明了单例的实例都可以使用 @inject 进行访问。比如下面的 MyTwitterApiClient 和SharedPreferences 的实例:
public class MainActivity extends Activity { @Inject MyTwitterApiClient mTwitterApiClient; @Inject SharedPreferences sharedPreferences; public void onCreate(Bundle savedInstance) { // assign singleton instances to fields InjectorClass.inject(this); }
Dagger2 会通过依赖关系并且生成易懂易分析的代码。以前通过手写的大量模板代码中的对象引用将会由它给你创建并传递到相应对象中。因此你可以更多的关注模块中构建的内容而不是模块中的对象实例的创建顺序。
因为依赖关系已经为我们独立出来,所以我们可以轻松的抽取出不同的模块进行测试。依赖的注入和配置独立于组件之外。因为对象是在一个独立、不耦合的地方初始化,所以当注入抽象方法的时候,我们只需要修改对象的实现方法,而不用大改代码库。依赖可以注入到一个组件中:我们可以注入这些依赖的模拟实现,这样使得测试更加简单。
我们不仅可以轻松的管理全局实例对象,也可以使用 Dagger2 中的 scope 定义不同的作用域。(比如根据 user session、activity 的生命周期)
Retrofit 是 Square 开发的一个 Android 和 Java 的 REST 客户端库。这个库非常简单并且具有很多特性,相比其他的网络库,更容易让初学者快速掌握。
创建 Retrofit 实例时需要通过 Retrofit.Builder,并调用 baseUrl 方法设置 URL。
接口定义
以获取时间列表为例
public interface TimeService {> @GET(“getTimes?”) Call< ResponseBody > getTimes (@Query(“month”) String month);}
注意,这里是 interface 不是 class,所以我们是无法直接调用该方法,我们需要用 Retrofit 创建一个 TimeService 的代理对象。
TimeService timeService= createRetrofit().create(TimeService .class);
接口调用
Call mService= timeService.getTimes (“1”); mService.enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { try { Log(response.body().string()); } catch (IOException e) { e.printStackTrace(); } } @Override public void onFailure(Call call, Throwable t) { t.printStackTrace(); } });
如何快速理解项目进行二次开发
当我们进入一个新公司工作,有可能接手的不是一个新项目而是维护别人开发的项目,面对庞大的项目不知从何下手。在这里我要告诉大家的是,拿到项目的时候不要盲目的进行开发而是阅读项目源码。阅读源码首先浏览项目结构,
通过这张结构图可以了解项目用的是 MVP 架构,有两个 lib 一个和 service 有关 一个和图片处理有关。然后再阅读 build.gradle 文件,里面有项目中用到的第三方库的引用地址,知道项目中用到了哪些技术,如果有不熟悉的第三方库就可以根据这个地址去查找资源熟悉调用方式项目中用到的模块,日后逐渐研究库的源码以及实现原理。等这些了解的差不多了,再看一看需求文档和设计图,对着需求走一遍流程,主要记录 activity 之间的跳转,可以画一张类之间跳转的结构图,这样整个跳转的逻辑会更清晰。
还有两个值得阅读的是:项目中的工具类和封装的组件。相信不少人遇到过,在网上找了很久的一个处理数据的方法,过了很多天发现项目的工具类中有直接就可以拿来用。封装的组件也和工具类似可以直接拿来用的,在后面的开发也提倡大家将项目组件化。
四.历年大厂面试题总结
腾讯地图
,主要记录 activity 之间的跳转,可以画一张类之间跳转的结构图,这样整个跳转的逻辑会更清晰。
还有两个值得阅读的是:项目中的工具类和封装的组件。相信不少人遇到过,在网上找了很久的一个处理数据的方法,过了很多天发现项目的工具类中有直接就可以拿来用。封装的组件也和工具类似可以直接拿来用的,在后面的开发也提倡大家将项目组件化。
四.历年大厂面试题总结
腾讯地图
|