Android

作者: 34sir | 来源:发表于2019-01-29 10:08 被阅读18次

    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()的话会销毁视图,即调用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)

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

    setWebViewClientsetWebChromeClient

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

    与JS的交互

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

    自定义View和动画

    设计模式

    开源框架对比

    RecyclerView

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

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

    相关文章

      网友评论

          本文标题:Android

          本文链接:https://www.haomeiwen.com/subject/ekieyftx.html