Android

Activity Window View之间的关系

关系.png

View怎么绘制到页面上?
发起自ViewRoot 先后经历onMeasure onLayout onDraw

Handler消息机制

主线程创建Handler 工作线程拿到其引用并发送消息 主线程的MQ插入此消息 主线程的Looper查看是否有新到消息 有就交由主线程的Handler处理 完成线程切换

ContentProvider

作用

进程间数据共享 即跨进程通信

原理

Binder进程间通信结合匿名共享内存(传输大数据)

启动步骤

  • 调用者通过URI访问目标Provider
  • 通过URI请求AMS返回访问目标Provider的代理对象
  • AMS创建一个新的应用进程 启动Provider组件
  • Provider将自己发布到AMS AMS将它到代理对象返回给调用者

主要方法

insert delete update query(增删改查)

ContentResolver

ContentProvider 不会与外界直接交互 而是通过ContentResolver类

作用

统一管理ContentProvider之间的p-【操作

  • 通过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()的话会销毁视图,即调用onDestoryViewonCreateView 等一系列生命周期
  • add()和 replace()不要在同一个阶级的FragmentManager里混搭使用

使用场景

  • 很高概率会使用当前的Fragment 建议使用show和hide 可以提高性能
  • 如果有很多图片 建议使用replace 利于配合图片框架回收内存

onHiddenChanged
当使用add()+show(),hide()跳转新的Fragment时 旧的Fragment回调onHiddenChanged() 不会回调onStop等生命周期方法 新的Fragment则不会回调onHiddenChanged

FragmentManager栈视图

栈关系图.png

对于宿主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()方法,计算对象的属性值并将其封装成一个新对象返回(包括 位移 渐变色等)

事件分发机制

事件分发.png

onTouch对比onTouchEvent

onTouch的优先级高于onTouchEvent 如果onTouch返回false 则onTouchEvent执行 否则不执行(onTouch消费了事件)

插件化

  • 插 件 中 ClassLoader 的问题;
    DexClassLoader可以加载jar/apk/dex 可以加载未安装的apk; PathClassLoader只能加载已经安装过的apk
    替换宿主activity的classloader目的是 加载插件apk的class

  • 插件中的资源文件访问问题;
    AssetManager实例并且反射调用addAssetPath方法

  • 插件中 Activity 组件的生命周期问题;
    封装一个instrumentation 通过反射 替换掉宿主的 (InstrumentationWrapper)
    instrumentation 可有跟踪activity以及application的生命周期 单元测试中用到

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级别的权限
    例如系统自带应用的代码:


        

        

开放部分URI给其他应用访问 参考原生ContactsProvider2(注意path-permission)


            
            
            
            
        

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+消息队列
  • 执行完会自动结束

使用场景

几个子任务 子任务按顺序执行 如果自己写线程需要手动控制 并且正常会是后台任务需要配合Service 所以才有IntentService

源码分析

  • 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 {

    public void attachView(V view);

    public void detachView();
}

MvpBasePresenter 重点实现以下几个方法:

  • attachView
  • detachView
  • getView
  • setModelData
  • getModelData
public class MvpBasePresenter implements MvpPresenter {
    protected Context mContext;
    private WeakReference 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(view);
    }

    /**
     * Get the attached view. You should always call {@link #isViewAttached()} to check if the view
     * is
     * attached to avoid NullPointerExceptions.
     *
     * @return null, 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

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);
        }

setWebViewClientsetWebChromeClient

  • setWebViewClient主要用于处理webView的控制问题,如加载、关闭、错误处理等
  • setWebChromeClient主要处理js对话框、图标、页面标题等

与JS的交互

  • @JavascriptInterface 注册方法
  • addJavascriptInterface(jsOperation, "js_operation"); 注册方法对象

自定义View和动画

设计模式

开源框架对比

RecyclerView

  • 优点
    高度解耦:设置它提供的不同LayoutManager,ItemDecoration , ItemAnimator实现效果
    显示的方式:LayoutManager
    Item间的间隔:ItemDecoration
    Item增删的动画:ItemAnimator

  • 点击长按等事件需要自己实现

Android 内存以及进程管理

  • 每个app的进程都是从zygote fork出来的 每个进程的Dalvik Heap都有一个限制的虚拟内存的范围 超过就会引发OOM
  • app退出 进程不会被立即杀死 也不会做交换内存的操作 而是被缓存起来 在内存不足的时候会触发LowMemory Killer 根据进程的优先级以及LRU算法来kill相应的应用 进程退出后台后优先级会明显降低
  • 对于前台进程和后台进程 ART 采用的回收算法不同(Dalvik 采用的是标记清除)
    前台进程要求响应度高 所以对GC效率要求高 所以采用的是标记清除
    后台进程对GC效率要求低 但是对内存碎片要求高 所以采用的标记整理
  • GC触发时机
    主要由系统控制
    1⃣️ 尝试在堆上分配内存 但是内存不足时会触发
    2⃣️ 分配大对象内存 比如Bitmap 会频繁GC
    3⃣️ 手动System.gc

内存优化

内存泄漏

  • Context context.getApplicationContext()
  • Handler这类 静态内部类+ WeakReference
  • 属性动画 及时cancel 动画会持有view

内存占用

  • 使用轻量级的数据结构
    比如ArrayMap SparseArray
  • 压缩Bitmap inSampleSize
    大图分块加载
    Bitmap 及时回收 recycle+System.gc
    BitMap利用LRU算法 进行缓存处理

内存抖动

充分利用线程池以及LRU算法

  • ListView ViewHolder 复用ContentView
  • 线程池线程复用
  • 避免循环中创建对象

RelativeLayout对比LinearLayout

RelativeLayout会让子View调用两次onMeasure
LinearLayout在有weight 的情况下 子View也会调用两次onMeasure
RelativeLayout中尽量使用padding代替margin

冷启动vs热启动

  • 冷启动
    应用启动时 后台没有此应用的进程 系统会重新创建一个进程分配给这个应用
    会创建和初始化Application
  • 热启动
    后台已经存在该进程(比如按下返回键 Home键等) 不需要创建Application

优化冷启动:

  • 减少Application中的耗时操作以及业务逻辑
  • 将第一个页面设置成透明

android:clearTaskOnLaunch 对比android:finishTaskOnLaunch

  • android:clearTaskOnLaunch
    标记是否从task清除根Activity之外的Activity 如果引用了其他应用的设置了allowTaskReparenting属性为“true”的Activity 他们会重新宿主到共同affinity的task中
  • android:finishTaskOnLaunch
    返回这个task时 只销毁当前的activity 其他的不会销毁

dex加载原理

https://www.jianshu.com/writer#/notebooks/22084316/notes/42197074/preview

JVM DVM ART 区别

https://www.jianshu.com/p/047d5b00ff7a

DVM与JVM的区别

  • JVM 基于栈 需要去栈中读写数据 需要的指令更多 导致速度变慢
    DVM 基于寄存器 不需要大量的出入栈指令
  • 执行的字节码不同
    JVM 中 java类被编译成一个或多个.class文件 并且打包成.jar 然后获取相应的字节码
    DVM 会用dx把所有的class文件打包成一个.dex文件 然后读区指令和数据 .dex将所有的信息整合到了一起 加快了速度
  • DVM 允许在有限的内存中同时运行多个进程
  • DVM 由Zygote 创建并初始化
    Zygote 是一个DVM进程 同时用来创建和初始化其他DVM进程 每当系统需要一个应用程序 Zygote就会fork自身 快速的创建和初始化一个DVM实例 所有的DVM实例会和Zygote共享一块内存 节省内存开销
  • DVM 有共享机制
    DVM拥有预加载-共享机制 不同的应用运行时可以共享相同的类 效率更高 JVM彼此独立

ART与DVM的区别

Android 5.0以后 ART替换Dalvik

  • ART中 AOT替换JIT 将字节码预编译成机器码并存储在本地 不需要每次运行都执行编译 大大增加效率
    AOT的两个缺点 1⃣️ 应用安装时间变长 2⃣️ 预编译的机器码需要额外的存储空间 为了解决这两个问题 7.0版本的ART加入JIT作为AOT的补充 将热点代码编译成机器码 缩短安装时间 节省内存

  • DVM为32位CPU设计 ART兼容64位和32位

  • ART对垃圾回收机制进行了改进 更频繁的执行并行垃圾回收 将GC暂停有2次减少到1一次

  • ART运行时堆空间划分和DVM不同

  • ART垃圾回收
    DVM采用的是标记-清除的算法 ART改进了改算法 并且使用了多种垃圾收集器
    1⃣️ CMS 一种获取最短收集暂停时间为目标的收集器 采用了标记-清除算法 完整的堆垃圾收集器
    2⃣️ CPMS 部分完整的堆垃圾收集器
    3⃣️ CSMS 粘性收集器 基于分代垃圾收集 只能释放上次GC以来分配的对象 这个收集器 比完整或者部分完整的垃圾收集器扫描的更加频繁 因为它更快并且暂停时间更短
    4⃣️ Marksweep + Semispace 非并发的GC 复制GC用于堆转换 以及齐性空间压缩 (碎片整理)

LruCache DiskLruCache

  • LruCache
    Android提供的使用最近最少使用算法的缓存类 内部基于LinkedHashMap实现
    原理是把最近最少使用的数据清除掉 没有真正释放内存 只是将数据从map中清除掉

  • DiskLruCache
    和LruCache思想一致 不过操作的是本地文件实体

你可能感兴趣的:(Android)