Android 基础面试题(二)

31、JSON的结构?

JSON是一种轻量级的数据交换格式, JSON简单说就是对象和数组,所以这两种结构就是对象和数组两种结构,通过这两种结构可以表示各种复杂的结构

  1. 对象:对象表示为“{}”扩起来的内容,数据结构为 {key:value,key:value,...}的键值对的结构,在面向对象的语言中,key为对象的属性,value为对应的属性值,所以很容易理解,取值方法为 对象.key 获取属性值,这个属性值的类型可以是 数字、字符串、数组、对象几种。
  2. 数组:数组在JSON中是中括号“[]”扩起来的内容,数据结构为 ["java","javascript","vb",...],取值方式和所有语言中一样,使用索引获取,字段值的类型可以是 数字、字符串、数组、对象几种。 经过对象、数组2种结构就可以组合成复杂的数据结构了。

32、ViewPager使用细节,如何设置成每次只初始化当前的Fragment,其他的不初始化(提示:Fragment懒加载)?

自定义一个 LazyLoadFragment 基类,利用 setUserVisibleHint 和 生命周期方法,通过对 Fragment 状态判断,进行数据加载,并将数据加载的接口提供开放出去,供子类使用。然后在子类 Fragment 中实现 requestData 方法即可。这里添加了一个 isDataLoaded 变量,目的是避免重复加载数据。考虑到有时候需要刷新数据的问题,便提供了一个用于强制刷新的参数判断。

public abstract class LazyLoadFragment extends BaseFragment {
    protected boolean isViewInitiated;
    protected boolean isDataLoaded;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        isViewInitiated = true;
        prepareRequestData();
    }
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        prepareRequestData();
    }
    public abstract void requestData();
    public boolean prepareRequestData() {
        return prepareRequestData(false);
    }
    public boolean prepareRequestData(boolean forceUpdate) {
        if (getUserVisibleHint() && isViewInitiated && (!isDataLoaded || forceUpdate)) {
            requestData();
            isDataLoaded = true;
            return true;
        }
        return false;
    }
}

35、Android为什么引入Parcelable?

可以肯定的是,两者都是支持序列化和反序列化的操作。

两者最大的区别在于 存储媒介的不同,Serializable 使用 I/O 读写存储在硬盘上,而 Parcelable 是直接 在内存中读写。很明显,内存的读写速度通常大于 IO 读写,所以在 Android 中传递数据优先选择 Parcelable。

Serializable 会使用反射,序列化和反序列化过程需要大量 I/O 操作, Parcelable 自已实现封送和解封(marshalled &unmarshalled)操作不需要用反射,数据也存放在 Native 内存中,效率要快很多。

36、有没有尝试简化Parcelable的使用?

使用Parcelable插件(Android Parcelable code generator)进行实体类的序列化的实现。

37、Bitmap 使用时候注意什么?

  1. 要选择合适的图片规格(bitmap类型):
    ALPHA_8   每个像素占用1byte内存  
    ARGB_4444 每个像素占用2byte内存   
    ARGB_8888 每个像素占用4byte内存(默认)  
    RGB_565 每个像素占用2byte内存
    
  2. 降低采样率。BitmapFactory.Options 参数inSampleSize的使用,先把options.inJustDecodeBounds设为true,只是去读取图片的大小,在拿到图片的大小之后和要显示的大小做比较通过calculateInSampleSize()函数计算inSampleSize的具体值,得到值之后。options.inJustDecodeBounds设为false读图片资源。
  3. 复用内存。即,通过软引用(内存不够的时候才会回收掉),复用内存块,不需要再重新给这个bitmap申请一块新的内存,避免了一次内存的分配和回收,从而改善了运行效率。
  4. 使用recycle()方法及时回收内存。
  5. 压缩图片。

38、Oom 是否可以try catch ?

只有在一种情况下,这样做是可行的:

在try语句中声明了很大的对象,导致OOM,并且可以确认OOM是由try语句中的对象声明导致的,那么在catch语句中,可以释放掉这些对象,解决OOM的问题,继续执行剩余语句。

但是这通常不是合适的做法。

Java中管理内存除了显式地catch OOM之外还有更多有效的方法:比如SoftReference, WeakReference, 硬盘缓存等。 在JVM用光内存之前,会多次触发GC,这些GC会降低程序运行的效率。 如果OOM的原因不是try语句中的对象(比如内存泄漏),那么在catch语句中会继续抛出OOM。

39、多进程场景遇见过么?

  1. 在新的进程中,启动前台Service,播放音乐。
  2. 一个成熟的应用一定是多模块化的。首先多进程开发能为应用解决了OOM问题,因为Android对内存的限制是针对于进程的,所以,当我们需要加载大图之类的操作,可以在新的进程中去执行,避免主进程OOM。而且假如图片浏览进程打开了一个过大的图片,Java heap 申请内存失败,该进程崩溃并不影响我主进程的使用。

40、Canvas.save()跟Canvas.restore()的调用时机

  • save:用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。
  • restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。
  • save和restore要配对使用(restore可以比save少,但不能多),如果restore调用次数比save多,会引发Error。save和restore操作执行的时机不同,就能造成绘制的图形不同。

41、数据库升级增加表和删除表都不涉及数据迁移,但是修改表涉及到对原有数据进行迁移。升级的方法如下所示:

将现有表命名为临时表。 创建新表。 将临时表的数据导入新表。 删除临时表。

如果是跨版本数据库升级,可以有两种方式,如下所示:

逐级升级,确定相邻版本与现在版本的差别,V1升级到V2,V2升级到V3,依次类推。 跨级升级,确定每个版本与现在数据库的差别,为每个case编写专门升级大代码。

public class DBservice extends SQLiteOpenHelper{
    private String CREATE_BOOK = "create table book(bookId integer primarykey,bookName text);";
    private String CREATE_TEMP_BOOK = "alter table book rename to _temp_book";
    private String INSERT_DATA = "insert into book select *,'' from _temp_book";
    private String DROP_BOOK = "drop table _temp_book";
    public DBservice(Context context, String name, CursorFactory factory,int version) {
    super(context, name, factory, version);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    switch (newVersion) {
        case 2:
            db.beginTransaction();
            db.execSQL(CREATE_TEMP_BOOK);
            db.execSQL(CREATE_BOOK);
            db.execSQL(INSERT_DATA);
            db.execSQL(DROP_BOOK);
            db.setTransactionSuccessful();
            db.endTransaction();
            break;
    }
}

42、编译期注解跟运行时注解

运行期注解(RunTime)利用反射去获取信息还是比较损耗性能的,对应@Retention(RetentionPolicy.RUNTIME)。

编译期(Compile time)注解,以及处理编译期注解的手段APT和Javapoet,对应@Retention(RetentionPolicy.CLASS)。 其中apt+javaPoet目前也是应用比较广泛,在一些大的开源库,如EventBus3.0+,页面路由 ARout、Dagger、Retrofit等均有使用的身影,注解不仅仅是通过反射一种方式来使用,也可以使用APT在编译期处理

43、bitmap recycler 相关

Android中,Bitmap的存储分为两部分,一部分是Bitmap的数据,一部分是Bitmap的引用。 在Android2.3时代,Bitmap的引用是放在堆中的,而Bitmap的数据部分是放在栈中的,需要用户调用recycle方法手动进行内存回收,而在Android2.3之后,整个Bitmap,包括数据和引用,都放在了堆中,这样,整个Bitmap的回收就全部交给GC了,这个recycle方法就再也不需要使用了。

bitmap recycler引发的问题:当图像的旋转角度小余两个像素点之间的夹角时,图像即使旋转也无法显示,因此,系统完全可以认为图像没有发生变化。这时系统就直接引用同一个对象来进行操作,避免内存浪费。

44、强引用置为null,会不会被回收?

不会立即释放对象占用的内存。 如果对象的引用被置为null,只是断开了当前线程栈帧中对该对象的引用关系,而 垃圾收集器是运行在后台的线程,只有当用户线程运行到安全点(safe point)或者安全区域才会扫描对象引用关系,扫描到对象没有被引用则会标记对象,这时候仍然不会立即释放该对象内存,因为有些对象是可恢复的(在 finalize方法中恢复引用 )。只有确定了对象无法恢复引用的时候才会清除对象内存。

45、Bundle传递数据为什么需要序列化?

序列化,表示将一个对象转换成可存储或可传输的状态。序列化的原因基本三种情况:

  1. 永久性保存对象,保存对象的字节序列到本地文件中;
  2. 对象在网络中传递;
  3. 对象在IPC间传递。

46、广播传输的数据是否有限制,是多少,为什么要限制?

Intent在传递数据时是有大小限制的,大约限制在1MB之内,你用Intent传递数据,实际上走的是跨进程通信(IPC),跨进程通信需要把数据从内核copy到进程中,每一个进程有一个接收内核数据的缓冲区,默认是1M;如果一次传递的数据超过限制,就会出现异常。

不同厂商表现不一样有可能是厂商修改了此限制的大小,也可能同样的对象在不同的机器上大小不一样。

传递大数据,不应该用Intent;考虑使用ContentProvider或者直接匿名共享内存。简单情况下可以考虑分段传输。

47、是否了解硬件加速?

硬件加速就是运用GPU优秀的运算能力来加快渲染的速度,而通常的基于软件的绘制渲染模式是完全利用CPU来完成渲染。

  1. 硬件加速是从API 11引入,API 14之后才默认开启。对于标准的绘制操作和控件都是支持的,但是对于自定义View的时候或者一些特殊的绘制函数就需要考虑是否需要关闭硬件加速。
  2. 我们面对不支持硬件加速的情况,就需要限制硬件加速,这个兼容性的问题是因为硬件加速是把View的绘制函数转化为使用OpenGL的函数来进完成实际的绘制的,那么必然会存在OpenGL中不支持原始回执函数的情况,对于这些绘制函数,就会失效。
  3. 硬件加速的消耗问题,因为是使用OpenGL,需要把系统中OpenGL加载到内存中,OpenGL API调用就会占用8MB,而实际上会占用更多内存,并且使用了硬件必然增加耗电量了。
  4. 硬件加速的优势还有display list的设计,使用这个我们不需要每次重绘都执行大量的代码,基于软件的绘制模式会重绘脏区域内的所有控件,而display只会更新列表,然后绘制列表内的控件。
  5. CPU更擅长复杂逻辑控制,而GPU得益于大量ALU和并行结构设计,更擅长数学运算。

48、ContentProvider的权限管理(读写分离,权限控制-精确到表级,URL控制)。

对于ContentProvider暴露出来的数据,应该是存储在自己应用内存中的数据,对于一些存储在外部存储器上的数据,并不能限制访问权限,使用ContentProvider就没有意义了。对于ContentProvider而言,有很多权限控制,可以在AndroidManifest.xml文件中对节点的属性进行配置,一般使用如下一些属性设置:

  • android:grantUriPermssions:临时许可标志。
  • android:permission:Provider读写权限。
  • android:readPermission:Provider的读权限。
  • android:writePermission:Provider的写权限。
  • android:enabled:标记允许系统启动Provider。
  • android:exported:标记允许其他应用程序使用这个Provider。
  • android:multiProcess:标记允许系统启动Provider相同的进程中调用客户端。

49、Fragment状态保存

Fragment状态保存入口:

  1. Activity的状态保存, 在Activity的onSaveInstanceState()里, 调用了FragmentManger的saveAllState()方法, 其中会对mActive中各个Fragment的实例状态和View状态分别进行保存.
  2. FragmentManager还提供了public方法: saveFragmentInstanceState(), 可以对单个Fragment进行状态保存, 这是提供给我们用的。
  3. FragmentManager的moveToState()方法中, 当状态回退到ACTIVITY_CREATED, 会调用saveFragmentViewState()方法, 保存View的状态.

50、直接在Activity中创建一个thread跟在service中创建一个thread之间的区别?

在Activity中被创建:该Thread的就是为这个Activity服务的,完成这个特定的Activity交代的任务,主动通知该Activity一些消息和事件,Activity销毁后,该Thread也没有存活的意义了。

在Service中被创建:这是保证最长生命周期的Thread的唯一方式,只要整个Service不退出,Thread就可以一直在后台执行,一般在Service的onCreate()中创建,在onDestroy()中销毁。所以,在Service中创建的Thread,适合长期执行一些独立于APP的后台任务,比较常见的就是:在Service中保持与服务器端的长连接。

51、如何计算一个Bitmap占用内存的大小,怎么保证加载Bitmap不产生内存溢出?

Bitamp 占用内存大小 = 宽度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一个像素所占的内存

注:这里inDensity表示目标图片的dpi(放在哪个资源文件夹下),inTargetDensity表示目标屏幕的dpi,所以你可以发现inDensity和inTargetDensity会对Bitmap的宽高进行拉伸,进而改变Bitmap占用内存的大小。

在Bitmap里有两个获取内存占用大小的方法。

  • getByteCount():API12 加入,代表存储 Bitmap 的像素需要的最少内存。
  • getAllocationByteCount():API19 加入,代表在内存中为 Bitmap 分配的内存大小,代替了 getByteCount() 方法。 在不复用 Bitmap 时,getByteCount() 和 getAllocationByteCount 返回的结果是一样的。在通过复用 Bitmap 来解码图片时,那么 getByteCount() 表示新解码图片占用内存的大 小,getAllocationByteCount() 表示被复用 Bitmap 真实占用的内存大小(即 mBuffer 的长度)。为了保证在加载Bitmap的时候不产生内存溢出,可以使用BitmapFactory进行图片压缩,主要有以下几个参数:
    • BitmapFactory.Options.inPreferredConfig:将ARGB_8888改为RGB_565,改变编码方式,节约内存。
    • BitmapFactory.Options.inSampleSize:缩放比例,可以参考Luban那个库,根据图片宽高计算出合适的缩放比例。
    • BitmapFactory.Options.inPurgeable:让系统可以内存不足时回收内存。

52、对于应用更新这块是如何做的?(灰度,强制更新,分区域更新)

  1. 通过接口获取线上版本号,versionCode
  2. 比较线上的versionCode 和本地的versionCode,弹出更新窗口
  3. 下载APK文件(文件下载)
  4. 安装APK

灰度: (1)找单一渠道投放特别版本。 (2)做升级平台的改造,允许针对部分用户推送升级通知甚至版本强制升级。 (3)开放单独的下载入口。 (4)是两个版本的代码都打到app包里,然后在app端植入测试框架,用来控制显示哪个版本。测试框架负责与服务器端API通信,由服务器端控制app上A/B版本的分布,可以实现指定的一组用户看到A版本,其它用户看到B版本。服务端会有相应的报表来显示A/B版本的数量和效果对比。最后可以由服务端的后台来控制,全部用户在线切换到A或者B版本~

无论哪种方法都需要做好版本管理工作,分配特别的版本号以示区别。 当然,既然是做灰度,数据监控(常规数据、新特性数据、主要业务数据)还是要做到位,该打的数据桩要打。 还有,灰度版最好有收回的能力,一般就是强制升级下一个正式版。

强制更新:一般的处理就是进入应用就弹窗通知用户有版本更新,弹窗可以没有取消按钮并不能取消。这样用户就只能选择更新或者关闭应用了,当然也可以添加取消按钮,但是如果用户选择取消则直接退出应用。

增量更新:bsdiff:二进制差分工具bsdiff是相应的补丁合成工具,根据两个不同版本的二进制文件,生成补丁文件.patch文件。通过bspatch使旧的apk文件与不定文件合成新的apk。 注意通过apk文件的md5值进行区分版本。

53、请解释安卓为啥要加签名机制。

  1. 发送者的身份认证 由于开发商可能通过使用相同的 Package Name 来混淆替换已经安装的程序,以此保证签名不同的包不被替换。
  2. 保证信息传输的完整性 签名对于包中的每个文件进行处理,以此确保包中内容不被替换。
  3. 防止交易中的抵赖发生, Market 对软件的要求。

54、为什么bindService可以跟Activity生命周期联动?

  1. bindService 方法执行时,LoadedApk 会记录 ServiceConnection 信息。
  2. Activity 执行 finish 方法时,会通过 LoadedApk 检查 Activity 是否存在未注销/解绑的 BroadcastReceiver 和 ServiceConnection,如果有,那么会通知 AMS 注销/解绑对应的 BroadcastReceiver 和 Service,并打印异常信息,告诉用户应该主动执行注销/解绑的操作。

55、如何通过Gradle配置多渠道包?

  • 用于生成不同渠道的包
    android {  
        productFlavors {
            xiaomi {}
            baidu {}
            wandoujia {}
            _360 {}        // 或“"360"{}”,数字需下划线开头或加上双引号
        }
    }
    
  • 执行./gradlew assembleRelease ,将会打出所有渠道的release包;
  • 执行./gradlew assembleWandoujia,将会打出豌豆荚渠道的release和debug版的包;
  • 执行./gradlew assembleWandoujiaRelease将生成豌豆荚的release包。
  • 因此,可以结合buildType和productFlavor生成不同的Build Variants,即类型与渠道不同的组合。

56、activty和Fragmengt之间怎么通信,Fragmengt和Fragmengt怎么通信?

  1. Handler
  2. 广播
  3. 事件总线:EventBus、RxBus、Otto
  4. 接口回调
  5. Bundle和setArguments(bundle)

57、自定义view效率高于XML定义吗?说明理由。

自定义view效率高于XML定义:

  1. 少了解析XML。
  2. 自定义View 减少了ViewGroup与View之间的测量,包括父量子,子量自身,子在父中位置摆放,当子view变化时,父的某些属性都会跟着变化。

58、广播注册一般有几种,各有什么优缺点?

  • 第一种是常驻型(静态注册):当应用程序关闭后如果有信息广播来,程序也会被系统调用,自己运行。
    • 优点: 无需担忧广播接收器是否被关闭,只要设备是开启状态,广播接收器就是打开着的。
  • 第二种不常驻(动态注册):广播会跟随程序的生命周期。
    • 优点: 在Android的广播机制中,动态注册优先级高于静态注册优先级,因此在必要情况下,是需要动态注册广播接收者的。
    • 缺点: 当用来注册的 Activity 关掉后,广播也就失效了。

59、服务启动一般有几种,服务和activty之间怎么通信,服务和服务之间怎么通信

  • startService:
    • onCreate()--->onStartCommand() ---> onDestory()
    • 如果服务已经开启,不会重复的执行onCreate(), 而是会调用onStartCommand()。一旦服务开启跟调用者(开启者)就没有任何关系了。 开启者退出了,开启者挂了,服务还在后台长期的运行。 开启者不能调用服务里面的方法。
  • bindService:
    • onCreate() --->onBind()--->onunbind()--->onDestory()
    • bind的方式开启服务,绑定服务,调用者挂了,服务也会跟着挂掉。 绑定者可以调用服务里面的方法。
  • 通信:
    • 通过Binder对象。
    • 通过broadcast(广播)。

60、ddms 和 traceView 的区别?

ddms 原意是:davik debug monitor service。简单的说 ddms 是一个程序执行查看器,在里面可以看见线程和堆栈等信息,traceView 是程序性能分析器。traceview 是 ddms 中的一部分内容。

Traceview 是 Android 平台特有的数据采集和分析工具,它主要用于分析 Android 中应用程序的 hotspot(瓶颈)。Traceview 本身只是一个数据分析工具,而数据的采集则需要使用 Android SDK 中的 Debug 类或者利用DDMS 工具。二者的用法如下:开发者在一些关键代码段开始前调用 Android SDK 中 Debug 类的 startMethodTracing 函数,并在关键代码段结束前调用 stopMethodTracing 函数。这两个函数运行过程中将采集运行时间内该应用所有线程(注意,只能是 Java线程) 的函数执行情况, 并将采集数据保存到/mnt/sdcard/下的一个文件中。 开发者然后需要利用 SDK 中的 Traceview工具来分析这些数据。

61、ListView卡顿原因

  • Adapter的getView方法里面convertView没有使用setTag和getTag方式;
  • 在getView方法里面ViewHolder初始化后的赋值或者是多个控件的显示状态和背景的显示没有优化好,抑或是里面含有复杂的计算和耗时操作;
  • 在getView方法里面 inflate的row 嵌套太深(布局过于复杂)或者是布局里面有大图片或者背景所致;
  • Adapter多余或者不合理的notifySetDataChanged;
  • listview 被多层嵌套,多次的onMessure导致卡顿,如果多层嵌套无法避免,建议把listview的高和宽设置为match_parent. 如果是代码继承的listview,那么也请你别忘记为你的继承类添加上LayoutPrams,注意高和宽都mactch_parent的;

62、AndroidManifest的作用与理解

AndroidManifest.xml文件,也叫清单文件,来获知应用中是否包含该组件,如果有会直接启动该组件。可以理解是一个应用的配置文件。作用:

  • 为应用的 Java 软件包命名。软件包名称充当应用的唯一标识符。
  • 描述应用的各个组件,包括构成应用的 Activity、服务、广播接收器和内容提供程序。它还为实现每个组件的类命名并发布其功能,例如它们可以处理的 Intent - 消息。这些声明向 Android 系统告知有关组件以及可以启动这些组件的条件的信息。
  • 确定托管应用组件的进程。
  • 声明应用必须具备哪些权限才能访问 API 中受保护的部分并与其他应用交互。还声明其他应用与该应用组件交互所需具备的权限
  • 列出 Instrumentation类,这些类可在应用运行时提供分析和其他信息。这些声明只会在应用处于开发阶段时出现在清单中,在应用发布之前将移除。
  • 声明应用所需的最低 Android API 级别
  • 列出应用必须链接到的库

63、LaunchMode应用场景

  • standard,创建一个新的Activity。
  • singleTop,栈顶不是该类型的Activity,创建一个新的Activity。否则,onNewIntent。
  • singleTask,回退栈中没有该类型的Activity,创建Activity,否则,onNewIntent+ClearTop。

注意:

设置了"singleTask"启动模式的Activity,它在启动的时候,会先在系统中查找属性值affinity等于它的属性值taskAffinity的Task存在;如果存在这样的Task,它就会在这个Task中启动,否则就会在新的任务栈中启动。因此, 如果我们想要设置了"singleTask"启动模式的Activity在新的任务中启动,就要为它设置一个独立的taskAffinity属性值。

如果设置了"singleTask"启动模式的Activity不是在新的任务中启动时,它会在已有的任务中查看是否已经存在相应的Activity实例, 如果存在,就会把位于这个Activity实例上面的Activity全部结束掉,即最终这个Activity 实例会位于任务的Stack顶端中。

在一个任务栈中只有一个”singleTask”启动模式的Activity存在。他的上面可以有其他的Activity。这点与singleInstance是有区别的。

singleInstance,回退栈中,只有这一个Activity,没有其他Activity。

singleTop适合接收通知启动的内容显示页面。

例如,某个新闻客户端的新闻内容页面,如果收到10个新闻推送,每次都打开一个新闻内容页面是很烦人的。

singleTask适合作为程序入口点。

例如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。

singleInstance应用场景:

闹铃的响铃界面。 你以前设置了一个闹铃:上午6点。在上午5点58分,你启动了闹铃设置界面,并按 Home 键回桌面;在上午5点59分时,你在微信和朋友聊天;在6点时,闹铃响了,并且弹出了一个对话框形式的 Activity(名为 AlarmAlertActivity) 提示你到6点了(这个 Activity 就是以 SingleInstance 加载模式打开的),你按返回键,回到的是微信的聊天界面,这是因为 AlarmAlertActivity 所在的 Task 的栈只有他一个元素, 因此退出之后这个 Task 的栈空了。如果是以 SingleTask 打开 AlarmAlertActivity,那么当闹铃响了的时候,按返回键应该进入闹铃设置界面。

64、说说Activity、Intent、Service 是什么关系

他们都是 Android 开发中使用频率最高的类。其中 Activity 和 Service 都是 Android 四大组件之一。他俩都是 Context 类的子类 ContextWrapper 的子类,因此他俩可以算是兄弟关系吧。不过兄弟俩各有各自的本领,Activity 负责用户界面的显示和交互,Service 负责后台任务的处理。Activity 和 Service 之间可以通过 Intent 传递数据,因此 可以把 Intent 看作是通信使者。

65、ApplicationContext和ActivityContext的区别

这是两种不同的context,也是最常见的两种.第一种中context的生命周期与Application的生命周期相关的,context随着Application的销毁而销毁,伴随application的一生,与activity的生命周期无关.第二种中的context跟Activity的生命周期是相关的,但是对一个Application来说,Activity可以销毁几次,那么属于Activity的context就会销毁多次.至于用哪种context,得看应用场景。还有就是,在使用context的时候,小心内存泄露,防止内存泄露,注意一下几个方面:

  • 不要让生命周期长的对象引用activity context,即保证引用activity的对象要与activity本身生命周期是一样的。
  • 对于生命周期长的对象,可以使用application context。
  • 避免非静态的内部类,尽量使用静态类,避免生命周期问题,注意内部类对外部对象引用导致的生命周期变化。

66、Handler、Thread和HandlerThread的差别

  1. Handler:在Android中负责发送和处理消息,通过它可以实现其他支线线程与主线程之间的消息通讯。
  2. Thread:Java进程中执行运算的最小单位,亦即执行处理机调度的基本单位。某一进程中一路单独运行的程序。
  3. HandlerThread:一个继承自Thread的类HandlerThread,Android中没有对Java中的Thread进行任何封装,而是提供了一个继承自Thread的类HandlerThread类,这个类对Java的Thread做了很多便利的封装。HandlerThread继承于Thread,所以它本质就是个Thread。与普通Thread的差别就在于,它在内部直接实现了Looper的实现,这是Handler消息机制必不可少的。有了自己的looper,可以让我们在自己的线程中分发和处理消息。如果不用HandlerThread的话,需要手动去调用Looper.prepare()和Looper.loop()这些方法。

67、ThreadLocal的原理

ThreadLocal是一个关于创建线程局部变量的类。使用场景如下所示:

  • 实现单个线程单例以及单个线程上下文信息存储,比如交易id等。
  • 实现线程安全,非线程安全的对象使用ThreadLocal之后就会变得线程安全,因为每个线程都会有一个对应的实例。 承载一些线程相关的数据,避免在方法中来回传递参数。

当需要使用多线程时,有个变量恰巧不需要共享,此时就不必使用synchronized这么麻烦的关键字来锁住,每个线程都相当于在堆内存中开辟一个空间,线程中带有对共享变量的缓冲区,通过缓冲区将堆内存中的共享变量进行读取和操作,ThreadLocal相当于线程内的内存,一个局部变量。每次可以对线程自身的数据读取和操作,并不需要通过缓冲区与 主内存中的变量进行交互。并不会像synchronized那样修改主内存的数据,再将主内存的数据复制到线程内的工作内存。ThreadLocal可以让线程独占资源,存储于线程内部,避免线程堵塞造成CPU吞吐下降。

在每个Thread中包含一个ThreadLocalMap,ThreadLocalMap的key是ThreadLocal的对象,value是独享数据。

68、计算一个view的嵌套层级

private int getParents(ViewParents view){
    if(view.getParents() == null) 
        return 0;
    } else {
    return (1 + getParents(view.getParents));
   }
}

69、MVP,MVVM,MVC解释和实践

MVC:
  • 视图层(View) 对应于XML布局文件和Java代码动态view部分
  • 控制层(Controller) MVC中Android的控制层是由Activity来承担的,Activity本来主要是作为初始化页面,展示数据的操作,但是因为XML视图功能太弱,所以Activity既要负责视图的显示又要加入控制逻辑,承担的功能过多。
  • 模型层(Model) 针对业务模型,建立数据结构和相关的类,它主要负责网络请求,数据库处理,I/O的操作。
总结

具有一定的分层,model彻底解耦,controller和view并没有解耦 层与层之间的交互尽量使用回调或者去使用消息机制去完成,尽量避免直接持有 controller和view在Android中无法做到彻底分离,但在代码逻辑层面一定要分清 业务逻辑被放置在model层,能够更好的复用和修改增加业务。

MVP

通过引入接口BaseView,让相应的视图组件如Activity,Fragment去实现BaseView,实现了视图层的独立,通过中间层Preseter实现了Model和View的完全解耦。MVP彻底解决了MVC中View和Controller傻傻分不清楚的问题,但是随着业务逻辑的增加,一个页面可能会非常复杂,UI的改变是非常多,会有非常多的case,这样就会造成View的接口会很庞大。

MVVM

MVP中我们说过随着业务逻辑的增加,UI的改变多的情况下,会有非常多的跟UI相关的case,这样就会造成View的接口会很庞大。而MVVM就解决了这个问题,通过双向绑定的机制,实现数据和UI内容,只要想改其中一方,另一方都能够及时更新的一种设计理念,这样就省去了很多在View层中写很多case的情况,只需要改变数据就行。

MVVM与DataBinding的关系?

MVVM是一种思想,DataBinding是谷歌推出的方便实现MVVM的工具。

看起来MVVM很好的解决了MVC和MVP的不足,但是由于数据和视图的双向绑定,导致出现问题时不太好定位来源,有可能数据问题导致,也有可能业务逻辑中对视图属性的修改导致。如果项目中打算用MVVM的话可以考虑使用官方的架构组件ViewModel、LiveData、DataBinding去实现MVVM。

三者如何选择?
  • 如果项目简单,没什么复杂性,未来改动也不大的话,那就不要用设计模式或者架构方法,只需要将每个模块封装好,方便调用即可,不要为了使用设计模式或架构方法而使用。
  • 对于偏向展示型的app,绝大多数业务逻辑都在后端,app主要功能就是展示数据,交互等,建议使用mvvm。
  • 对于工具类或者需要写很多业务逻辑app,使用mvp或者mvvm都可。

70、SharedPrefrences的apply和commit有什么区别?

这两个方法的区别在于:

  1. apply没有返回值而commit返回boolean表明修改是否提交成功。
  2. apply是将修改数据原子提交到内存, 而后异步真正提交到硬件磁盘, 而commit是同步的提交到硬件磁盘,因此,在多个并发的提交commit的时候,他们会等待正在处理的commit保存到磁盘后在操作,从而降低了效率。而apply只是原子的提交到内容,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率。
  3. apply方法不会提示任何失败的提示。 由于在一个进程中,sharedPreference是单实例,一般不会出现并发冲突,如果对提交的结果不关心的话,建议使用apply,当然需要确保提交成功且有后续操作的话,还是需要用commit的。

71、Base64、MD5是加密方法么?

Base64是什么?

Base64是用文本表示二进制的编码方式,它使用4个字节的文本来表示3个字节的原始二进制数据。 它将二进制数据转换成一个由64个可打印的字符组成的序列:A-Za-z0-9+/

MD5是什么?

MD5是哈希算法的一种,可以将任意数据产生出一个128位(16字节)的散列值,用于确保信息传输完整一致。我们常在注册登录模块使用MD5,用户密码可以使用MD5加密的方式进行存储。如:md5(hello world,32) = 5eb63bbbe01eeed093cb22bb8f5acdc3

加密,指的是对数据进行转换以后,数据变成了另一种格式,并且除了拿到解密方法的人,没人能把数据转换回来。 MD5是一种信息摘要算法,它是不可逆的,不可以解密。所以它只能算的上是一种单向加密算法。 Base64也不是加密算法,它是一种数据编码方式,虽然是可逆的,但是它的编码方式是公开的,无所谓加密。

72、HttpClient和HttpConnection的区别?

Http Client适用于Web浏览器,拥有大量灵活的API,实现起来比较稳定,且其功能比较丰富,提供了很多工具,封装了http的请求头,参数,内容体,响应,还有一些高级功能,代理、COOKIE、鉴权、压缩、连接池的处理。   但是,正因此,在不破坏兼容性的前提下,其庞大的API也使人难以改进,因此Android团队对于修改优化Apache Http Client并不积极。(并在Android 6.0中抛弃了Http Client,替换成OkHttp)

HttpURLConnection对于大部分功能都进行了包装,Http Client的高级功能代码会较复杂,另外,HttpURLConnection在Android 2.3中增加了一些Https方面的改进(包括Http Client,两者都支持https)。且在Android 4.0中增加了response cache。当缓存被安装后(调用HttpResponseCache的install()方法),所有的HTTP请求都会满足以下三种情况:

  • 所有的缓存响应都由本地存储来提供。因为没有必要去发起任务的网络连接请求,所有的响应都可以立刻获取到。
  • 视情况而定的缓存响应必须要有服务器来进行更新检查。比如说客户端发起了一条类似于 “如果/foo.png这张图片发生了改变,就将它发送给我” 这样的请求,服务器需要将更新后的数据进行返回,或者返回一个304 Not Modified状态。如果请求的内容没有发生,客户端就不会下载任何数据。
  • 没有缓存的响应都是由服务器直接提供的。这部分响应会在稍后存储到响应缓存中。

Android 2.2版本之前,HttpClient拥有较少的bug,因此使用它是最好的选择。 而在Android 2.3版本及以后,HttpURLConnection则是最佳的选择。它的API简单,体积较小,因而非常适用于Android项目。压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用。对于新的应用程序应该更加偏向于使用HttpURLConnection,因为在以后的工作当中Android官方也会将更多的时间放在优化HttpURLConnection上面。

73、ActivityA跳转ActivityB然后B按back返回A,各自的生命周期顺序,A与B均不透明。

  • ActivityA跳转到ActivityB:
    Activity A:onPause
    Activity B:onCreate
    Activity B:onStart
    Activity B:onResume
    Activity A:onStop
    
  • ActivityB返回ActivityA:
    Activity B:onPause
    Activity A:onRestart
    Activity A:onStart
    Activity A:onResume
    Activity B:onStop
    Activity B:onDestroy
    

74、如何通过广播拦截和abort一条短信?

可以监听这条信号,在传递给真正的接收程序时,我们将自定义的广播接收程序的优先级大于它,并且取消广播的传播,这样就可以实现拦截短信的功能了。

75、BroadcastReceiver,LocalBroadcastReceiver 区别?

1、应用场景
  1. BroadcastReceiver用于应用之间的传递消息;
  2. 而LocalBroadcastManager用于应用内部传递消息,比broadcastReceiver更加高效。
2、安全
  1. BroadcastReceiver使用的Content API,所以本质上它是跨应用的,所以在使用它时必须要考虑到不要被别的应用滥用;
  2. LocalBroadcastManager不需要考虑安全问题,因为它只在应用内部有效。
3、原理方面
  1. 与BroadcastReceiver是以 Binder 通讯方式为底层实现的机制不同,LocalBroadcastManager 的核心实现实际还是 Handler,只是利用到了 IntentFilter 的 match 功能,至于 BroadcastReceiver 换成其他接口也无所谓,顺便利用了现成的类和概念而已。
  2. LocalBroadcastManager因为是 Handler 实现的应用内的通信,自然安全性更好,效率更高。

76、如何选择第三方,从那些方面考虑?

大方向:从软件环境做判断

性能是开源软件第一解决的问题。一个好的生态,是一个优秀的开源库必备的,取决标准就是观察它是否一直在持续更新迭代,是否能及时处理GitHub上用户提出来的问题。大家在社区针对这个开源库是否有比较活跃的探讨。

背景,该开源库由谁推出,由哪个公司推出来的。用户数和有哪些知名的企业落地使用

小方向:从软件开发者的角度做判断

是否解决了我们现有问题或长期来看带来的维护成本。公司有多少人会。学习成本。

77、简单说下接入支付的流程,是否自己接入过支付功能?

Alipay支付功能:

  1. 首先登录支付宝开放平台创建应用,并给应用添加App支付功能, 由于App支付功能需要签约,因此需要上传公司信息和证件等资料进行签约。
  2. 签约成功后,需要配置秘钥。使用支付宝提供的工具生成RSA公钥和私钥,公钥需要设置到管理后台。
  3. Android studio集成
    (1)copy jar包;
    (2)发起支付请求,处理支付请求。
    

78、单例实现线程的同步的要求:

  1. 单例类确保自己只有一个实例(构造函数私有:不被外部实例化,也不被继承)。
  2. 单例类必须自己创建自己的实例。
  3. 单例类必须为其他对象提供唯一的实例。

79、如何保证Service不被杀死?

Android 进程不死从3个层面入手:

  • 提供进程优先级,降低进程被杀死的概率
    • 方法一:监控手机锁屏解锁事件,在屏幕锁屏时启动1个像素的 Activity,在用户解锁时将 Activity 销毁掉。
    • 方法二:启动前台service。
    • 方法三:提升service优先级:
    • 在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = "1000"这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播。
  • 在进程被杀死后,进行拉活
    • 方法一:注册高频率广播接收器,唤起进程。如网络变化,解锁屏幕,开机等
    • 方法二:双进程相互唤起。
    • 方法三:依靠系统唤起。
    • 方法四:onDestroy方法里重启service:service + broadcast 方式,就是当service走ondestory的时候,发送一个自定义的广播,当收到广播的时候,重新启动service;
  • 依靠第三方
    • 根据终端不同,在小米手机(包括 MIUI)接入小米推送、华为手机接入华为推送;其他手机可以考虑接入腾讯信鸽或极光推送与小米推送做 A/B Test。

80、说说ContentProvider、ContentResolver、ContentObserver 之间的关系?

  • ContentProvider:管理数据,提供数据的增删改查操作,数据源可以是数据库、文件、XML、网络等,ContentProvider为这些数据的访问提供了统一的接口,可以用来做进程间数据共享。
  • ContentResolver:ContentResolver可以为不同URI操作不同的ContentProvider中的数据,外部进程可以通过ContentResolver与ContentProvider进行交互。
  • ContentObserver:观察ContentProvider中的数据变化,并将变化通知给外界。

81、如何导入外部数据库?

把原数据库包括在项目源码的 res/raw。

Android系统下数据库应该存放在 /data/data/com.(package name)/ 目录下,所以我们需要做的是把已有的数据库传入那个目录下。操作方法是用FileInputStream读取原数据库,再用FileOutputStream把读取到的东西写入到那个目录。

82、LinearLayout、FrameLayout、RelativeLayout性能对比,为什么?

RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,也会调用子 View 2次onMeasure

RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin。

在不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是RelativeLayout。

为什么Google给开发者默认新建了个RelativeLayout,而自己却在DecorView中用了个LinearLayout?

因为DecorView的层级深度是已知而且固定的,上面一个标题栏,下面一个内容栏。采用RelativeLayout并不会降低层级深度,所以此时在根节点上用LinearLayout是效率最高的。而之所以给开发者默认新建了个RelativeLayout是希望开发者能采用尽量少的View层级来表达布局以实现性能最优,因为复杂的View嵌套对性能的影响会更大一些。

83、scheme跳转协议

Android中的scheme是一种页面内跳转协议,通过定义自己的scheme协议,可以跳转到app中的各个页面。服务器可以定制化告诉app跳转哪个页面。App可以通过跳转到另一个App页面。可以通过H5页面跳转页面

84、HandlerThread

  1. HandlerThread原理:当系统有多个耗时任务需要执行时,每个任务都会开启个新线程去执行耗时任务,这样会导致系统多次创建和销毁线程,从而影响性能。为了解决这一问题,Google提出了HandlerThread,HandlerThread本质上是一个线程类,它继承了Thread。HandlerThread有自己的内部Looper对象,可以进行loopr循环。通过获取HandlerThread的looper对象传递给Handler对象,可以在handleMessage()方法中执行异步任务。创建HandlerThread后必须先调用HandlerThread.start()方法,Thread会先调用run方法,创建Looper对象。当有耗时任务进入队列时,则不需要开启新线程,在原有的线程中执行耗时任务即可,否则线程阻塞。它在Android中的一个具体的使用场景是IntentService。由于HanlderThread的run()方法是一个无限循环,因此当明确不需要再使用HandlerThread时,可以通过它的quit或者quitSafely方法来终止线程的执行。
  2. HanlderThread的优缺点
    1. HandlerThread优点是异步不会堵塞,减少对性能的消耗。
    2. HandlerThread缺点是不能同时继续进行多任务处理,要等待进行处理,处理效率较低。
    3. HandlerThread与线程池不同,HandlerThread是一个串队列,背后只有一个线程。

85、IntentService

IntentService是一种特殊的Service,它继承了Service并且它是一个抽象类,因此必须创建它的子类才能使用IntentService。

原理

在实现上,IntentService封装了HandlerThread和Handler。当IntentService被第一次启动时,它的onCreate()方法会被调用,onCreat()方法会创建一个HandlerThread,然后使用它的Looper来构造一个Handler对象mServiceHandler,这样通过mServiceHandler发送的消息最终都会在HandlerThread中执行。

生成一个默认的且与主线程互相独立的工作者线程来执行所有传送至onStartCommand()方法的Intetnt。

生成一个工作队列来传送Intent对象给onHandleIntent()方法,同一时刻只传送一个Intent对象,这样一来,你就不必担心多线程的问题。在所有的请求(Intent)都被执行完以后会自动停止服务,所以,你不需要自己去调用stopSelf()方法来停止。

该服务提供了一个onBind()方法的默认实现,它返回null。

提供了一个onStartCommand()方法的默认实现,它将Intent先传送至工作队列,然后从工作队列中每次取出一个传送至onHandleIntent()方法,在该方法中对Intent做相应的处理。

为什么在mServiceHandler的handleMessage()回调方法中执行完onHandlerIntent()方法后要使用带参数的stopSelf()方法?

因为stopSel()方法会立即停止服务,而stopSelf(int startId)会等待所有的消息都处理完毕后才终止服务,一般来说,stopSelf(int startId)在尝试停止服务之前会判断最近启动服务的次数是否和startId相等,如果相等就立刻停止服务,不相等则不停止服务。

86、如何将一个Activity设置成窗口的样式。

  • 中配置:
    android:theme="@android:style/Theme.Dialog"
    
  • 另外
    android:theme="@android:style/Theme.Translucnt"
    
  • 是设置透明。

87、Android中跨进程通讯的几种方式

  1. 访问其他应用程序的Activity 如调用系统通话应用
    Intent callIntent = new Intent(Intent.ACTION_CALL,Uri.parse("tel:12345678");
    startActivity(callIntent);
    
  2. Content Provider 如访问系统相册
  3. 广播(Broadcast) 如显示系统时间
  4. AIDL服务

88、显示Intent与隐式Intent的区别

  • 对明确指出了目标组件名称的Intent,我们称之为“显式Intent”。
  • 对于没有明确指出目标组件名称的Intent,则称之为“隐式 Intent”。
  • 对于隐式意图,在定义Activity时,指定一个intent-filter,当一个隐式意图对象被一个意图过滤器进行匹配时,将有三个方面会被参考到:
    • 动作(Action)
    • 类别(Category ['kætɪg(ə)rɪ] )
    • 数据(Data )
      <activity android:name=".MainActivity"  android:label="@string/app_name">
                  <intent-filter>
                      <action android:name="com.wpc.test" />
                      <category android:name="android.intent.category.DEFAULT" />
                      <data android:mimeType="image/gif"/>
                  </intent-filter>
      </activity>
      

89、Android Holo主题与MD主题的理念,以及你的看法

  • Holo Theme 是 Android Design 的最基础的呈现方式。因为是最为基础的 Android Design 呈现形式,每一台 Android 4.X 的手机系统内部都有集成 Holo Theme 需要的控件,即开发者不需要自己设计控件,而是直接从系统里调用相应的控件。在 UI 方面没有任何的亮点,和 Android4.X 的设置/电话的视觉效果极度统一。由此带来的好处显而易见,这个应用作为 Android 应用的辨识度极高,且完全不可能与系统风格产生冲突。
  • Material design其实是单纯的一种设计语言,它包含了系统界面风格、交互、UI,更加专注拟真,更加大胆丰富的用色,更加丰富的交互形式,更加灵活的布局形式
    • 鲜明、形象的界面风格
    • 色彩搭配使得应用看起来非常的大胆、充满色彩感,凸显内容
    • Material design对于界面的排版非常的重视
    • Material design的交互设计上采用的是响应式交互,这样的交互设计能把一个应用从简单展现用户所请求的信息,提升至能与用户产生更强烈、更具体化交互的工具。

90、如何让程序自动启动?

定义一个Braodcastreceiver,action为BOOT——COMPLETE,接受到广播后启动程序。

91、Fragment 在 ViewPager 里面的生命周期,滑动 ViewPager 的页面时 Fragment 的生命周期的变化。

92、如何查看模拟器中的SP与SQList文件。如何可视化查看布局嵌套层数与加载时间。

93、各大平台打包上线的流程与审核时间,常见问题(主流的应用市场说出3-4个)

94、屏幕适配的处理技巧都有哪些?

一、为什么要适配

为了保证用户获得一致的用户体验效果,使得某一元素在Android不同尺寸、不同分辨率的、不同系统的手机上具备相同的显示效果,能够保持界面上的效果一致,我们需要对各种手机屏幕进行适配!

  • Android系统碎片化:基于Google原生系统,小米定制的MIUI、魅族定制的flyme、华为定制的EMUI等等;
  • Android机型屏幕尺寸碎片化:5寸、5.5寸、6寸等等;
  • Android屏幕分辨率碎片化:320x480、480x800、720x1280、1080x1920等。
二、基本概念
  • 像素(px):像素就是手机屏幕的最小构成单元,px = 1像素点 一般情况下UI设计师的设计图会以px作为统一的计量单位。
  • 分辨率:手机在横向、纵向上的像素点数总和 一般描述成 宽*高 ,即横向像素点个数 * 纵向像素点个数(如1080 x 1920),单位:px。
  • 屏幕尺寸:手机对角线的物理尺寸。单位 英寸(inch),一英寸大约2.54cm 常见的尺寸有4.7寸、5寸、5.5寸、6寸。
  • 屏幕像素密度(dpi):每英寸的像素点数,例如每英寸内有160个像素点,则其像素密度为160dpi,单位:dpi(dots per inch)。
  • 标准屏幕像素密度(mdpi): 每英寸长度上还有160个像素点(160dpi),即称为标准屏幕像素密度(mdpi)。
  • 密度无关像素(dp):与终端上的实际物理像素点无关,可以保证在不同屏幕像素密度的设备上显示相同的效果,是安卓特有的长度单位,dp与px的转换:1dp = (dpi / 160 ) * 1px。
  • 独立比例像素(sp):字体大小专用单位 Android开发时用此单位设置文字大小,推荐使用12sp、14sp、18sp、22sp作为字体大小。
三、适配方案

适配的最多的3个分辨率:1280720,1920 1080,800*480。解决方案:

对于Android的屏幕适配,我认为可以从以下4个方面来做:

1. 布局组件适配
  • 请务必使用密度无关像素 dp 或独立比例像素 sp 单位指定尺寸。
  • 使用相对布局或线性布局,不要使用绝对布局
  • 使用wrap_content、match_parent、权重
  • 使用minWidth、minHeight、lines等属性

dimens使用: 不同的屏幕尺寸可以定义不同的数值,或者是不同的语言显示我们也可以定义不同的数值,因为翻译后的长度一般都不会跟中文的一致。此外,也可以使用百分比布局或者AndroidStudio2.2的新特性约束布局。

2. 布局适配

使用限定符(屏幕密度限定符、尺寸限定符、最小宽度限定符、布局别名、屏幕方向限定符)根据屏幕的配置来加载相应的UI布局。

3. 图片资源适配

使用自动拉伸图.9png图片格式使图片资源自适应屏幕尺寸。

普通图片和图标:建议按照官方的密度类型进行切图即可,但一般我们只需xxhdpi或xxxhdpi的切图即可满足我们的需求;

4. 代码适配:

在代码中使用Google提供的API对设备的屏幕宽度进行测量,然后按照需求进行设置。

5. 接口配合:

本地加载图片前判断手机分辨率或像素密度,向服务器请求对应级别图片。

95、断点续传实现?

在本地下载过程中要使用数据库实时存储到底存储到文件的哪个位置了,这样点击开始继续传递时,才能通过HTTP的GET请求中的 setRequestProperty("Range","bytes=startIndex-endIndex"); 方法可以告诉服务器,数据从哪里开始,到哪里结束。同时在本地的文件写入时,RandomAccessFile的seek()方法也支持在文件中的任意位置进行写入操作。最后通过广播或事件总线机制将子线程的进度告诉Activity的进度条。关于断线续传的HTTP状态码是206,即HttpStatus.SC_PARTIAL_CONTENT。