ContentProvider
作用
进程间数据共享 即跨进程通信
原理
Binder进程间通信结合匿名共享内存(传输大数据)
启动步骤
- 调用者通过URI访问目标Provider
- 通过URI请求AMS返回访问目标Provider到代理对象
- AMS创建一个新的应用进程 启动Provider组件
- Provider将自己发布到AMS AMS将它到代理对象返回给调用者
主要方法
insert delete update query(增删改查)
ContentResolver
ContentProvider 不会与外界直接交互 而是通过ContentResolver类
作用
统一管理ContentProvider
之间到操作
- 通过URl可以操作不同到
ContentProvider
间的操作 - 外部进程通过
ContentResolver
类与ContentProvider
交互
为什么使用ContentResolver
类 而不直接与ContentProvider
交互?
ContentResolver
可以对所有ContentProvider
进行统一管理 相当于一个代理
使用
ContentResolver
提供类和ContentProvider
相同名字对方法
// 使用ContentResolver前,需要先获取ContentResolver
// 可通过在所有继承Context的类中 通过调用getContentResolver()来获得ContentResolver
ContentResolver resolver = getContentResolver();
// 设置ContentProvider的URI
Uri uri = Uri.parse("content://cn.scu.myprovider/user");
// 根据URI 操作 ContentProvider中的数据
// 此处是获取ContentProvider中 user表的所有记录
Cursor cursor = resolver.query(uri, null, null, null, "userid desc");
辅助类
ContentUris
作用
操作URI
使用
两个核心方法:
- withAppendedId
- parseId
// withAppendedId()作用:向URI追加一个id
Uri uri = Uri.parse("content://cn.scu.myprovider/user")
Uri resultUri = ContentUris.withAppendedId(uri, 7);
// 最终生成后的Uri为:content://cn.scu.myprovider/user/7
// parseId()作用:从URL中获取ID
Uri uri = Uri.parse("content://cn.scu.myprovider/user/7")
long personid = ContentUris.parseId(uri);
//获取的结果为:7
UriMatcher
作用
1⃣️ 在ContentProvider
中注册URI
2⃣️ 根据 URI
匹配 ContentProvider
中对应的数据表
使用
// 步骤1:初始化UriMatcher对象
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
//常量UriMatcher.NO_MATCH = 不匹配任何路径的返回码
// 即初始化时不匹配任何东西
// 步骤2:在ContentProvider 中注册URI(addURI())
int URI_CODE_a = 1;
int URI_CODE_b = 2;
matcher.addURI("cn.scu.myprovider", "user1", URI_CODE_a);
matcher.addURI("cn.scu.myprovider", "user2", URI_CODE_b);
// 若URI资源路径 = content://cn.scu.myprovider/user1 ,则返回注册码URI_CODE_a
// 若URI资源路径 = content://cn.scu.myprovider/user2 ,则返回注册码URI_CODE_b
// 步骤3:根据URI 匹配 URI_CODE,从而匹配ContentProvider中相应的资源(match())
@Override
public String getType(Uri uri) {
Uri uri = Uri.parse(" content://cn.scu.myprovider/user1");
switch(matcher.match(uri)){
// 根据URI匹配的返回码是URI_CODE_a
// 即matcher.match(uri) == URI_CODE_a
case URI_CODE_a:
return tableNameUser1;
// 如果根据URI匹配的返回码是URI_CODE_a,则返回ContentProvider中的名为tableNameUser1的表
case URI_CODE_b:
return tableNameUser2;
// 如果根据URI匹配的返回码是URI_CODE_b,则返回ContentProvider中的名为tableNameUser2的表
}
}
ContentObserver
内容观察者
观察
ContentProvider
中的数据变化并且通知外界
当ContentProvider
中发生数据改变时就会触发
使用
// 步骤1:注册内容观察者ContentObserver
getContentResolver().registerContentObserver(uri);
// 通过ContentResolver类进行注册,并指定需要观察的URI
// 步骤2:当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
public class UserContentProvider extends ContentProvider {
public Uri insert(Uri uri, ContentValues values) {
db.insert("user", "userid", values);
getContext().getContentResolver().notifyChange(uri, null);
// 通知访问者
}
}
// 步骤3:解除观察者
getContentResolver().unregisterContentObserver(uri);
// 同样需要通过ContentResolver类进行解除
BroadcastReceiver
作用
监听 / 接收 应用 App 发出的广播消息,并做出响应
原理
观察者
原理.png
使用
分为动态注册和静态注册两种 生命周期上有区别
Activity横竖屏切换的生命周期
- 竖屏切换到横屏
onPause-->onSaveInstanceState-->onStop-->onDestroy-->onCreate-->onStart-->onRestoreInstanceState-->onResume - 横屏切换到竖屏
同上
如果设置Activity的android:configChanges属性为orientation|screenSize或者orientation|screenSize|keyboardHidden 生命周期将发生改变
只会调用onConfigurationChanged,而不会重新加载Activity的各个生命周期
Activity与Fragment生命周期对比
生命周期.png关于Fragment
可理解成模块化的Activity
不能独立存在 需要依赖Activity
生命周期随承载它的Activity控制
为什么使用Fragment?
- 可以将Activity分离成可重用的组件 并且有自己的生命周期和UI
- 适用不同的屏幕尺寸
- 轻量切换 流畅
坑
-
getActivity 空指针
Fragment已经onDetach 但是异步任务仍然在执行
注意判空 -
Can not perform this action after onSaveInstanceState
离开Activity时 系统会调用onSaveInstanceState
保存当前的数据和状态 直到回到该Activity之前(onResume
之前) 执行Fragment事务就会抛出这个异常(一般是其他的Activity回调让当前页面执行事务的情况)
解决方式
1⃣️ 事务使用commitAllowingStateLoss提交 但是有可能导致该提交无效(宿主app被强杀时)
2⃣️ onActivityForResult()/onNewIntent()
做到事务的完整性 不会丢失事务
// ReceiverActivity 或 其子Fragment:
void start(){
startActivityForResult(new Intent(this, SenderActivity.class), 100);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 100 && resultCode == 100) {
// 执行Fragment事务
}
}
// SenderActivity 或 其子Fragment:
void do() { // 操作ReceiverActivity(或其子Fragment)执行事务
setResult(100);
finish();
}
-
Fragment重叠异常
正确使用hide show的姿势
在类onCreate的方法加载Fragment 并且没有判断saveInstanceState==null或if(findFragmentByTag(mFragmentTag) == null) 导致重复加载了同一个Fragment -
嵌套的问题
使用建议
1⃣️ 对Fragment传递数据建议使用setArguments(Bundle args)
通过getArguments ()
取出 内存重启前 系统会保存数据 不会造成数据丢失
2⃣️ 使用newInstance(参数) 创建Fragment对象
public class MyFragment extends Fragment {
/**
* 静态工厂方法需要一个int型的值来初始化fragment的参数,
* 然后返回新的fragment到调用者
*/
public static MyFragment newInstance(int index) {
MyFragment f = new MyFragment();
Bundle args = new Bundle();
args.putInt("index", index);
f.setArguments(args);
return f;
}
}
3⃣️ 基类Fragment定义全局的Activity变量
使用区别
- show(),hide()最终是让Fragment的View setVisibility(true还是false),不会调用生命周期
- replace()的话会销毁视图,即调用
onDestoryView
、onCreateView
等一系列生命周期 - add()和 replace()不要在同一个阶级的FragmentManager里混搭使用
使用场景
- 很高概率会使用当前的
Fragment
建议使用show和hide 可以提高性能 - 如果有很多图片 建议使用replace 利于配合图片框架回收内存
onHiddenChanged
当使用add()+show(),hide()跳转新的Fragment时 旧的Fragment回调onHiddenChanged() 不会回调onStop等生命周期方法 新的Fragment则不会回调onHiddenChanged
FragmentManager栈视图
对于宿主Activity getSupportFragmentManager
获取到的是FragmentActivity的FragmentManager对象;
对于Fragment getFragmentManager
是获取的父Fragment(如果没有则是FragmentActivity)的 FragmentManager
对象 而getChildFragmentManager
是获取的自己的 FragmentManager
对象
使用FragmentPagerAdapter+ViewPager注意事项
- 切换回上一个Fragment页面时(已经初始化完成) 不会回调任何生命周期方法以及
onHiddenChanged
只有setUserVisibleHint(boolean isVisibleToUser)
会回调 懒加载就在这里处理 - 在给ViewPager绑定FragmentPagerAdapter时
new FragmentPagerAdapter(fragmentManager)
的FragmentManager
一定要保证正确 如果ViewPager
是Activity中的控件 传递getSupportFragmentManager(); 如果是Fragment中的控件 应该传递getChildFragmentManager()
ViewPager内的Fragments是当前组件的子Fragment - 不需要考虑 内存重启 的情况下 去恢复fragments的问题 FragmentPagerAdapter已经帮我们处理好了
数据传递
- Activity传递给Fragment
setArguments+Bundle - Fragment传递给Activity
接口回调
getFragmentManager,getSupportFragmentManager ,getChildFragmentManager 区别
getFragmentManager()
所得到的是所在fragment 的父容器的管理器
getChildFragmentManager()
所得到的是在fragment 里面子容器的管理器
getSupportFragmentManager()
是3.0以下版本getFragmentManager()
的替代品
Fragment嵌套Fragment要用getChildFragmentManager
FragmentPagerAdapter与FragmentStatePagerAdapter的区别与使用场景
-
FragmentPagerAdapter
保存所有加入的fragment 步长超过1的会调用destroyItem fragment的生命周期里 只有onDestroyView调用了 没有调用onDestory和onDetach 此时只是view销毁了 fragment并没有销毁 下次再创建的时候只会调用onCreateView和onActivityCreated 这种情况下占用内存大 但是避免了频繁的创建和销毁
适用于页面比较少的情况 -
FragmentStatePagerAdapter
步长以内的fragment 和FragmentPagerAdapter一样 不会调用任何销毁操作 再次显示也无需创建 步长以外的会调用destroyItem 此时是会这正的销毁 适用于页面比较多的情况
自定义view
关于MeasureSpec.UNSPECIFIED
测量模式 | 表示意思 |
---|---|
UNSPECIFIED | 父容器(例如ScrollView等)没有对当前View有任何限制,当前View可以任意取尺寸 |
EXACTLY | 当前的尺寸就是当前View应该取的尺寸 指定了具体大小 例如100dp或者match_parent |
AT_MOST | 当前尺寸是当前View能取的最大尺寸 wrap_content |
onSavedInstanceState()和onRestoreInstanceState()
onSavedInstanceState的调用时机:系统回收或者home键
onRestoreInstanceState调用时机:onSavedInstanceState调用的前提下Activity销毁重建时
onSavedInstanceState的默认操作:
保存Activity的状态 比如UI(UI控件必须指定id)
临时数据使用onSaveInstanceState保存恢复,永久性数据使用onPause方法保存(数据持久化)
getX getRawX getTranslationX
- getX 触摸点距离自身左边的距离
- getRawX 触摸点距离屏幕左边的距离
- getTranslationX 该view在x轴方向的偏移量 初始值为0
动画种类
- 传统动画
帧动画和补间动画(淡入淡出 缩放 旋转 位移) - 属性动画
Interpolator和TypeEvaluator
- Interpolator 插值器 直接控制动画的变化速率 需要重写getInterpolation(float input),控制时间参数的变化
- TypeEvaluator 需要重写evaluate()方法,计算对象的属性值并将其封装成一个新对象返回(包括 位移 渐变色等)
事件分发机制
事件分发.pngonTouch对比onTouchEvent
onTouch的优先级高于onTouchEvent 如果onTouch返回false 则onTouchEvent执行 否则不执行(onTouch消费了事件)
插件化
-
插 件 中 ClassLoader 的问题;
DexClassLoader可以加载jar/apk/dex 可以加载未安装的apk; PathClassLoader只能加载已经安装过的apk
替换宿主activity的classloader目的是 加载插件apk的class -
插件中的资源文件访问问题;
AssetManager实例并且反射调用addAssetPath方法 -
插件中 Activity 组件的生命周期问题;
封装一个instrumentation,替换掉宿主的 (InstrumentationWrapper)
Serializable和Parcelable
Serializalbe 会使用反射 序列化和反序列化过程需要大量I/O操作
Parcelable 不需要反射 数据也存放在Native内存中 效率高;Parcelable基于Parcel(最快的序列化/反序列化机制)
资源目录的读取顺序
- 先去掉有冲突的资源目录
- 优先级进行查找
- 根据屏幕尺寸限定符选择资源时,如果没有更好的匹配资源,则系统将使用专为小于当前屏幕的屏幕而设计的资源
res/raw和assets的区别
两个目录都会被打包进APK 且不经过任何的压缩处理
assets支持任意深度的子目录 这些文件不会生成任何资源ID 只能使用AssetManager按相对的路径读取文件
AIDL是什么 支持哪些数据类型?
实现IPC的方式之一 还有一个是Messenger(单线程串行 不适合多线程并发 实际上基于AIDL)
支持的数据类型:
- 基本数据类型
- CharSequence
- List & Map(List和Map中的所有元素都必须是AIDL支持的数据类型、其他AIDL生成的接口或您声明的可打包类型)
Bitmap OOM
- Bitmap 储存的是 像素信息
- Drawable 储存的是 对 Canvas 的一系列操作;与View不同 不接受事件 无法与用户交互
优化:
- 不使用 调用recycle()
- inBitmap 设置了这个属性则会重用这个Bitmap的内存从而提升性能
- inSampleSize 压缩图片
- 加载很大的图片? BitmapRegionDecoder分块加载
框架搭建
网路框架
Retrofit 内置OkHttp 前者专注与接口的封装 后者专注于网络请求的高效
Retrofit提供不同的Converter实现(也可以自定义),同时提供RxJava支持(返回Observable对象)
OkHttp 作用:
- 支持SPDY, 可以合并多个到同一个主机的请求
- 使用连接池技术减少请求的延迟(如果SPDY是可用的话)
- 使用GZIP压缩减少传输的数据量
- 缓存响应避免重复的网络请求
- 提供了拦截器 方便监控
Retrofit解耦方式:
- 注解配置参数
- 工厂来生成CallAdapter,Converter
- Java动态代理
整体框架
mvp;mvvm
为什么使用Binder?
实现进程通信的方式:
- 管道
- Socket
- 共享内存
原因:
- 性能
Binder数据拷贝只需要一次 管道、消息队列、Socket都需要2次;
共享内存不需要拷贝 但是实现复杂 - 安全
Binder机制从协议本身就支持对通信双方做身份校检
ART和Dalvik
Dalvik是针对Android应用的虚拟机
Android 5.0之后Dalvik被ART替换
区别:
ART 去掉了中间解释字节码的过程
Dalvik是JIT的机制 与JIT相对的是AOT(Ahead-Of-Time) 发生在程序运行之前
Android应用的进程
每一个App都运行在独立的进程中 进程有自己独立的内存空间 每一个进程的名字是App的packageName 每一个进程都是由Zygote进程fork出来的 受AMS管理
优先级
由高到低:
- 前台
- 可见
- 服务
- 后台
- 空
线程
CPU调度的基本单位
线程安全:多个线程同时访问一个类时不管怎么调度 这个类都能表现正确的行为
如何线程同步
-
wait notify notifyAll
wait()的作用是让当前线程进入等待状态 同时让当前线程释放所有锁
notify()是唤醒单个线程
notifyAll()是唤醒所有的线程 -
wait对比yield(或sleep)
wait()是让线程由“运行状态”进入到“等待(阻塞)状态” 会释放锁
yield()是让线程由“运行状态”进入到“就绪状态” 让其他具有相同优先级的等待线程可以获取执行权(不能保证) 不会释放锁 -
CountDownLatch对比join
业务场景:
假设一条流水线上有三个工作者:worker0,worker1,worker2 worker2可以开始这个任务的前提是worker0和worker1完成了他们的工作 而worker0和worker1是可以并行他们各自的工作的
join实现:
public class Worker extends Thread {
private String name;
private long time;
public Worker(String name, long time) {
this.name = name;
this.time = time;
}
@Override
public void run() {
// 自动生成的方法存根
try {
System.out.println(name + "开始工作");
Thread.sleep(time);
System.out.println(name + "工作完成,耗费时间=" + time);
} catch (InterruptedException e) {
// 自动生成的 catch 块
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Worker worker0 = new Worker("worker0", (long) (Math.random()*2000+3000));
Worker worker1 = new Worker("worker1", (long) (Math.random()*2000+3000));
Worker worker2 = new Worker("worker2", (long) (Math.random()*2000+3000));
worker0.start();
worker1.start();
worker0.join();
worker1.join();
System.out.println("准备工作就绪");
worker2.start();
}
CountDownLatch实现:
public class Worker extends Thread {
private String name;
private long time;
private CountDownLatch countDownLatch;
public Worker(String name, long time,CountDownLatch countDownLatch) {
this.name = name;
this.time = time;
this.countDownLatch=countDownLatch;
}
@Override
public void run() {
// TODO 自动生成的方法存根
try {
System.out.println(name+"开始工作");
Thread.sleep(time);
System.out.println(name+"工作完成,耗费时间="+time);
countDownLatch.countDown();
System.out.println("countDownLatch.getCount()="+countDownLatch.getCount());
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
// CountDownLatch相比join的优势在于:thread.join() 方法必须等thread 执行完毕 当前线程才能继续往下执行
// 而CountDownLatch通过计数器提供了更灵活的控制 只要检测到计数器为0当前线程就可以往下执行
// 可以实现更复杂的业务场景 比如原需求改为:work2 只需要等待work0和work1完成他们
// 各自工作的第一个阶段之后就可以开始自己的工作
CountDownLatch countDownLatch = new CountDownLatch(2);
Worker worker0 = new Worker("worker0", (long) (Math.random()*2000+3000), countDownLatch);
Worker worker1 = new Worker("worker1", (long) (Math.random()*2000+3000), countDownLatch);
Worker worker2 = new Worker("worker2", (long) (Math.random()*2000+3000), countDownLatch);
worker0.start();
worker1.start();
// CountDownLatch计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier
// 当计数器为零时,会释放所有等待的线程,await后的代码将被执行
countDownLatch.await();
System.out.println("准备工作就绪");
worker2.start();
}
CountDownLatch的使用场景
需要满足一定条件 接下来的事情才会继续执行
如何在SQLite数据库中进行大量的数据插入?
使用SQLiteDatabase的insert,delete等方法或者execSQL方法默认都开启了事务,如果操作的顺利完成才会更新.db数据库。事务的实现是依赖于名为rollback journal
文件,借助这个临时文件来完成原子操作和回滚功能
SQLite想要执行操作,需要将程序中的SQL语句编译成对应的SQLiteStatement,比如" select * from table1 ",每执行一次都需要将这个String类型的SQL语句转换成SQLiteStatement
如下insert的操作最终都是将ContentValues转成SQLiteStatement
public long insertWithOnConflict(String table, String nullColumnHack,
ContentValues initialValues, int conflictAlgorithm) {
// 省略部份代码
SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
try {
return statement.executeInsert();
} finally {
statement.close();
}
} finally {
releaseReference();
}
}
重点来了 对于批量处理插入或者更新的操作 我们可以重用SQLiteStatement
使用SQLiteDatabase的beginTransaction()方法开启一个事务:
try
{
// 开启事务
sqLiteDatabase.beginTransaction();
SQLiteStatement stat = sqLiteDatabase.compileStatement(insertSQL);
// 插入10000次 重用SQLiteStatement
for (int i = 0; i < 10000; i++)
{
stat.bindLong(1, 123456);
stat.bindString(2, "test");
stat.executeInsert();
}
sqLiteDatabase.setTransactionSuccessful();
}
catch (SQLException e)
{
e.printStackTrace();
}
finally
{
// 结束
sqLiteDatabase.endTransaction();
sqLiteDatabase.close();
}
- 插入或者更新 用SQLiteStatement+事务
- 查询建立索引
为什么要设计ContentProvider?
- 封装 对数据进行封装 提供统一的接口 使用者没必要关注内部细节 (有点迭代器模式的味道)
- 跨进程数据共享
android:exported
属性非常重要 如果设置为true 则能够被调用或交互;设置为false时 只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务
对同一个签名的其他应用开放 需要设置signature
级别的权限
例如系统自带应用的代码:
<permission android:name="com.android.gallery3d.filtershow.permission.READ"
android:protectionLevel="signature" />
<permission android:name="com.android.gallery3d.filtershow.permission.WRITE"
android:protectionLevel="signature" />
<provider
android:name="com.android.gallery3d.filtershow.provider.SharedImageProvider"
android:authorities="com.android.gallery3d.filtershow.provider.SharedImageProvider"
android:grantUriPermissions="true"
android:readPermission="com.android.gallery3d.filtershow.permission.READ"
android:writePermission="com.android.gallery3d.filtershow.permission.WRITE" />
开放部分URI给其他应用访问 参考原生ContactsProvider2
(注意path-permission
)
<provider android:name="ContactsProvider2"
android:authorities="contacts;com.android.contacts"
android:label="@string/provider_label"
android:multiprocess="false"
android:exported="true"
android:grantUriPermissions="true"
android:readPermission="android.permission.READ_CONTACTS"
android:writePermission="android.permission.WRITE_CONTACTS">
<path-permission
android:pathPrefix="/search_suggest_query"
android:readPermission="android.permission.GLOBAL_SEARCH" />
<path-permission
android:pathPrefix="/search_suggest_shortcut"
android:readPermission="android.permission.GLOBAL_SEARCH" />
<path-permission
android:pathPattern="/contacts/.*/photo"
android:readPermission="android.permission.GLOBAL_SEARCH" />
<grant-uri-permission android:pathPattern=".*" />
</provider>
android:multiprocess
默认false 表示ContentProvider是单例;为true 为每一个进程创建一个实例
ContentProvider运行在哪个线程中?
- ContentProvider和调用者在同一个进程,ContentProvider的方法(query/insert/update/delete等)和调用者在同一线程中
- ContentProvider和调用者在不同的进程,ContentProvider的方法会运行在它自身所在进程的一个Binder线程中
ContentProvider跨进程通信方式
每一个应用进程有16个Binder线程去和远程服务进行交互
- 共享数据 是通过Binder机制
- 不同应用程序间传递数据是通过 匿名共享内存
SharedPreferences两种提交方式的区别
- commit 同步 返回一个boolean值告知是否保存成功
- apply 异步 推荐方法
registerOnSharedPreferenceChangeListener
可以监听键值变化的监听器
多进程操作
在SDK 3.0及以上版本,可以通过Context.MODE_MULTI_PROCESS属性来实现SharedPreferences多进程共享
public static SharedPreferences getSharedPreferences(String name) {
if (null != context) {
if (Build.VERSION.SDK_INT >= 11) {
return context.getSharedPreferences(name, Context.MODE_MULTI_PROCESS);
} else {
return context.getSharedPreferences(name, Context.MODE_PRIVATE);
}
}
return null;
}
开源项目解决多进程的问题:tray
使用AsyncTask会有什么问题
- 对象必须在主线程中创建 为了切换到主线程
-
excute
方法必须在UI线程中调用 且此方法只能调用一次 - 非静态内部类容易引发内存泄漏 (问题)
- 串行执行 不适合多个异步操作(问题)
AsyncTask是串行执行的 需要并行可以使用 executeOnExecutor
AsyncTask替代方案:1.EventBus 2.RxJava
优化ListView性能
- 利用ViewHolder重用ConvertView
- 异步线程加载图片
- getView中避免频繁创建对象
- 快速滑动时不要加载图片
- 将ListView的scrollingCache和animateCache这两个属性设置为false(默认是true)
- 减少List Item的Layout层次
如何理解Context?
问题:Application(或者Service)和Activity都可以调用Context的startActivity方法,那么在这两个地方调用startActivity有区别吗?
Application(或者Service)需要给Intent设置Intent.FLAG_ACTIVITY_NEW_TASK才能正常启动Activity 这就会引出Activity的Task栈问题:
FLAG_ACTIVITY_NEW_TASK
意义是为这个Activity指定或分配一个任务栈 为什么Application和Service需要这么做?
看源码:
ContextImpl.java
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
// Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
// generally not allowed, except if the caller specifies the task id the activity should
// be launched in.
// 对FLAG_ACTIVITY_NEW_TASK进行判断
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
&& options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}
ContextWrapper.java
@Override
public void startActivity(Intent intent) {
// mBase是一个ContextImpl对象 此处是装饰者模式的应用
mBase.startActivity(intent);
}
Activity.java
@Override
public void startActivity(Intent intent) {
this.startActivity(intent, null);
}
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
// Note we want to go through this call for compatibility with
// applications that may have overridden the method.
startActivityForResult(intent, -1);
}
}
由源码可知 Activity重写了startActivity方法 去掉了对FLAG_ACTIVITY_NEW_TASK的检查 而Application和Service并没有重写此方法 所以依旧是ContextImpl中的逻辑
new创建的View实例它的onSaveStateInstance会被调用吗?
自定义View的状态保存需满足两个条件:
- View有唯一的id
- 初始化时需要调用
setSaveEnabled(true)
答案:只要设置了唯一的id 就会被调用
自定义View的状态如何保存?
Activity的 onSaveInstanceState
onRestoreInstanceState
最终也会调用到控件的这两个同名方法 具体的保存逻辑是在这两个方法中实现的
广播中如何进行耗时操作?
开启一个Service 将耗时操作交给Service 一般为IntentService
Service的onCreate回调函数可以做耗时的操作吗?
不可以 因为onCreate也是在主线程中调用的
做法:Thread+Handler
关于IntentService
- 回调函数onHandleIntent中可以直接进行耗时操作
- 多次调用onHandleIntent函数 多个耗时任务会按顺序执行 因为内部使用了Handler+消息队列
- 执行完会自动结束
源码分析
- IntentService如何单独开启1个新的工作线程?
@Override
public void onCreate() {
super.onCreate();
// 1. 通过实例化andlerThread新建线程 & 启动;故 使用IntentService时,不需额外新建线程
// HandlerThread继承自Thread,内部封装了 Looper
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
// 2. 获得工作线程的 Looper & 维护自己的工作队列
mServiceLooper = thread.getLooper();
// 3. 新建mServiceHandler & 绑定上述获得Looper
// 新建的Handler 属于工作线程 ->>分析1
mServiceHandler = new ServiceHandler(mServiceLooper);
}
/**
* 分析1:ServiceHandler源码分析
**/
private final class ServiceHandler extends Handler {
// 构造函数
public ServiceHandler(Looper looper) {
super(looper);
}
// IntentService的handleMessage()把接收的消息交给onHandleIntent()处理
@Override
public void handleMessage(Message msg) {
// onHandleIntent 方法在工作线程中执行
// onHandleIntent() = 抽象方法,使用时需重写 ->>分析2
onHandleIntent((Intent)msg.obj);
// 执行完调用 stopSelf() 结束服务
stopSelf(msg.arg1);
}
}
/**
* 分析2: onHandleIntent()源码分析
* onHandleIntent() = 抽象方法,使用时需重写
**/
@WorkerThread
protected abstract void onHandleIntent(Intent intent);
- IntentService 如何通过onStartCommand() 将Intent 传递给服务 & 依次插入到工作队列中?
/**
* onStartCommand()源码分析
* onHandleIntent() = 抽象方法,使用时需重写
**/
public int onStartCommand(Intent intent, int flags, int startId) {
// 调用onStart()->>分析1
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
/**
* 分析1:onStart(intent, startId)
**/
public void onStart(Intent intent, int startId) {
// 1. 获得ServiceHandler消息的引用
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
// 2. 把 Intent参数 包装到 message 的 obj 发送消息中,
//这里的Intent = 启动服务时startService(Intent) 里传入的 Intent
msg.obj = intent;
// 3. 发送消息,即 添加到消息队列里
mServiceHandler.sendMessage(msg);
}
注意事项
- 工作任务队列 = 顺序执行
若一个任务正在IntentService中执行,此时你再发送1个新的任务请求,这个新的任务会一直等待直到前面一个任务执行完毕后才开始执行
原因:
1⃣️ onCreate只会调用一次 即只会创建一个工作线程
2⃣️ 多次调用startService时 也就是onStartCommand也会调用多次 其实不会创建新的工作线程 只是把消息放到队列中等待执行
如果服务停止 会清空消息队列中的消息 后续的事件不执行
- 不建议通过 bindService() 启动 IntentService
// 在IntentService中,onBind()默认返回null
@Override
public IBinder onBind(Intent intent) {
return null;
}
采用 bindService()启动 IntentService的生命周期如下
onCreate() ->> onBind() ->> onunbind()->> onDestory()
不会走到onStart() 或 onStartcommand() 不会将消息发送到消息队列onHandleIntent
不会回调
activity启动流程
根Activity的启动流程
- AMS#startActivity
- ActivityStack#startActivityMayWait
- ApplicationThread#schedulePauseActivity
- AMS#activityPaused
- AMS#startProcessLockked
- ApplicationThread#scheduleLaunchActivity
- ActivityThread#performLaunchActivity
总结为这几个流程:
- Launcher通过Binder进程间通信机制通知AMS启动一个Activity
- AMS通过Binder进程间通信机制通知Launcher进入Paused状态
- AMS创建一个新的进程,用来启动一个ActivityThread实例
- ActivityThread通过ApplicationThread(一个Binder对象)与AMS通信
子activity启动流程与根activity启动流程大致相同 只不过根activity需要startProcessLocked
attachApplication
Service的两种启动方式
- startService
- bindService
区别:生命周期
- startService
onCreate()--->onStartCommand()(onStart()方法已过时) ---> onDestory()
生命周期不受调用者限制:stopService(Intent)
才能结束生命周期 - bindService
onCreate() --->onBind()--->onUnbind()--->onDestory()
生命周期和调用者同步
广播
- 静态注册
常驻型广播 - 动态注册
非常驻型广播
HttpClient与HttpUrlConnection对比
HttpUrlConnection支持压缩和缓存机制
http与https对比
https=http+ssl
https对传输的数据会进行加密并且需要证书 而ssl的主要工作就是加密
进程保活
- 黑色保活 进程广播互相唤起
- 白色保活
- 灰色保活
API < 18,启动前台Service时直接传入new Notification();
API >= 18,同时启动两个id相同的前台Service,然后再将后启动的Service做stop处理
进程间通信方式
- Intent
- 使用文件共享
- Messenger
- AIDL
- ContentProvider
- Socket
三级缓存
- 内存缓存
- 本地缓存
- 网络缓存
mvp
public interface MvpView {
}
public interface MvpPresenter<V extends MvpView> {
public void attachView(V view);
public void detachView();
}
MvpBasePresenter 重点实现以下几个方法:
- attachView
- detachView
- getView
- setModelData
- getModelData
public class MvpBasePresenter<V extends MvpView, M extends BaseModel> implements MvpPresenter<V> {
protected Context mContext;
private WeakReference<V> viewRef;
private M data;
/**
* must be Application context
* @param context must be Application context
*/
public MvpBasePresenter(Context context) {
if (context instanceof Application) {
this.mContext = context;
} else {
throw new IllegalArgumentException("context must be ApplicationContext");
}
}
@Override
public void attachView(V view) {
viewRef = new WeakReference<V>(view);
}
/**
* Get the attached view. You should always call {@link #isViewAttached()} to check if the view
* is
* attached to avoid NullPointerExceptions.
*
* @return <code>null</code>, if view is not attached, otherwise the concrete view instance
*/
@Nullable
protected V getView() {
return viewRef == null ? null : viewRef.get();
}
public M getModelData() {
return data;
}
protected void setModelData(M data) {
this.data=data;
}
/**
* Checks if a view is attached to this presenter. You should always call this method before
* calling {@link #getView()} to get the view instance.
*/
protected boolean isViewAttached() {
return viewRef != null && viewRef.get() != null;
}
@Override
public void detachView() {
if (viewRef != null) {
viewRef.clear();
viewRef = null;
}
}
}
public class BaseModel implements Parcelable{
protected BaseModel(Parcel in) {
}
protected BaseModel() {
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
}
}
public abstract class BaseFragment<P extends MvpPresenter> extends Fragment {
protected P mPresenter;
protected View rootView;
protected boolean isPrepared;
private Unbinder unbind;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
if (rootView == null) {
int layoutRes = getLayoutRes();
mPresenter = getPresenter();
rootView = getActivity().getLayoutInflater().inflate(layoutRes, null);
unbind = ButterKnife.bind(this, rootView);
isPrepared = true;
mPresenter.attachView(getMvpView());
init();
} else {
if (rootView.getParent() != null) {
((ViewGroup) rootView.getParent()).removeView(rootView);
}
unbind = ButterKnife.bind(this, rootView);
mPresenter.attachView(getMvpView());
}
return rootView;
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (mPresenter != null) {
mPresenter.detachView();
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (unbind != null) {
unbind.unbind();
}
}
/**
* Return the layout resource like R.layout.my_layout
*
* @return the layout resource or zero ("0"), if you don't want to have an UI
*/
protected abstract int getLayoutRes();
public abstract P getPresenter();
protected abstract MvpView getMvpView();
protected abstract void init();
}
对Context对理解
抽象类 提供全局信息对接口
framework中对继承关系:
Context继承关系.png
由图可知:Context总数=Application总数+Activity数+Service数
Application Activity以及Service关于Context使用上的区别:
- Dialog
仅Activity支持Dialog - startActivity
Application Service中startActivity 需要intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
从源码的角度解释 Activity重写了Context的startActivity
方法 去除了对FLAG_ACTIVITY_NEW_TASK
的验证 而Application和Service没有重写 至于Activity为什么会去除对FLAG_ACTIVITY_NEW_TASK
的验证 因为再Lancher启动Activity时会默认设置FLAG_ACTIVITY_NEW_TASK
并且之后跳转到Activity默认都会在这个TASK中
JNI
public class AndroidJni {
//声明一块静态域
static{
System.loadLibrary("main"); //JNI约定 标识libmain.so
}
//声明native方法
public native void dynamicLog();
public native void staticLog();
}
Dalvik虚拟机和java虚拟机对比
架构不同
- java虚拟机 基于栈架构 需要频繁从栈上读取和写入数据 耗费CPU
- 基于寄存器 方式比栈快
sleep对比wait
- sleep 不释放同步锁 Tread的静态方法 到指定时间会自动苏醒 任何地方是可以执行 需要捕获异常
- wait 释放同步锁 Object的方法 直到调用notify对象才会重新激活 同步块中执行 不需要捕获异常
事件分发
- View
- VIewGroup
Activity保存状态
重写 onSaveInstanceState
方法
@Override
protected void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
// 存入数据
outState.putInt("currentposition", videoView.getCurrentPosition());
Log.v("tag", "onSaveInstanceState");
super.onSaveInstanceState(outState);
}
onCreate
中
savedInstanceState.getInt("currentposition") //获取数据
WebView与JS交互
WebSettings setting = webView.getSettings();
setting.setJavaScriptEnabled(true); //支持js
setting.setDefaultTextEncodingName("GBK"); //设置字符编码
webView.setWebChromeClient(new WebChromeClient() {
public void onProgressChanged(WebView view, int progress) {// 载入进度改变而触发
if (progress == 100) {
handler.sendEmptyMessage(1);// 如果全部载入,隐藏进度对话框
}
super.onProgressChanged(view, progress);
}
setWebViewClient
和setWebChromeClient
-
setWebViewClient
主要用于处理webView的控制问题,如加载、关闭、错误处理等 -
setWebChromeClient
主要处理js对话框、图标、页面标题等
与JS的交互
-
@JavascriptInterface
注册方法 -
addJavascriptInterface(jsOperation, "js_operation");
注册方法对象
自定义View和动画
设计模式
开源框架对比
RecyclerView
-
优点
高度解耦:设置它提供的不同LayoutManager,ItemDecoration , ItemAnimator实现效果
显示的方式:LayoutManager
Item间的间隔:ItemDecoration
Item增删的动画:ItemAnimator -
点击长按等事件需要自己实现
网友评论