美文网首页面试
Android 综合技术面试题

Android 综合技术面试题

作者: 星邪Ara | 来源:发表于2022-04-12 08:50 被阅读0次

    1.1 请谈谈你对 MVC、MVP、MVVM、MVI的理解?

    MVC

    Model:主要用于网络请求、数据库、业务逻辑处理等操作。
    View:用于展示UI,一般采用XML文件进行界面的描述。
    Controller:控制层的重任落在了众多Activity上,Activity需要交割业务逻辑至Model层处理。
    事件从View层流向Controller层,经过Model层处理,然后通知View层更新。

    缺点:

    1. Controller层,也就是activity承担了部分View层的职责,导致该层过于臃肿。
    2. 在MVC中,Model层处理完数据后,直接通知View层更新,因此MV耦合性强。

    MVP

    Model:数据处理层。
    View:视图显示层。
    Presenter:业务逻辑处理层。
    通过接口,V和P间接相互持有引用,P和M间接相互持有引用,但是V和M完全解耦,而且把业务逻辑从activity中移到 P中,减轻了activity的压力。

    缺点:
    当View层足够复杂的时候,View接口会变得很庞大。

    MVVM

    Model:数据处理层。
    View:视图显示层。
    ViewModel:视图模型,通过框架实现View层和Model层的双向绑定。

    优点:

    1. View和Model双向绑定,一方的改变都会影响另一方,开发者不用再去手动修改UI的数据。
    2. 不需要findViewById也不需要butterknife,不需要拿到具体的View去设置数据绑定监听器等等,这些都可以用DataBinding完成。
    3. View和Model的双向绑定是支持生命周期检测的,不会担心页面销毁了还有回调发生,这个由Lifecycle完成。
    4. 不会像MVC一样导致Activity中代码量巨大,也不会像MVP一样出现大量的View和Presenter接口,项目结构更加低耦合。

    缺点:
    由于数据和视图的双向绑定,导致出现问题时不好定位来源,有可能数据问题导致,也有可能业务逻辑中对视图属性的修改导致。

    VMI

    View: Activity 和 Layout XML 文件,与 MVVM 中 View 的概念相同;
    Intent: 定义数据操作,是将数据传到 Model 的唯一来源,相比 MVVM 是新的概念;
    ViewModel: 存储视图状态,负责处理表现逻辑,并将 ViewState 设置给可观察数据容器;
    ViewState: 一个数据类,包含页面状态和对应的数据。

    唯一可信源: 数据只有一个来源(ViewModel),与 MVVM 的思想相同;
    单数据流: View 和 ViewModel 之间只有一个数据流,只有一个地方可以修改数据,确保数据是安全稳定的。并且 View 只需要订阅一个 ViewState 就可以获取所有状态和数据,相比 MVVM 是新的特性;
    响应式: ViewState 包含页面当前的状态和数据,View 通过订阅 ViewState 就可以完成页面刷新,相比于 MVVM 是新的特性。

    优点:
    Mvi模式采用单向流动的设计思路,用户意图Intent通过View层传输到Model,数据处理完毕之后,再返回状态State到View层显示,由此一个循环结束.一个界面的所有东西,都是不断循环在一个闭环内,所以数据的流向都能方便地监测到,哪一环出现问题就能快速地定位到问题所在.

    缺点:
    State 膨胀: 所有视图变化都转换为 ViewState,还需要管理不同状态下对应的数据。实践中应该根据状态之间的关联程度来决定使用单流还是多流;
    内存开销: ViewState 是不可变类,状态变更时需要创建新的对象,存在一定内存开销;
    局部刷新: View 根据 ViewState 响应,不易实现局部 Diff 刷新,可以使用 Flow#distinctUntilChanged() 来刷新来减少不必要的刷新。

    选择:
    1、如果项目简单,没什么复杂性,未来改动也不大的话,那就不要用设计模式或者架构方法,只需要将每个模块封装好,方便调用即可,不要为了使用设计模式或架构方法而使用。
    2、对于偏向展示型的app,绝大多数业务逻辑都在后端,app主要功能就是展示数据,交互等,建议使用mvvm。
    3、对于工具类或者需要写很多业务逻辑app,使用mvp或者mvvm都可。
    4、MVI 与前者的主要区别不在于强调严格的单向数据流,而在于从命令式的开发模式,转变为响应式的开发模式。

    我们并不是说越新潮,越复杂的架构就是最好的,只有合适的架构才是最好的。

    1.2 分别介绍下你所知道Android的几种存储方式?

    网络存储 :一般就是http get或http post 从服务器获取数据,业务数据获取的常用办法。
    SqlLite:将数据缓存到本地数据库,可用于存储大量不经常改变的数据,可配合ContentProvider使用。
    文件存储:将一些不太敏感的数据保存到本地,SharePreference:用XML格式文件存储数据,在data/data/<pa'ka'geName>/shared_prefs下,不支持数据频繁读写,频繁读写会造成数据错乱。
    ContentProvider:四大组件之一,一般配合SqlLite、SharePreference、文件存储使用,支持数据的并发读取。

    1.3 简述下热修复的原理?

    热修复分为三个部分,分别是Java代码部分热修复,Native代码部分热修复,还有资源热修复。

    资源部分热更新直接反射更改所有保存的AssetManager和Resources对象就行(可能需要重启应用)

    Native代码部分也很简单,系统找到一个so文件的路径是根据ClassLoader找的,修改ClassLoader里保存的路径就行(可能需要重启应用)

    Java部分的话目前主流有两种方式,一种是Java派,一种是Native派。

    • java派:通过修改ClassLoader来让系统优先加载补丁包里的类
      代表作有腾讯的tinker,谷歌官方的Instant Run,包括multidex也是采用的这种方案优点是稳定性较好,缺点是可能需要重启应用
    • native派:通过内存操作实现,比如方法替换等
      代表作是阿里的SopHix,如果算上hook框架的话,还有dexposed,epic等等
      优点是即时生效无需重启,缺点是稳定性不好:如果采用方法替换方式实现,假如这个方法被内联/Sharpening优化了,那么就失效了;inline hook则无法修改超短方法。
      热修复后使用反射调用对应方法时可能发生IllegalArgumentException。

    1.4 谈谈如何适配更多机型的?

    1. 一般来说主要针对屏幕适配,最小宽度适配和今日头条density适配
    2. 权限适配,安卓6.0的运行时权限,这里有坑,6.0以前,Vivo有i管家进行权限管理,魅族自带有权限管理,还有其他第三方软件进行权限限制,导致权限不可用
    3. 异形屏幕适配,一般来说都是刘海,水滴,挖孔部分不进行使用或者就直接不管不显示缺失部分,可以满足大部分需求,小部分需求需要使用异形部分的需要按手机型号进行特定适配,官网都有适配方法
    4. 安卓系统适配,及时关注新系统新特性,使情况修改 targetSdk
    5. 语言,Left Right和Start End,这些适配基本不需要太大关注
    6. “和ios一样”,口才或者脑细胞适配,能说服就下班,不能就加班

    1.5 请谈谈你是如何进行多渠道打包的?

    配置gradle实现多渠道打包

    每当应用发布一个新的版本的时候,我们会分发到每一个应用市场中去,比如,360手机助手,小米应用市场,华为应用市场等。为了能够统计每个应用市场的下载量,活跃量我们必须用一个标记来区分这些不同市场分发下去的应用,渠道号也就应运而生。随着渠道的不断增加,需要生成的渠道包也就越来越多。 在打包的过程中,我们一般都是使用gradle来进行的。gradle为我们的打包提高了很多的便利,多渠道打包也可以轻松实现。

    1. 首先在AndroidManifest.xml文件中定义一个metadata
    <meta-data android:name="CHANNEL" android:value="${CHANNEL_VALUE}" />
    

    2.然后在gradle文件中设置一下productFlavors

        android {
            productFlavors {
                xiaomi {
                    manifestPlaceholders =[CHANNEL_VALUE: "xiaomi"]
                }
                _360 {
                    manifestPlaceholders =[CHANNEL_VALUE: "_360"]
                }
                baidu {
                    manifestPlaceholders =[CHANNEL_VALUE: "baidu"]
                }
                wandoujia {
                    manifestPlaceholders =[CHANNEL_VALUE: "wandoujia"]
                }
            }
        }
    

    productFlavors是为了在同一个项目中创建应用的不同版本。具体的配置信息可以看官方说明。

    1. 执行gradle AS就可以将所有的渠道包输出了。

    gradle实现多渠道打包的缺点

    虽然gradle配置多渠道打包很简单,也很方便,但是这种方式存在一个致命的缺陷,那就是费时间。因为AndroidManifest.xml文件被修改过了,所以所有的包都必须重新编译签名。一般来说100个渠道包就要至少一个小时的时间,这一个小时5杯咖啡都不够等的。更要命的是万一哪里需要微调一下代码或者文案,那么不好意思,一切又得重头来。这就很麻烦了,所以有没有什么方法可以快速完成打包呢?我们继续往下看。

    1.6多渠道快速打包

    快速打包方案Version_1.0

    如上所说,我们去到信息只是修改了一下manifest文件里面的一个meta-data的值而已,有没有什么办法可以不需要重新构建代码呢?答案是肯定的。我们可以使用apktool,反编译我们的APK文件。

    apktool d yourApkName build
    

    经过解码后,我们会得到如下文件:



    我们发现我们需要修改的manifest文件就在里面,所以通过命令可以修改下他的内容,然后重新打包,就可以生成一个全新的渠道包了,省去了重新编译构建代码的过程。使用一下Python脚本,将manifest文件里面channel信息进行替换。

        import re
        def replace_channel(channel, manifest):
        pattern = r'(<metadata\s+android:name="channel"\s+android:value=")(\S+)("\s+/>)'
        replacement = r"\g<1>{channel}\g<3>".format(channel=channel)
        return re.sub(pattern, replacement, manifest)
    

    然后通过apktool重新将文件夹打包生成APK。

    apktool b build your_unsigned_apk
    

    最后,使用jarsigner重新签名apk:

    jarsigner -sigalg MD5withRSA -digestalg SHA1 -keystore your_keystore_path -storepass your_storepass -signedjar your_signed_apk, your_unsigned_apk, your_alias
    

    通过这上面的一系列过程,我们可以实现不重新编译构建项目就生成不同的渠道包。这会节省很多的时间。但是随着渠道包增加,重新签名也会占用很大一部分时间,那能不能不重新签名呢?

    分析签名的算法后发现,在打包过程后的META-INF文件夹下面添加空白文件是不会对签名的结果产生影响的。

    所以我们只要像META_INF文件夹里面写入空白的文件来标识渠道号就可以了。
    通过Python脚本像APK文件中写入渠道:

    import zipfile
    zipped = zipfile.ZipFile(your_apk, 'a', zipfile.ZIP_DEFLATED)
    empty_channel_file = "METAINF/mtchannel_{channel}".format(channel=your_channel)
    zipped.write(your_empty_file,empty_channel_file)
    

    执行后会在META-INF文件夹下面生成一个空白文件:
    然后我们在项目中去读取这个空白文件:

        public static String getChannel(Context context) {
            ApplicationInfo appinfo = context.getApplicationInfo();
            String sourceDir = appinfo.sourceDir;
            String ret = "";
            ZipFile zipfile = null;
            try {
                zipfile = new ZipFile(sourceDir);
                Enumeration<?> entries = zipfile.entries();
                while (entries.hasMoreElements()) {
                    ZipEntry entry = ((ZipEntry) entries.nextElement());
                    String entryName = entry.getName();
                    if (entryName.startsWith("mtchannel")) {
                        ret = entryName;
                        break;
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (zipfile != null) {
                    try {
                        zipfile.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            String[] split = ret.split("_");
            if (split != null && split.length >= 2) {
                return
                        ret.substring(split[0].length() + 1);
            } else {
                return "";
            }
        }
    

    这样每生成一个渠道包只需要复制一下APK,然后添加一个空态的文件到META-INF下面就可以了,这样100个渠道包一分钟之内就可以搞定了。

    快速打包方案Version_2.0

    上面的方案基本上已经比较完美的解决我们打包的问题了,然而好景不长,Google在Android 7.0中更新了应用的签名算法-APK Signature Scheme v2,它是一个对全文件进行签名的方案,能提供更快的应用安装时间、对未授权APK文件的更改提供更多保护,在默认情况下,Android Gradle 2.2.0插件会使用APK Signature Scheme v2和传统签名方案来签署你的应用。因为是对全文件进行签名的,所以之前的添加空白文件的方案就没有作用了。

    不过目前这个方案还不是强制性的,我们可以选择在gradle配置文件中将其关闭:

        android {
            defaultConfig { ...}
            signingConfigs {
                release {
                    storeFile file ("myreleasekey.keystore")
                    storePassword "password"
                    keyAlias "MyReleaseKey"
                    keyPassword "password"
                    v2SigningEnabled false
                }
            }
        }
    

    那么新的签名方案对已有的渠道生成方案有什么影响呢?
    下图是新的应用签名方案和旧的签名方案的一个对比:

    新的签名方案会在ZIP文件格式的 Central Directory 区块所在文件位置的前面添加一个APK Signing Block区块,下面按照ZIP文件的格式来分析新应用签名方案签名后的APK包。 整个APK(ZIP文件格式)会被分为以下四个区块:

    1. Contents of ZIP entries(from offset 0 until the start of APK Signing Block)
    2. APK Signing Block
    3. ZIP Central Directory
    4. ZIP End of Central Directory

    新应用签名方案的签名信息会被保存在区块2(APK Signing Block)中, 而区块1(Contents of ZIP entries)、区块3(ZIP Central Directory)、区块4(ZIP End of Central Directory)是受保护的,在签名后任何对区块1、3、4的修改都逃不过新的应用签名方案的检查。

    之前的渠道包生成方案是通过在META-INF目录下添加空文件,用空文件的名称来作为渠道的唯一标识,之前在META-INF下添加文件是不需要重新签名应用的,这样会节省不少打包的时间,从而提高打渠道包的速度。但在新的应用签名方案下META-INF已经被列入了保护区了,向META-INF添加空文件的方案会对区块1、3、4都会有影响,新应用签名方案签署的应用经过我们旧的生成渠道包方案处理后,在安装时会报以下错误:

    Failure
    [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Failed to collect certificates from base.apk:
    META-INF/CERT.SF indicates base.apk is signed using APK Signature Scheme v2,
    but no such signature was found. Signature stripped?]
    

    区块1,3,4是受保护的,任何的修改都会引起签名的不一致,但是区块2是不受保护的,所以能不能在区块2上面找到解决办法呢?首先看一下区块2的文件结构:

    区块2中APK Signing Block是由这几部分组成:2个用来标示这个区块长度的8字节 + 这个区块的魔数(APK Sig Block 42)+ 这个区块所承载的数据(ID-value)。

    我们重点来看一下这个ID-value,它由一个8字节的长度标示+4字节的ID+它的负载组成。V2的签名信息是以ID(0x7109871a)的ID-value来保存在这个区块中,不知大家有没有注意这是一组ID-value,也就是说它是可以有若干个这样的ID-value来组成,那我们是不是可以在这里做一些文章呢?

    对于签名的认证过程是这样的:

    1. 寻找APK Signing Block,如果能够找到,则进行验证,验证成功则继续进行安装,如果失败了则终止安装
    2. 如果未找到APK Signing Block,则执行原来的签名验证机制,也是验证成功则继续进行安装,如果失败了则终止安装

    在校验的时候,检验代码如下:

        public static ByteBuffer findApkSignatureSchemeV2Block(ByteBuffer apkSigningBlock, Result result)
                throws SignatureNotFoundException {
            checkByteOrderLittleEndian(apkSigningBlock);
            // FORMAT:
            // OFFSET DATA TYPE DESCRIPTION
            // * @+0 bytes uint64: size in bytes (excluding this field)
            // * @+8 bytes pairs
            // * @-24 bytes uint64: size in bytes (same as the one above)
            // * @-16 bytes uint128: magic
            ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
            int entryCount = 0;
            while (pairs.hasRemaining()) {
                entryCount++;
                if (pairs.remaining() < 8) {
                    throw new SignatureNotFoundException(
                            "Insufficient data to read size of APK Signing Block entry #" + entryCount);
                }
                long lenLong = pairs.getLong();
                if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
                    throw new SignatureNotFoundException("APK Signing Block entry #" + entryCount +
                            " size out of range: " + lenLong);
                }
                int len = (int) lenLong;
                int nextEntryPos = pairs.position() + len;
                if (len > pairs.remaining()) {
                    throw new SignatureNotFoundException("APK Signing Block entry #" + entryCount +
                            " size out of range: " + len + ", available: " + pairs.remaining());
                }
                int id = pairs.getInt();
                if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {
                    return getByteBuffer(pairs, len - 4);
                }
                result.addWarning(Issue.APK_SIG_BLOCK_UNKNOWN_ENTRY_ID, id);
                pairs.position(nextEntryPos);
            }
            throw new SignatureNotFoundException(
                    "No APK Signature Scheme v2 block in APK Signing Block");
        }
    

    我们可以发现,述代码中关键的一个位置是 if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {return getByteBuffer(pairs, len - 4);},通过源代码可以看出Android是通过查找ID为APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a的ID-value,来获取APK Signature Scheme v2 Block,对这个区块中其他的ID-value选择了忽略。也就是说,在APKSignature Scheme v2中没有看到对无法识别的ID,有相关处理的介绍。所以我们可以通过写入自定义的ID-Value来自定义渠道。

    1.6 MVP中你是如何处理Presenter层以防止内存泄漏的?

    首先 MVP 会出现内存泄漏是因为 Presenter 层持有 View对象,一般我们会把 Activity 做为 View 传递到Presenter,Presenter 持有 View对象,Activity 退出了但是没有回收出现内存泄漏。
    解决办法:

    1. Activity onDestroy() 方法中调用 Presenter 中的方法,把 View 置为 null
    2. 使用 Lifecycle
    3. 使用 MVVM

    1.7 如何计算一张图片所占的内存空间大小?

    通常情况下图片占用内存的大小:图片分辨率X像素点大小。

    api获取方法为bitmap.getByteCount()。

    如果放入res/drawable下,通过BitmapFactory.decodeResource()方法加载不同dpi文件下的同一张图片到内存的大小是不一样(存在分辨率的转换),同时也与设备的dpi大小有关。而放在sd卡、网络或者assert里则是一样的(即使是不通dpi的设备)。图片占用内存大小跟控件大小无关(glide是先拿到控件大小,再去对图片加载做的处理)。

    1.8 有没有遇到64k问题,应该如何解决?

    单个dex文件方法超过64k,基本上都是引用过多的依赖才导致的。
    解决方案:

    1. 导入依赖
    'com.android.support:multidex:1.0.1'
    
    1. defaultConfig增加这个设置
    multiDexEnabled true
    
    1. android下面增加这个设置
    dexOptions {
        incremental true
        javaMaxHeapSize "4g"
    }
    

    以上都是在app的buildl.gradle中设置的,编译。

    1. 打开自定义的Application,继承MultiDexApplication,并重写attachBaseContext方法

    1.9 如何优化 Gradle 的构建速度?

    1. 物理设备:16g+内存、硬盘ssd、高配CPU,最好是超频CPU。5.0GHz那种
    2. 配置:使用高版本AS
        android.injected.testOnly=false
        android.buildCacheDir=buildCacheDir
        org.gradle.caching=true
        # 开启缓存
        android.enableBuildCache=true
        android.enableAapt2=true
        # 配置大内存
        org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
        # 并行编译
        org.gradle.parallel=true
        # 开启孵化模式
        org.gradle.configureondemand=true
        # 守护进程
        org.gradle.daemon=true
        android.enableSeparateAnnotationProcessing=true
        buildTypes{
            debug{
                crunchPngs false
                aaptOptions.cruncherEnabled = false
            }
        }
        dexOptions {
            preDexLibraries true
        }
    

    如果使用了multiDexEnabled ,一定要依赖最新版本的1.0.3版本
    依赖的库不带+号,每次都会自动检查最新版

    1. AS设置,如果已下载好依赖,可以设置离线模式

    以上配置可以加快构建和编译速度。

    1.10 如何获取Android设备唯一ID?

    常用方法:

    • IMEI
      权限(读手机权限)
      可以手动更改(模拟器上) /关闭权限报错
    • Mac地址
      访问WIFI权限
      有些设备没有wifi(没有上网功能)/6.0以上版本返回的都是固定的02:00:00 之类地址
    • ANDROID_ID
      设备首次运行,系统会随机生成一个64位的数字,转换成16进制保存
      手机恢复出厂设置后值有变化/ 有些国产设备不会返回值
    • Serial Number (设备序列号)
      有些国产手机(红米)会出现垃圾数据

    实现思路:

    1. 获取设备的IMEI -->设备的Mac地址-->随机生成的UUID
    2. 拼接在一起,然后用MD5加密
    3. 存储到本地的文件中,隐藏并且存储在App中的SP中
    4. 使用的时候先从SP中读取,没有的再生成,然后把生成的唯一id保存到SP中

    1.11 谈一谈Android P禁用http对我们开发有什么影响?

    • APP改用https请求
    • targetSdkVersion 降到27以下
    • res --> add : network_security_config.xml
    <?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
        <base-config cleartextTrafficPermitted="true" />
    </network-security-config>
    
    AndroidManifest.xml--> application :
    android:networkSecurityConfig="@xml/network_security_config"
    

    1.12 什么是AOP?在Android中它有哪些应用场景?

    aop的实现有静态代理和动态代理,静态代理有静态代理模式,基于Ajc编译器的AspectJ,动态代理有JDK动态代理,在Android的实现是InvicationHandler,需要实现接口,还有CGlib实现方式创建子类来继承源类。

    应用场景有各种状态监测比如登录、网络、权限等。日志埋点,性能分析。

    1.13 什么是MVVM?你是如何将其应用于具体项目中的?

    MVVM架构其实由来已久,在Android中MVVM指代的是Model-View-ViewModel。Model就是数据模型,View一般认为是Activity/Fragment。那么这个ViewModel其实是Google为Android应用程序搭建MVVM架构而设计的一个组件(类)。
    顾名思义,ViewModel可以认为是View与Model之间的一个容器,在没有MVVM之前,我们通常会将View和Model之间的业务处理直接放在Activity/Fragment中进行处理,现在有了ViewModel,可以将相关逻辑放在其中处理,View(Activity/Fragment)只进行数据的展示,而不进行数据的加工处理。
    那么数据在处理或者变化后,如何通知到View呢?这就需要通过Jetpack的另外一个组件LiveData。LiveData可以使得ViewModel中的数据变化后,通知到View,它起到一个桥梁的作用。本质上这是一个观察者模式。

    在Android项目中应用MVVM架构,Google官方建议的做法是使用Jetpack相关组件,这些组件设计的目的就是为了方便开发者搭建MVVM规范的应用程序。

    1.14 请谈谈你是如何实现数据埋点的?

    一、埋点技术

    代码埋点:
    所谓的代码埋点就是在你需要统计数据的地方植入N行代码,统计用户的关键行为。比如你想统计首页某个banner的点击量,上报的数据可以采用KEY-VALUE形式,我们定义 KEY为「CLICK_ADD_BTN」,VALUE的值为点击的次数。当用户点击banner时,banner详情的代码会通过按钮的「回调」来触发执行,程序猿在业务代码执行完后,又加上了统计代码,把「CLICK_ADD_BTN」对应的VALUE加1,banner被统计到了一次使用。

    代码埋点的优点:
    使用者控制精准,可以非常精确地选择什么时候发送数据使用者可以比较方便地设置自定义属性、自定义事件,传递比较丰富的数据到服务端。

    代码埋点的缺点:
    埋点代价比较大,每一个控件的埋点都需要添加相应的代码,不仅工作量大,而且限定了必须是技术人员才能完成;
    更新代价比较大,每一次更新,都需要更新埋点方案,然后通过各个应用市场进行分发,而且有的用户还不一定更新,这样你就获取不到这批用户数据。

    可视化埋点:
    既然代码埋点代价比较大,每一个埋点都需要写代码,那就使用可视化交互手段代替写代码;既然每次代码埋点都需要更新,那就参照现在的很多手游做法,把核心代码和配置、资源分开,每次用户启动app的时候通过网络更新配置和资源。

    可视化埋点优势:
    可视化买点解决了代码埋点埋点代价大和更新代价大两个问题。

    可视化埋点劣势:
    可视化埋点能够覆盖的功能有限,目前并不是所有的控件操作都可以通过这种方案进行定制;

    无埋点:
    可视化埋点先通过界面配置哪些控件的操作数据需要收集;“无埋点”则是先尽可能收集所有控件的操作数据,然后再通过界面配置哪些数据需要在系统里面进行分析,“无埋点”也就是“全埋点”的意思。

    无埋点的优点:
    可视化埋点只能收集到你埋点以后的数据,如果你想对某个按钮进行点击分析,则只能分析增加可视化埋点以后的数据,之前的数据你收集不到,而无埋点在你部署SDK的时候数据就一直在收集。
    因为无埋点对页面所有元素进行埋点,那么这个页面每个元素被点击的概率你也就知道,对点击概率比较大的元素可以进行深入分析。

    无埋点的缺点:
    由于无埋点方案所有的元素数据都收集,会给数据传输和服务器带来较大的压力。

    二、数据埋点方式

    1、公司研发在自己的产品当中注入统计代码,搭建相应的后台查询,这种代价比较大。
    2、集成第三方统计的SDK,比如友盟、百度移动统计、Sensors Data、GrowingIO、Talking Data等。

    三、如何进行数据埋点

    1、明确目标
    经常有人问我说我要获取那些数据来进行数据分析,其实这个问题不应该问别人,应该问问你自己,你是想用这个数据干什么,如果你想绘制基础的人群画像你就需要获取用户机型、网络类型、操作系统,IP地域等数据;如果你想分析每一个注册转化率,你就需要获取每一个步骤的点击次数,然后制作成漏斗,看那一步转化率出现了问题;
    目的不一样,获取的数据也不一样,使用的埋点技术也不一样,我们无论做什么事情都不能忘了我们的目的!
    2、获取相应数据
    业务不同,目的不同获取的数据也不同,这里我只说一些比较共性的数据。
    2.1、产品各个渠道下载量
    这个可以用第三方数据统计工具来进行,这样我们可以知道我们产品着重在那个渠道进行推广。
    2.2、产品活跃状态分析
    产品活跃状态监控,留存分析、流失分析、新增变化等,次日留存率、七日留存率、月留存率,尤其对于处于成长期的产品而已,这个指标很重要,如果留存率比较低,说明你的产品有问题,这个时候你就需要进行用户调研,找到流失的问题,否则大面积拉新,只能拉多少死多少,至于留存率、新增的变化这些数据,我们也可以借助第三方统计工具来进行。
    2.3、事件分析
    比如你想统计某个页面的UV、PV、元素的点击量、用户停留时长、页面跳出率等数据指标,可以选择代码埋点和可视化埋点等前端埋点解决方案。当某个页面的UV很高,但是跳出率也很高,说明页面有问题,你就要好好想想页面的问题出在什么地方。
    2.4、基本信息获取
    基本信息获取,例如机型、网络类型、操作系统,IP地域等,绘制基础用户人群画像,这种分析出来的用户画像颗粒度比较大,如果想更精准的进行用户画像可以结合推荐系统,来获取用户的兴趣指标,以及用户操作行为等数据来进行更精准的用户画像,从而为产品运营和产品设计提供参考,可以借助第三方统计工具和自定义埋点的方式进行数据的收集。
    2.5、漏斗模型
    对于产品的关键路径一定要进行漏斗模型分析,比如注册路径,从用户输入注册手机号到注册成功,中间可能会有几个步骤,如果100个人注册,最后只有一个人注册成功,那么求运营同学心里的阴影面积。还有电商的购买下单路径,从浏览商品到最后下单购买成功,每一个步骤的转化率是多少,对于漏的比较多的那个步骤我们肯定要着重关注,分析原因。这个可以技术研发进行埋点,获取更精确的数据,比如下图的埋点表。

    如何给app客户端进行埋点?

    领导说,APP需要加一下统计,你负责搞定
    研发说,APP需要统计哪些地方,你列一下埋点需求
    研发说,APP的数据统计SDK用哪家的?你选好了注册一下、运营说,咱们的APP都能看哪些数据?平台在哪?怎么查首页的UV?
    数据分析是一个很复杂的工作,很多人在谈如何挖掘数据,做用户画像,设计数据漏斗,如何负责用户生命周期管理,但发现很多人却卡在了数据分析的第一步,那就是如何做数据埋点。

    首页陈峰老师先明确下完成一个APP数据埋点的几个步骤:

    • 注册一家统计网站
    • 新建应用
    • 获取KEY和SDK代码包
    • 将埋点需求和SDK包发给研发
    • 自定义埋点需求完善
    • 研发开发并完成APP上线
    • 在后台查看数据
      1、注册账号
      建议用公司邮箱或者公用邮箱注册,别用自己的私人邮箱和手机号码,后续一旦有交接和工作变动时会比较麻烦。
      2、新建应用
      登录后一般都有“新建应用”,可以选标准统计,大部分APP都选这个。游戏的app另说。
      名称写自己app的名称,分类自己选1个。选错了也不影响。
      平台根据情况自己选。后期我们看数据和埋点都是ios和安卓分开的,所以你如果2个端都做,就一起都选上。
      描述可选,不用填。
      3、获取KEY和SDK代码包
      完成后可以得到2个APPKEY。分别是ios和安卓的。
      这里的appkey很重要,你可以下载了给研发,也可以稍后让研发自己登录进来自己下载。
      ios和安卓是分开2个独立的,后续埋点和看数据都是分开的。这个切记。
      这时候,重点来了。
      此时,如果我们只想看 APP的活跃用户,留存用户,下载量。用户地域分布,渠道分布,那么其实就够了。
      4、将埋点需求和SDK包发给研发
      你这时候,就把刚才获得的appkey和sdk包的下载地址,发给研发。或者直接把账号和密码发给研发。然后告诉研发,集成下百度移动统计的SDK包。这样发版后,就可以看到大部分数据了。
      如下的数据都可以看到:

    5、自定义事件完善
    比如我们想看页面里面 注册 搜索按钮,顶部banner,底部 首页和 我的 2个导航条的点击量。
    一个埋点事件对应1个按钮或者一个页面或者一个弹层。你来定义。
    如果埋点比较多,你也可以批量添加。批量添加的时候,您需要下载excel模板,按照要求填写好,上传进来即可。具体一看便知。
    添加完成后就可以把这个列表导出或者人肉复制出来一个表格。发给研发。并附上你的原型图。做好对应关系标注。
    6、研发开发并完成APP上线
    完成上面几步后,研发哥哥就可以看懂进入第7步研发阶段了。
    7、在后台查看数据
    上线后就可以看到数据了。大部分数据一般隔天更新。

    三、埋点后能看到什么数据

    上面提到,按照步骤完成数据分析sdk集成和自定义事件后,就可以看到数据了。
    不添加自定义事件,可以看到基础数据,添加后,可以看到更细节的按钮,页面等点击数据。
    查看自定义事件埋点数据,还是进入刚才的“事件分析”页面,点击对应埋点即可看到数据。
    可以筛选时间段。
    除了这些外,如果你还想看 几个页面之间的转化路径和数据漏斗。那还需要添加“转化分析”。
    添加转化分析后,可以看到例如: 进入首页-点击注册按钮进入注册成功页 这几步的转化率和流失率。会自动生成一个转化分析图。当然你也可以分别看这几个页面的数据,自己去分析汇总。
    进阶的方法还有把事件埋点配合转化分析、访问路径、转化漏斗等工具使用,从点到面地了解用户的使用行为、APP存在的问题。

    产品核心指标一般包含:

    1. 产品规模
      1.1 用户数据。如新增用户、用户类型分布、活跃用户、沉默用户、启动次数、版本分析等。
      1.2 业务数据。这个与具体业务有关,如问答社区的问题数,回答数,全网热度,浏览量;如对含交易平台的交易量,交易额,客单价,转化率,利润等。
    2. 产品运营
      2.1 流量数据。pv,uv,dau,mau,留存分析(次日留存,7日留存, 用户新鲜度), 流失分析(日周月、自然流失、回归流失),
      2.2 渠道数据。渠道流量,渠道转换率,渠道评价,渠道时段详情,渠道质量(渠道活跃用户/渠道流量)等。

    1.15 假如让你实现断点上传功能,你认为应该怎样去做?

    分2种情况:
    分块上传:多线程读取本地文件指定区域流上传,header带有上传位置标记,服务器接收到多个上传请求会生成多个上传临时文件接收,最后合并成完整文件。(续传如下)

    正常续传:本地请求上传,服务器响应是否未完成的临时文件和文件byte,本地收到就接着指定位置读流上传。

    其中会涉及块的数据校验等

    1.16 webp和svg格式的图片各自有什么特点?应该如何在Android中使用?

    一、
    svg格式
    svg是矢量图,这意味着svg图片由直线和曲线以及绘制它们的方法组成。当你放大一个svg图片的时候,你看到的还是线和曲线,而不会出现像素点。svg图片在放大时,不会失真,所以它非常适合用来绘制企业Logo、Icon

    SVG格式特点:
    1、SVG 指可伸缩矢量图形 (Scalable Vector Graphics)
    2、SVG 用来定义用于网络的基于矢量的图形
    3、SVG 使用 XML 格式定义图形
    4、SVG 图像在放大或改变尺寸的情况下其图形质量不会有所损失
    5、SVG 是万维网联盟的标准
    6、SVG 与诸如 DOM和 XSL 之类的W3C标准是一个整体

    二、
    webp格式
    webp是谷歌开发的一种新图片格式,是同时支持有损和无损压缩的、使用直接色的、点阵图。

    1.17 说说你是如何进行单元测试的?以及如何应用在MVP和MVVM中?

    单元测试库 junit mockito Rebolectric
    说下mvp工程中的测试方法
    测试主要有 三大部分

    1. 普通工具类 使用junit 直接测试
    2. mvp的p使用@mock标注view的接口,初始化真正的p,直接调用p的方法看看verify view的某些方法是否按照预期被调用
    3. mvp的v用rebolectric去setup一个Activity,然后用这个库找到界面上的按钮,或者触发生命周期(onStart),判断一下当前界面的某些view是否被显示或者TextView的值或者Dialog是否显示,Toast是否弹出错误
    4. 还有网络部分的测试,可以直接使用junit进行测试,判断下返回值是否符合预期

    1.18 对于GIF图片加载有什么思路和建议?

    gif图实际上就是多帧合并的图

    参考Fresco内部实现:
    1,View层使用一个Drawable,包含bitmap,并依据gif的信息不断的更新并绘制bitmap
    2,C层提供api功能,例如:输入gif数据流,提供解析gif信息、更新bitmap等功能

    1.19 为什么要将项目迁移到AndroidX?如何进行迁移?

    现在Android官方支持的最低系统版本已经是4.0.1,对应的API版本号是15。support-v4、appcompat-v7库也不再支持那么久远的系统了,但是它们的名字却一直保留了下来,虽然它们现在的实际作用已经对不上当初命名的原因了。

    那么很明显,Android团队也意识到这种命名已经非常不合适了,于是对这些API的架构进行了一次重新的划分,推出了AndroidX。因此,AndroidX本质上其实就是对Android Support Library进行的一次升级,升级内容主要在于以下两个方面。

    第一,包名。之前Android Support Library中的API,它们的包名都是在android.support.下面的,而AndroidX库中所有API的包名都变成了在androidx.下面。这是一个很大的变化,意味着以后凡是android.包下面的API都是随着Android操作系统发布的,而androidx.包下面的API都是随着扩展库发布的,这些API基本不会依赖于操作系统的具体版本。

    第二,命名规则。吸取了之前命名规则的弊端,AndroidX所有库的命名规则里都不会再包含具体操作系统API的版本号了。比如,像appcompat-v7库,在AndroidX中就变成了appcompat库。

    一个AndroidX完整的依赖库格式如下所示:

    implementation 'androidx.appcompat:appcompat:1.0.2'
    
    

    了解了AndroidX是什么之后,现在你应该放轻松了吧?它其实并不是什么全新的东西,而是对Android SupportLibrary的一次升级。因此,AndroidX上手起来也没有任何困难的地方,比如之前你经常使用的RecyclerView、ViewPager等等库,在AndroidX中都会有一个对应的版本,只要改一下包名就可以完全无缝使用,用法方面基本上都没有任何的变化。

    但是有一点需要注意,AndroidX和Android Support Library中的库是非常不建议混合在一起使用的,因为它们可能会产生很多不兼容的问题。最好的做法是,要么全部使用AndroidX中的库,要么全部使用Android Support Library中的库。

    而现在Android团队官方的态度也很明确,未来都会为AndroidX为主,Android Support Library已经不再建议使用,并会慢慢停止维护。另外,从Android Studio 3.4.2开始,我发现新建的项目已经强制勾选使用AndroidX架构了。

    相关文章

      网友评论

        本文标题:Android 综合技术面试题

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