Android 綜合技術(shù)面試題

1.1 請(qǐng)談?wù)勀銓?duì) MVC、MVP、MVVM、MVI的理解?

MVC

Model:主要用于網(wǎng)絡(luò)請(qǐng)求、數(shù)據(jù)庫(kù)、業(yè)務(wù)邏輯處理等操作。
View:用于展示UI,一般采用XML文件進(jìn)行界面的描述。
Controller:控制層的重任落在了眾多Activity上,Activity需要交割業(yè)務(wù)邏輯至Model層處理。
事件從View層流向Controller層,經(jīng)過(guò)Model層處理,然后通知View層更新。

缺點(diǎn):

  1. Controller層,也就是activity承擔(dān)了部分View層的職責(zé),導(dǎo)致該層過(guò)于臃腫。
  2. 在MVC中,Model層處理完數(shù)據(jù)后,直接通知View層更新,因此MV耦合性強(qiáng)。

MVP

Model:數(shù)據(jù)處理層。
View:視圖顯示層。
Presenter:業(yè)務(wù)邏輯處理層。
通過(guò)接口,V和P間接相互持有引用,P和M間接相互持有引用,但是V和M完全解耦,而且把業(yè)務(wù)邏輯從activity中移到 P中,減輕了activity的壓力。

缺點(diǎn):
當(dāng)View層足夠復(fù)雜的時(shí)候,View接口會(huì)變得很龐大。

MVVM

Model:數(shù)據(jù)處理層。
View:視圖顯示層。
ViewModel:視圖模型,通過(guò)框架實(shí)現(xiàn)View層和Model層的雙向綁定。

優(yōu)點(diǎn):

  1. View和Model雙向綁定,一方的改變都會(huì)影響另一方,開(kāi)發(fā)者不用再去手動(dòng)修改UI的數(shù)據(jù)。
  2. 不需要findViewById也不需要butterknife,不需要拿到具體的View去設(shè)置數(shù)據(jù)綁定監(jiān)聽(tīng)器等等,這些都可以用DataBinding完成。
  3. View和Model的雙向綁定是支持生命周期檢測(cè)的,不會(huì)擔(dān)心頁(yè)面銷(xiāo)毀了還有回調(diào)發(fā)生,這個(gè)由Lifecycle完成。
  4. 不會(huì)像MVC一樣導(dǎo)致Activity中代碼量巨大,也不會(huì)像MVP一樣出現(xiàn)大量的View和Presenter接口,項(xiàng)目結(jié)構(gòu)更加低耦合。

缺點(diǎn):
由于數(shù)據(jù)和視圖的雙向綁定,導(dǎo)致出現(xiàn)問(wèn)題時(shí)不好定位來(lái)源,有可能數(shù)據(jù)問(wèn)題導(dǎo)致,也有可能業(yè)務(wù)邏輯中對(duì)視圖屬性的修改導(dǎo)致。

VMI

View: Activity 和 Layout XML 文件,與 MVVM 中 View 的概念相同;
Intent: 定義數(shù)據(jù)操作,是將數(shù)據(jù)傳到 Model 的唯一來(lái)源,相比 MVVM 是新的概念;
ViewModel: 存儲(chǔ)視圖狀態(tài),負(fù)責(zé)處理表現(xiàn)邏輯,并將 ViewState 設(shè)置給可觀察數(shù)據(jù)容器;
ViewState: 一個(gè)數(shù)據(jù)類(lèi),包含頁(yè)面狀態(tài)和對(duì)應(yīng)的數(shù)據(jù)。

唯一可信源: 數(shù)據(jù)只有一個(gè)來(lái)源(ViewModel),與 MVVM 的思想相同;
單數(shù)據(jù)流: View 和 ViewModel 之間只有一個(gè)數(shù)據(jù)流,只有一個(gè)地方可以修改數(shù)據(jù),確保數(shù)據(jù)是安全穩(wěn)定的。并且 View 只需要訂閱一個(gè) ViewState 就可以獲取所有狀態(tài)和數(shù)據(jù),相比 MVVM 是新的特性;
響應(yīng)式: ViewState 包含頁(yè)面當(dāng)前的狀態(tài)和數(shù)據(jù),View 通過(guò)訂閱 ViewState 就可以完成頁(yè)面刷新,相比于 MVVM 是新的特性。

優(yōu)點(diǎn):
Mvi模式采用單向流動(dòng)的設(shè)計(jì)思路,用戶意圖Intent通過(guò)View層傳輸?shù)組odel,數(shù)據(jù)處理完畢之后,再返回狀態(tài)State到View層顯示,由此一個(gè)循環(huán)結(jié)束.一個(gè)界面的所有東西,都是不斷循環(huán)在一個(gè)閉環(huán)內(nèi),所以數(shù)據(jù)的流向都能方便地監(jiān)測(cè)到,哪一環(huán)出現(xiàn)問(wèn)題就能快速地定位到問(wèn)題所在.

缺點(diǎn):
State 膨脹: 所有視圖變化都轉(zhuǎn)換為 ViewState,還需要管理不同狀態(tài)下對(duì)應(yīng)的數(shù)據(jù)。實(shí)踐中應(yīng)該根據(jù)狀態(tài)之間的關(guān)聯(lián)程度來(lái)決定使用單流還是多流;
內(nèi)存開(kāi)銷(xiāo): ViewState 是不可變類(lèi),狀態(tài)變更時(shí)需要?jiǎng)?chuàng)建新的對(duì)象,存在一定內(nèi)存開(kāi)銷(xiāo);
局部刷新: View 根據(jù) ViewState 響應(yīng),不易實(shí)現(xiàn)局部 Diff 刷新,可以使用 Flow#distinctUntilChanged() 來(lái)刷新來(lái)減少不必要的刷新。

選擇:
1、如果項(xiàng)目簡(jiǎn)單,沒(méi)什么復(fù)雜性,未來(lái)改動(dòng)也不大的話,那就不要用設(shè)計(jì)模式或者架構(gòu)方法,只需要將每個(gè)模塊封裝好,方便調(diào)用即可,不要為了使用設(shè)計(jì)模式或架構(gòu)方法而使用。
2、對(duì)于偏向展示型的app,絕大多數(shù)業(yè)務(wù)邏輯都在后端,app主要功能就是展示數(shù)據(jù),交互等,建議使用mvvm。
3、對(duì)于工具類(lèi)或者需要寫(xiě)很多業(yè)務(wù)邏輯app,使用mvp或者mvvm都可。
4、MVI 與前者的主要區(qū)別不在于強(qiáng)調(diào)嚴(yán)格的單向數(shù)據(jù)流,而在于從命令式的開(kāi)發(fā)模式,轉(zhuǎn)變?yōu)轫憫?yīng)式的開(kāi)發(fā)模式。

我們并不是說(shuō)越新潮,越復(fù)雜的架構(gòu)就是最好的,只有合適的架構(gòu)才是最好的。

1.2 分別介紹下你所知道Android的幾種存儲(chǔ)方式?

網(wǎng)絡(luò)存儲(chǔ) :一般就是http get或http post 從服務(wù)器獲取數(shù)據(jù),業(yè)務(wù)數(shù)據(jù)獲取的常用辦法。
SqlLite:將數(shù)據(jù)緩存到本地?cái)?shù)據(jù)庫(kù),可用于存儲(chǔ)大量不經(jīng)常改變的數(shù)據(jù),可配合ContentProvider使用。
文件存儲(chǔ):將一些不太敏感的數(shù)據(jù)保存到本地,SharePreference:用XML格式文件存儲(chǔ)數(shù)據(jù),在data/data/<pa'ka'geName>/shared_prefs下,不支持?jǐn)?shù)據(jù)頻繁讀寫(xiě),頻繁讀寫(xiě)會(huì)造成數(shù)據(jù)錯(cuò)亂。
ContentProvider:四大組件之一,一般配合SqlLite、SharePreference、文件存儲(chǔ)使用,支持?jǐn)?shù)據(jù)的并發(fā)讀取。

1.3 簡(jiǎn)述下熱修復(fù)的原理?

熱修復(fù)分為三個(gè)部分,分別是Java代碼部分熱修復(fù),Native代碼部分熱修復(fù),還有資源熱修復(fù)。

資源部分熱更新直接反射更改所有保存的AssetManager和Resources對(duì)象就行(可能需要重啟應(yīng)用)

Native代碼部分也很簡(jiǎn)單,系統(tǒng)找到一個(gè)so文件的路徑是根據(jù)ClassLoader找的,修改ClassLoader里保存的路徑就行(可能需要重啟應(yīng)用)

Java部分的話目前主流有兩種方式,一種是Java派,一種是Native派。

  • java派:通過(guò)修改ClassLoader來(lái)讓系統(tǒng)優(yōu)先加載補(bǔ)丁包里的類(lèi)
    代表作有騰訊的tinker,谷歌官方的Instant Run,包括multidex也是采用的這種方案優(yōu)點(diǎn)是穩(wěn)定性較好,缺點(diǎn)是可能需要重啟應(yīng)用
  • native派:通過(guò)內(nèi)存操作實(shí)現(xiàn),比如方法替換等
    代表作是阿里的SopHix,如果算上hook框架的話,還有dexposed,epic等等
    優(yōu)點(diǎn)是即時(shí)生效無(wú)需重啟,缺點(diǎn)是穩(wěn)定性不好:如果采用方法替換方式實(shí)現(xiàn),假如這個(gè)方法被內(nèi)聯(lián)/Sharpening優(yōu)化了,那么就失效了;inline hook則無(wú)法修改超短方法。
    熱修復(fù)后使用反射調(diào)用對(duì)應(yīng)方法時(shí)可能發(fā)生IllegalArgumentException。

1.4 談?wù)勅绾芜m配更多機(jī)型的?

  1. 一般來(lái)說(shuō)主要針對(duì)屏幕適配,最小寬度適配和今日頭條density適配
  2. 權(quán)限適配,安卓6.0的運(yùn)行時(shí)權(quán)限,這里有坑,6.0以前,Vivo有i管家進(jìn)行權(quán)限管理,魅族自帶有權(quán)限管理,還有其他第三方軟件進(jìn)行權(quán)限限制,導(dǎo)致權(quán)限不可用
  3. 異形屏幕適配,一般來(lái)說(shuō)都是劉海,水滴,挖孔部分不進(jìn)行使用或者就直接不管不顯示缺失部分,可以滿足大部分需求,小部分需求需要使用異形部分的需要按手機(jī)型號(hào)進(jìn)行特定適配,官網(wǎng)都有適配方法
  4. 安卓系統(tǒng)適配,及時(shí)關(guān)注新系統(tǒng)新特性,使情況修改 targetSdk
  5. 語(yǔ)言,Left Right和Start End,這些適配基本不需要太大關(guān)注
  6. “和ios一樣”,口才或者腦細(xì)胞適配,能說(shuō)服就下班,不能就加班

1.5 請(qǐng)談?wù)勀闶侨绾芜M(jìn)行多渠道打包的?

配置gradle實(shí)現(xiàn)多渠道打包

每當(dāng)應(yīng)用發(fā)布一個(gè)新的版本的時(shí)候,我們會(huì)分發(fā)到每一個(gè)應(yīng)用市場(chǎng)中去,比如,360手機(jī)助手,小米應(yīng)用市場(chǎng),華為應(yīng)用市場(chǎng)等。為了能夠統(tǒng)計(jì)每個(gè)應(yīng)用市場(chǎng)的下載量,活躍量我們必須用一個(gè)標(biāo)記來(lái)區(qū)分這些不同市場(chǎng)分發(fā)下去的應(yīng)用,渠道號(hào)也就應(yīng)運(yùn)而生。隨著渠道的不斷增加,需要生成的渠道包也就越來(lái)越多。 在打包的過(guò)程中,我們一般都是使用gradle來(lái)進(jìn)行的。gradle為我們的打包提高了很多的便利,多渠道打包也可以輕松實(shí)現(xiàn)。

  1. 首先在AndroidManifest.xml文件中定義一個(gè)metadata
<meta-data android:name="CHANNEL" android:value="${CHANNEL_VALUE}" />

2.然后在gradle文件中設(shè)置一下productFlavors

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

productFlavors是為了在同一個(gè)項(xiàng)目中創(chuàng)建應(yīng)用的不同版本。具體的配置信息可以看官方說(shuō)明。

  1. 執(zhí)行g(shù)radle AS就可以將所有的渠道包輸出了。

gradle實(shí)現(xiàn)多渠道打包的缺點(diǎn)

雖然gradle配置多渠道打包很簡(jiǎn)單,也很方便,但是這種方式存在一個(gè)致命的缺陷,那就是費(fèi)時(shí)間。因?yàn)锳ndroidManifest.xml文件被修改過(guò)了,所以所有的包都必須重新編譯簽名。一般來(lái)說(shuō)100個(gè)渠道包就要至少一個(gè)小時(shí)的時(shí)間,這一個(gè)小時(shí)5杯咖啡都不夠等的。更要命的是萬(wàn)一哪里需要微調(diào)一下代碼或者文案,那么不好意思,一切又得重頭來(lái)。這就很麻煩了,所以有沒(méi)有什么方法可以快速完成打包呢?我們繼續(xù)往下看。

1.6多渠道快速打包

快速打包方案Version_1.0

如上所說(shuō),我們?nèi)サ叫畔⒅皇切薷牧艘幌耺anifest文件里面的一個(gè)meta-data的值而已,有沒(méi)有什么辦法可以不需要重新構(gòu)建代碼呢?答案是肯定的。我們可以使用apktool,反編譯我們的APK文件。

apktool d yourApkName build

經(jīng)過(guò)解碼后,我們會(huì)得到如下文件:



我們發(fā)現(xiàn)我們需要修改的manifest文件就在里面,所以通過(guò)命令可以修改下他的內(nèi)容,然后重新打包,就可以生成一個(gè)全新的渠道包了,省去了重新編譯構(gòu)建代碼的過(guò)程。使用一下Python腳本,將manifest文件里面channel信息進(jìn)行替換。

    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)

然后通過(guò)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

通過(guò)這上面的一系列過(guò)程,我們可以實(shí)現(xiàn)不重新編譯構(gòu)建項(xiàng)目就生成不同的渠道包。這會(huì)節(jié)省很多的時(shí)間。但是隨著渠道包增加,重新簽名也會(huì)占用很大一部分時(shí)間,那能不能不重新簽名呢?

分析簽名的算法后發(fā)現(xiàn),在打包過(guò)程后的META-INF文件夾下面添加空白文件是不會(huì)對(duì)簽名的結(jié)果產(chǎn)生影響的。

所以我們只要像META_INF文件夾里面寫(xiě)入空白的文件來(lái)標(biāo)識(shí)渠道號(hào)就可以了。
通過(guò)Python腳本像APK文件中寫(xiě)入渠道:

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)

執(zhí)行后會(huì)在META-INF文件夾下面生成一個(gè)空白文件:
然后我們?cè)陧?xiàng)目中去讀取這個(gè)空白文件:

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

這樣每生成一個(gè)渠道包只需要復(fù)制一下APK,然后添加一個(gè)空態(tài)的文件到META-INF下面就可以了,這樣100個(gè)渠道包一分鐘之內(nèi)就可以搞定了。

快速打包方案Version_2.0

上面的方案基本上已經(jīng)比較完美的解決我們打包的問(wèn)題了,然而好景不長(zhǎng),Google在Android 7.0中更新了應(yīng)用的簽名算法-APK Signature Scheme v2,它是一個(gè)對(duì)全文件進(jìn)行簽名的方案,能提供更快的應(yīng)用安裝時(shí)間、對(duì)未授權(quán)APK文件的更改提供更多保護(hù),在默認(rèn)情況下,Android Gradle 2.2.0插件會(huì)使用APK Signature Scheme v2和傳統(tǒng)簽名方案來(lái)簽署你的應(yīng)用。因?yàn)槭菍?duì)全文件進(jìn)行簽名的,所以之前的添加空白文件的方案就沒(méi)有作用了。

不過(guò)目前這個(gè)方案還不是強(qiáng)制性的,我們可以選擇在gradle配置文件中將其關(guān)閉:

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

那么新的簽名方案對(duì)已有的渠道生成方案有什么影響呢?
下圖是新的應(yīng)用簽名方案和舊的簽名方案的一個(gè)對(duì)比:

新的簽名方案會(huì)在ZIP文件格式的 Central Directory 區(qū)塊所在文件位置的前面添加一個(gè)APK Signing Block區(qū)塊,下面按照Z(yǔ)IP文件的格式來(lái)分析新應(yīng)用簽名方案簽名后的APK包。 整個(gè)APK(ZIP文件格式)會(huì)被分為以下四個(gè)區(qū)塊:

  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

新應(yīng)用簽名方案的簽名信息會(huì)被保存在區(qū)塊2(APK Signing Block)中, 而區(qū)塊1(Contents of ZIP entries)、區(qū)塊3(ZIP Central Directory)、區(qū)塊4(ZIP End of Central Directory)是受保護(hù)的,在簽名后任何對(duì)區(qū)塊1、3、4的修改都逃不過(guò)新的應(yīng)用簽名方案的檢查。

之前的渠道包生成方案是通過(guò)在META-INF目錄下添加空文件,用空文件的名稱來(lái)作為渠道的唯一標(biāo)識(shí),之前在META-INF下添加文件是不需要重新簽名應(yīng)用的,這樣會(huì)節(jié)省不少打包的時(shí)間,從而提高打渠道包的速度。但在新的應(yīng)用簽名方案下META-INF已經(jīng)被列入了保護(hù)區(qū)了,向META-INF添加空文件的方案會(huì)對(duì)區(qū)塊1、3、4都會(huì)有影響,新應(yīng)用簽名方案簽署的應(yīng)用經(jīng)過(guò)我們舊的生成渠道包方案處理后,在安裝時(shí)會(huì)報(bào)以下錯(cuò)誤:

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?]

區(qū)塊1,3,4是受保護(hù)的,任何的修改都會(huì)引起簽名的不一致,但是區(qū)塊2是不受保護(hù)的,所以能不能在區(qū)塊2上面找到解決辦法呢?首先看一下區(qū)塊2的文件結(jié)構(gòu):

區(qū)塊2中APK Signing Block是由這幾部分組成:2個(gè)用來(lái)標(biāo)示這個(gè)區(qū)塊長(zhǎng)度的8字節(jié) + 這個(gè)區(qū)塊的魔數(shù)(APK Sig Block 42)+ 這個(gè)區(qū)塊所承載的數(shù)據(jù)(ID-value)。

我們重點(diǎn)來(lái)看一下這個(gè)ID-value,它由一個(gè)8字節(jié)的長(zhǎng)度標(biāo)示+4字節(jié)的ID+它的負(fù)載組成。V2的簽名信息是以ID(0x7109871a)的ID-value來(lái)保存在這個(gè)區(qū)塊中,不知大家有沒(méi)有注意這是一組ID-value,也就是說(shuō)它是可以有若干個(gè)這樣的ID-value來(lái)組成,那我們是不是可以在這里做一些文章呢?

對(duì)于簽名的認(rèn)證過(guò)程是這樣的:

  1. 尋找APK Signing Block,如果能夠找到,則進(jìn)行驗(yàn)證,驗(yàn)證成功則繼續(xù)進(jìn)行安裝,如果失敗了則終止安裝
  2. 如果未找到APK Signing Block,則執(zhí)行原來(lái)的簽名驗(yàn)證機(jī)制,也是驗(yàn)證成功則繼續(xù)進(jìn)行安裝,如果失敗了則終止安裝

在校驗(yàn)的時(shí)候,檢驗(yàn)代碼如下:

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

我們可以發(fā)現(xiàn),述代碼中關(guān)鍵的一個(gè)位置是 if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {return getByteBuffer(pairs, len - 4);},通過(guò)源代碼可以看出Android是通過(guò)查找ID為APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a的ID-value,來(lái)獲取APK Signature Scheme v2 Block,對(duì)這個(gè)區(qū)塊中其他的ID-value選擇了忽略。也就是說(shuō),在APKSignature Scheme v2中沒(méi)有看到對(duì)無(wú)法識(shí)別的ID,有相關(guān)處理的介紹。所以我們可以通過(guò)寫(xiě)入自定義的ID-Value來(lái)自定義渠道。

1.6 MVP中你是如何處理Presenter層以防止內(nèi)存泄漏的?

首先 MVP 會(huì)出現(xiàn)內(nèi)存泄漏是因?yàn)?Presenter 層持有 View對(duì)象,一般我們會(huì)把 Activity 做為 View 傳遞到Presenter,Presenter 持有 View對(duì)象,Activity 退出了但是沒(méi)有回收出現(xiàn)內(nèi)存泄漏。
解決辦法:

  1. Activity onDestroy() 方法中調(diào)用 Presenter 中的方法,把 View 置為 null
  2. 使用 Lifecycle
  3. 使用 MVVM

1.7 如何計(jì)算一張圖片所占的內(nèi)存空間大小?

通常情況下圖片占用內(nèi)存的大小:圖片分辨率X像素點(diǎn)大小。

api獲取方法為bitmap.getByteCount()。

如果放入res/drawable下,通過(guò)BitmapFactory.decodeResource()方法加載不同dpi文件下的同一張圖片到內(nèi)存的大小是不一樣(存在分辨率的轉(zhuǎn)換),同時(shí)也與設(shè)備的dpi大小有關(guān)。而放在sd卡、網(wǎng)絡(luò)或者assert里則是一樣的(即使是不通dpi的設(shè)備)。圖片占用內(nèi)存大小跟控件大小無(wú)關(guān)(glide是先拿到控件大小,再去對(duì)圖片加載做的處理)。

1.8 有沒(méi)有遇到64k問(wèn)題,應(yīng)該如何解決?

單個(gè)dex文件方法超過(guò)64k,基本上都是引用過(guò)多的依賴才導(dǎo)致的。
解決方案:

  1. 導(dǎo)入依賴
'com.android.support:multidex:1.0.1'
  1. defaultConfig增加這個(gè)設(shè)置
multiDexEnabled true
  1. android下面增加這個(gè)設(shè)置
dexOptions {
    incremental true
    javaMaxHeapSize "4g"
}

以上都是在app的buildl.gradle中設(shè)置的,編譯。

  1. 打開(kāi)自定義的Application,繼承MultiDexApplication,并重寫(xiě)attachBaseContext方法

1.9 如何優(yōu)化 Gradle 的構(gòu)建速度?

  1. 物理設(shè)備:16g+內(nèi)存、硬盤(pán)ssd、高配CPU,最好是超頻CPU。5.0GHz那種
  2. 配置:使用高版本AS
    android.injected.testOnly=false
    android.buildCacheDir=buildCacheDir
    org.gradle.caching=true
    # 開(kāi)啟緩存
    android.enableBuildCache=true
    android.enableAapt2=true
    # 配置大內(nèi)存
    org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
    # 并行編譯
    org.gradle.parallel=true
    # 開(kāi)啟孵化模式
    org.gradle.configureondemand=true
    # 守護(hù)進(jìn)程
    org.gradle.daemon=true
    android.enableSeparateAnnotationProcessing=true
    buildTypes{
        debug{
            crunchPngs false
            aaptOptions.cruncherEnabled = false
        }
    }
    dexOptions {
        preDexLibraries true
    }

如果使用了multiDexEnabled ,一定要依賴最新版本的1.0.3版本
依賴的庫(kù)不帶+號(hào),每次都會(huì)自動(dòng)檢查最新版

  1. AS設(shè)置,如果已下載好依賴,可以設(shè)置離線模式

以上配置可以加快構(gòu)建和編譯速度。

1.10 如何獲取Android設(shè)備唯一ID?

常用方法:

  • IMEI
    權(quán)限(讀手機(jī)權(quán)限)
    可以手動(dòng)更改(模擬器上) /關(guān)閉權(quán)限報(bào)錯(cuò)
  • Mac地址
    訪問(wèn)WIFI權(quán)限
    有些設(shè)備沒(méi)有wifi(沒(méi)有上網(wǎng)功能)/6.0以上版本返回的都是固定的02:00:00 之類(lèi)地址
  • ANDROID_ID
    設(shè)備首次運(yùn)行,系統(tǒng)會(huì)隨機(jī)生成一個(gè)64位的數(shù)字,轉(zhuǎn)換成16進(jìn)制保存
    手機(jī)恢復(fù)出廠設(shè)置后值有變化/ 有些國(guó)產(chǎn)設(shè)備不會(huì)返回值
  • Serial Number (設(shè)備序列號(hào))
    有些國(guó)產(chǎn)手機(jī)(紅米)會(huì)出現(xiàn)垃圾數(shù)據(jù)

實(shí)現(xiàn)思路:

  1. 獲取設(shè)備的IMEI -->設(shè)備的Mac地址-->隨機(jī)生成的UUID
  2. 拼接在一起,然后用MD5加密
  3. 存儲(chǔ)到本地的文件中,隱藏并且存儲(chǔ)在App中的SP中
  4. 使用的時(shí)候先從SP中讀取,沒(méi)有的再生成,然后把生成的唯一id保存到SP中

1.11 談一談Android P禁用http對(duì)我們開(kāi)發(fā)有什么影響?

  • APP改用https請(qǐng)求
  • 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中它有哪些應(yīng)用場(chǎng)景?

aop的實(shí)現(xiàn)有靜態(tài)代理和動(dòng)態(tài)代理,靜態(tài)代理有靜態(tài)代理模式,基于Ajc編譯器的AspectJ,動(dòng)態(tài)代理有JDK動(dòng)態(tài)代理,在Android的實(shí)現(xiàn)是InvicationHandler,需要實(shí)現(xiàn)接口,還有CGlib實(shí)現(xiàn)方式創(chuàng)建子類(lèi)來(lái)繼承源類(lèi)。

應(yīng)用場(chǎng)景有各種狀態(tài)監(jiān)測(cè)比如登錄、網(wǎng)絡(luò)、權(quán)限等。日志埋點(diǎn),性能分析。

1.13 什么是MVVM?你是如何將其應(yīng)用于具體項(xiàng)目中的?

MVVM架構(gòu)其實(shí)由來(lái)已久,在Android中MVVM指代的是Model-View-ViewModel。Model就是數(shù)據(jù)模型,View一般認(rèn)為是Activity/Fragment。那么這個(gè)ViewModel其實(shí)是Google為Android應(yīng)用程序搭建MVVM架構(gòu)而設(shè)計(jì)的一個(gè)組件(類(lèi))。
顧名思義,ViewModel可以認(rèn)為是View與Model之間的一個(gè)容器,在沒(méi)有MVVM之前,我們通常會(huì)將View和Model之間的業(yè)務(wù)處理直接放在Activity/Fragment中進(jìn)行處理,現(xiàn)在有了ViewModel,可以將相關(guān)邏輯放在其中處理,View(Activity/Fragment)只進(jìn)行數(shù)據(jù)的展示,而不進(jìn)行數(shù)據(jù)的加工處理。
那么數(shù)據(jù)在處理或者變化后,如何通知到View呢?這就需要通過(guò)Jetpack的另外一個(gè)組件LiveData。LiveData可以使得ViewModel中的數(shù)據(jù)變化后,通知到View,它起到一個(gè)橋梁的作用。本質(zhì)上這是一個(gè)觀察者模式。

在Android項(xiàng)目中應(yīng)用MVVM架構(gòu),Google官方建議的做法是使用Jetpack相關(guān)組件,這些組件設(shè)計(jì)的目的就是為了方便開(kāi)發(fā)者搭建MVVM規(guī)范的應(yīng)用程序。

1.14 請(qǐng)談?wù)勀闶侨绾螌?shí)現(xiàn)數(shù)據(jù)埋點(diǎn)的?

一、埋點(diǎn)技術(shù)

代碼埋點(diǎn):
所謂的代碼埋點(diǎn)就是在你需要統(tǒng)計(jì)數(shù)據(jù)的地方植入N行代碼,統(tǒng)計(jì)用戶的關(guān)鍵行為。比如你想統(tǒng)計(jì)首頁(yè)某個(gè)banner的點(diǎn)擊量,上報(bào)的數(shù)據(jù)可以采用KEY-VALUE形式,我們定義 KEY為「CLICK_ADD_BTN」,VALUE的值為點(diǎn)擊的次數(shù)。當(dāng)用戶點(diǎn)擊banner時(shí),banner詳情的代碼會(huì)通過(guò)按鈕的「回調(diào)」來(lái)觸發(fā)執(zhí)行,程序猿在業(yè)務(wù)代碼執(zhí)行完后,又加上了統(tǒng)計(jì)代碼,把「CLICK_ADD_BTN」對(duì)應(yīng)的VALUE加1,banner被統(tǒng)計(jì)到了一次使用。

代碼埋點(diǎn)的優(yōu)點(diǎn):
使用者控制精準(zhǔn),可以非常精確地選擇什么時(shí)候發(fā)送數(shù)據(jù)使用者可以比較方便地設(shè)置自定義屬性、自定義事件,傳遞比較豐富的數(shù)據(jù)到服務(wù)端。

代碼埋點(diǎn)的缺點(diǎn):
埋點(diǎn)代價(jià)比較大,每一個(gè)控件的埋點(diǎn)都需要添加相應(yīng)的代碼,不僅工作量大,而且限定了必須是技術(shù)人員才能完成;
更新代價(jià)比較大,每一次更新,都需要更新埋點(diǎn)方案,然后通過(guò)各個(gè)應(yīng)用市場(chǎng)進(jìn)行分發(fā),而且有的用戶還不一定更新,這樣你就獲取不到這批用戶數(shù)據(jù)。

可視化埋點(diǎn):
既然代碼埋點(diǎn)代價(jià)比較大,每一個(gè)埋點(diǎn)都需要寫(xiě)代碼,那就使用可視化交互手段代替寫(xiě)代碼;既然每次代碼埋點(diǎn)都需要更新,那就參照現(xiàn)在的很多手游做法,把核心代碼和配置、資源分開(kāi),每次用戶啟動(dòng)app的時(shí)候通過(guò)網(wǎng)絡(luò)更新配置和資源。

可視化埋點(diǎn)優(yōu)勢(shì):
可視化買(mǎi)點(diǎn)解決了代碼埋點(diǎn)埋點(diǎn)代價(jià)大和更新代價(jià)大兩個(gè)問(wèn)題。

可視化埋點(diǎn)劣勢(shì):
可視化埋點(diǎn)能夠覆蓋的功能有限,目前并不是所有的控件操作都可以通過(guò)這種方案進(jìn)行定制;

無(wú)埋點(diǎn):
可視化埋點(diǎn)先通過(guò)界面配置哪些控件的操作數(shù)據(jù)需要收集;“無(wú)埋點(diǎn)”則是先盡可能收集所有控件的操作數(shù)據(jù),然后再通過(guò)界面配置哪些數(shù)據(jù)需要在系統(tǒng)里面進(jìn)行分析,“無(wú)埋點(diǎn)”也就是“全埋點(diǎn)”的意思。

無(wú)埋點(diǎn)的優(yōu)點(diǎn):
可視化埋點(diǎn)只能收集到你埋點(diǎn)以后的數(shù)據(jù),如果你想對(duì)某個(gè)按鈕進(jìn)行點(diǎn)擊分析,則只能分析增加可視化埋點(diǎn)以后的數(shù)據(jù),之前的數(shù)據(jù)你收集不到,而無(wú)埋點(diǎn)在你部署SDK的時(shí)候數(shù)據(jù)就一直在收集。
因?yàn)闊o(wú)埋點(diǎn)對(duì)頁(yè)面所有元素進(jìn)行埋點(diǎn),那么這個(gè)頁(yè)面每個(gè)元素被點(diǎn)擊的概率你也就知道,對(duì)點(diǎn)擊概率比較大的元素可以進(jìn)行深入分析。

無(wú)埋點(diǎn)的缺點(diǎn):
由于無(wú)埋點(diǎn)方案所有的元素?cái)?shù)據(jù)都收集,會(huì)給數(shù)據(jù)傳輸和服務(wù)器帶來(lái)較大的壓力。

二、數(shù)據(jù)埋點(diǎn)方式

1、公司研發(fā)在自己的產(chǎn)品當(dāng)中注入統(tǒng)計(jì)代碼,搭建相應(yīng)的后臺(tái)查詢,這種代價(jià)比較大。
2、集成第三方統(tǒng)計(jì)的SDK,比如友盟、百度移動(dòng)統(tǒng)計(jì)、Sensors Data、GrowingIO、Talking Data等。

三、如何進(jìn)行數(shù)據(jù)埋點(diǎn)

1、明確目標(biāo)
經(jīng)常有人問(wèn)我說(shuō)我要獲取那些數(shù)據(jù)來(lái)進(jìn)行數(shù)據(jù)分析,其實(shí)這個(gè)問(wèn)題不應(yīng)該問(wèn)別人,應(yīng)該問(wèn)問(wèn)你自己,你是想用這個(gè)數(shù)據(jù)干什么,如果你想繪制基礎(chǔ)的人群畫(huà)像你就需要獲取用戶機(jī)型、網(wǎng)絡(luò)類(lèi)型、操作系統(tǒng),IP地域等數(shù)據(jù);如果你想分析每一個(gè)注冊(cè)轉(zhuǎn)化率,你就需要獲取每一個(gè)步驟的點(diǎn)擊次數(shù),然后制作成漏斗,看那一步轉(zhuǎn)化率出現(xiàn)了問(wèn)題;
目的不一樣,獲取的數(shù)據(jù)也不一樣,使用的埋點(diǎn)技術(shù)也不一樣,我們無(wú)論做什么事情都不能忘了我們的目的!
2、獲取相應(yīng)數(shù)據(jù)
業(yè)務(wù)不同,目的不同獲取的數(shù)據(jù)也不同,這里我只說(shuō)一些比較共性的數(shù)據(jù)。
2.1、產(chǎn)品各個(gè)渠道下載量
這個(gè)可以用第三方數(shù)據(jù)統(tǒng)計(jì)工具來(lái)進(jìn)行,這樣我們可以知道我們產(chǎn)品著重在那個(gè)渠道進(jìn)行推廣。
2.2、產(chǎn)品活躍狀態(tài)分析
產(chǎn)品活躍狀態(tài)監(jiān)控,留存分析、流失分析、新增變化等,次日留存率、七日留存率、月留存率,尤其對(duì)于處于成長(zhǎng)期的產(chǎn)品而已,這個(gè)指標(biāo)很重要,如果留存率比較低,說(shuō)明你的產(chǎn)品有問(wèn)題,這個(gè)時(shí)候你就需要進(jìn)行用戶調(diào)研,找到流失的問(wèn)題,否則大面積拉新,只能拉多少死多少,至于留存率、新增的變化這些數(shù)據(jù),我們也可以借助第三方統(tǒng)計(jì)工具來(lái)進(jìn)行。
2.3、事件分析
比如你想統(tǒng)計(jì)某個(gè)頁(yè)面的UV、PV、元素的點(diǎn)擊量、用戶停留時(shí)長(zhǎng)、頁(yè)面跳出率等數(shù)據(jù)指標(biāo),可以選擇代碼埋點(diǎn)和可視化埋點(diǎn)等前端埋點(diǎn)解決方案。當(dāng)某個(gè)頁(yè)面的UV很高,但是跳出率也很高,說(shuō)明頁(yè)面有問(wèn)題,你就要好好想想頁(yè)面的問(wèn)題出在什么地方。
2.4、基本信息獲取
基本信息獲取,例如機(jī)型、網(wǎng)絡(luò)類(lèi)型、操作系統(tǒng),IP地域等,繪制基礎(chǔ)用戶人群畫(huà)像,這種分析出來(lái)的用戶畫(huà)像顆粒度比較大,如果想更精準(zhǔn)的進(jìn)行用戶畫(huà)像可以結(jié)合推薦系統(tǒng),來(lái)獲取用戶的興趣指標(biāo),以及用戶操作行為等數(shù)據(jù)來(lái)進(jìn)行更精準(zhǔn)的用戶畫(huà)像,從而為產(chǎn)品運(yùn)營(yíng)和產(chǎn)品設(shè)計(jì)提供參考,可以借助第三方統(tǒng)計(jì)工具和自定義埋點(diǎn)的方式進(jìn)行數(shù)據(jù)的收集。
2.5、漏斗模型
對(duì)于產(chǎn)品的關(guān)鍵路徑一定要進(jìn)行漏斗模型分析,比如注冊(cè)路徑,從用戶輸入注冊(cè)手機(jī)號(hào)到注冊(cè)成功,中間可能會(huì)有幾個(gè)步驟,如果100個(gè)人注冊(cè),最后只有一個(gè)人注冊(cè)成功,那么求運(yùn)營(yíng)同學(xué)心里的陰影面積。還有電商的購(gòu)買(mǎi)下單路徑,從瀏覽商品到最后下單購(gòu)買(mǎi)成功,每一個(gè)步驟的轉(zhuǎn)化率是多少,對(duì)于漏的比較多的那個(gè)步驟我們肯定要著重關(guān)注,分析原因。這個(gè)可以技術(shù)研發(fā)進(jìn)行埋點(diǎn),獲取更精確的數(shù)據(jù),比如下圖的埋點(diǎn)表。

如何給app客戶端進(jìn)行埋點(diǎn)?

領(lǐng)導(dǎo)說(shuō),APP需要加一下統(tǒng)計(jì),你負(fù)責(zé)搞定
研發(fā)說(shuō),APP需要統(tǒng)計(jì)哪些地方,你列一下埋點(diǎn)需求
研發(fā)說(shuō),APP的數(shù)據(jù)統(tǒng)計(jì)SDK用哪家的?你選好了注冊(cè)一下、運(yùn)營(yíng)說(shuō),咱們的APP都能看哪些數(shù)據(jù)?平臺(tái)在哪?怎么查首頁(yè)的UV?
數(shù)據(jù)分析是一個(gè)很復(fù)雜的工作,很多人在談如何挖掘數(shù)據(jù),做用戶畫(huà)像,設(shè)計(jì)數(shù)據(jù)漏斗,如何負(fù)責(zé)用戶生命周期管理,但發(fā)現(xiàn)很多人卻卡在了數(shù)據(jù)分析的第一步,那就是如何做數(shù)據(jù)埋點(diǎn)。

首頁(yè)陳峰老師先明確下完成一個(gè)APP數(shù)據(jù)埋點(diǎn)的幾個(gè)步驟:

  • 注冊(cè)一家統(tǒng)計(jì)網(wǎng)站
  • 新建應(yīng)用
  • 獲取KEY和SDK代碼包
  • 將埋點(diǎn)需求和SDK包發(fā)給研發(fā)
  • 自定義埋點(diǎn)需求完善
  • 研發(fā)開(kāi)發(fā)并完成APP上線
  • 在后臺(tái)查看數(shù)據(jù)
    1、注冊(cè)賬號(hào)
    建議用公司郵箱或者公用郵箱注冊(cè),別用自己的私人郵箱和手機(jī)號(hào)碼,后續(xù)一旦有交接和工作變動(dòng)時(shí)會(huì)比較麻煩。
    2、新建應(yīng)用
    登錄后一般都有“新建應(yīng)用”,可以選標(biāo)準(zhǔn)統(tǒng)計(jì),大部分APP都選這個(gè)。游戲的app另說(shuō)。
    名稱寫(xiě)自己app的名稱,分類(lèi)自己選1個(gè)。選錯(cuò)了也不影響。
    平臺(tái)根據(jù)情況自己選。后期我們看數(shù)據(jù)和埋點(diǎn)都是ios和安卓分開(kāi)的,所以你如果2個(gè)端都做,就一起都選上。
    描述可選,不用填。
    3、獲取KEY和SDK代碼包
    完成后可以得到2個(gè)APPKEY。分別是ios和安卓的。
    這里的appkey很重要,你可以下載了給研發(fā),也可以稍后讓研發(fā)自己登錄進(jìn)來(lái)自己下載。
    ios和安卓是分開(kāi)2個(gè)獨(dú)立的,后續(xù)埋點(diǎn)和看數(shù)據(jù)都是分開(kāi)的。這個(gè)切記。
    這時(shí)候,重點(diǎn)來(lái)了。
    此時(shí),如果我們只想看 APP的活躍用戶,留存用戶,下載量。用戶地域分布,渠道分布,那么其實(shí)就夠了。
    4、將埋點(diǎn)需求和SDK包發(fā)給研發(fā)
    你這時(shí)候,就把剛才獲得的appkey和sdk包的下載地址,發(fā)給研發(fā)。或者直接把賬號(hào)和密碼發(fā)給研發(fā)。然后告訴研發(fā),集成下百度移動(dòng)統(tǒng)計(jì)的SDK包。這樣發(fā)版后,就可以看到大部分?jǐn)?shù)據(jù)了。
    如下的數(shù)據(jù)都可以看到:

5、自定義事件完善
比如我們想看頁(yè)面里面 注冊(cè) 搜索按鈕,頂部banner,底部 首頁(yè)和 我的 2個(gè)導(dǎo)航條的點(diǎn)擊量。
一個(gè)埋點(diǎn)事件對(duì)應(yīng)1個(gè)按鈕或者一個(gè)頁(yè)面或者一個(gè)彈層。你來(lái)定義。
如果埋點(diǎn)比較多,你也可以批量添加。批量添加的時(shí)候,您需要下載excel模板,按照要求填寫(xiě)好,上傳進(jìn)來(lái)即可。具體一看便知。
添加完成后就可以把這個(gè)列表導(dǎo)出或者人肉復(fù)制出來(lái)一個(gè)表格。發(fā)給研發(fā)。并附上你的原型圖。做好對(duì)應(yīng)關(guān)系標(biāo)注。
6、研發(fā)開(kāi)發(fā)并完成APP上線
完成上面幾步后,研發(fā)哥哥就可以看懂進(jìn)入第7步研發(fā)階段了。
7、在后臺(tái)查看數(shù)據(jù)
上線后就可以看到數(shù)據(jù)了。大部分?jǐn)?shù)據(jù)一般隔天更新。

三、埋點(diǎn)后能看到什么數(shù)據(jù)

上面提到,按照步驟完成數(shù)據(jù)分析sdk集成和自定義事件后,就可以看到數(shù)據(jù)了。
不添加自定義事件,可以看到基礎(chǔ)數(shù)據(jù),添加后,可以看到更細(xì)節(jié)的按鈕,頁(yè)面等點(diǎn)擊數(shù)據(jù)。
查看自定義事件埋點(diǎn)數(shù)據(jù),還是進(jìn)入剛才的“事件分析”頁(yè)面,點(diǎn)擊對(duì)應(yīng)埋點(diǎn)即可看到數(shù)據(jù)。
可以篩選時(shí)間段。
除了這些外,如果你還想看 幾個(gè)頁(yè)面之間的轉(zhuǎn)化路徑和數(shù)據(jù)漏斗。那還需要添加“轉(zhuǎn)化分析”。
添加轉(zhuǎn)化分析后,可以看到例如: 進(jìn)入首頁(yè)-點(diǎn)擊注冊(cè)按鈕進(jìn)入注冊(cè)成功頁(yè) 這幾步的轉(zhuǎn)化率和流失率。會(huì)自動(dòng)生成一個(gè)轉(zhuǎn)化分析圖。當(dāng)然你也可以分別看這幾個(gè)頁(yè)面的數(shù)據(jù),自己去分析匯總。
進(jìn)階的方法還有把事件埋點(diǎn)配合轉(zhuǎn)化分析、訪問(wèn)路徑、轉(zhuǎn)化漏斗等工具使用,從點(diǎn)到面地了解用戶的使用行為、APP存在的問(wèn)題。

產(chǎn)品核心指標(biāo)一般包含:

  1. 產(chǎn)品規(guī)模
    1.1 用戶數(shù)據(jù)。如新增用戶、用戶類(lèi)型分布、活躍用戶、沉默用戶、啟動(dòng)次數(shù)、版本分析等。
    1.2 業(yè)務(wù)數(shù)據(jù)。這個(gè)與具體業(yè)務(wù)有關(guān),如問(wèn)答社區(qū)的問(wèn)題數(shù),回答數(shù),全網(wǎng)熱度,瀏覽量;如對(duì)含交易平臺(tái)的交易量,交易額,客單價(jià),轉(zhuǎn)化率,利潤(rùn)等。
  2. 產(chǎn)品運(yùn)營(yíng)
    2.1 流量數(shù)據(jù)。pv,uv,dau,mau,留存分析(次日留存,7日留存, 用戶新鮮度), 流失分析(日周月、自然流失、回歸流失),
    2.2 渠道數(shù)據(jù)。渠道流量,渠道轉(zhuǎn)換率,渠道評(píng)價(jià),渠道時(shí)段詳情,渠道質(zhì)量(渠道活躍用戶/渠道流量)等。

1.15 假如讓你實(shí)現(xiàn)斷點(diǎn)上傳功能,你認(rèn)為應(yīng)該怎樣去做?

分2種情況:
分塊上傳:多線程讀取本地文件指定區(qū)域流上傳,header帶有上傳位置標(biāo)記,服務(wù)器接收到多個(gè)上傳請(qǐng)求會(huì)生成多個(gè)上傳臨時(shí)文件接收,最后合并成完整文件。(續(xù)傳如下)

正常續(xù)傳:本地請(qǐng)求上傳,服務(wù)器響應(yīng)是否未完成的臨時(shí)文件和文件byte,本地收到就接著指定位置讀流上傳。

其中會(huì)涉及塊的數(shù)據(jù)校驗(yàn)等

1.16 webp和svg格式的圖片各自有什么特點(diǎn)?應(yīng)該如何在Android中使用?

一、
svg格式
svg是矢量圖,這意味著svg圖片由直線和曲線以及繪制它們的方法組成。當(dāng)你放大一個(gè)svg圖片的時(shí)候,你看到的還是線和曲線,而不會(huì)出現(xiàn)像素點(diǎn)。svg圖片在放大時(shí),不會(huì)失真,所以它非常適合用來(lái)繪制企業(yè)Logo、Icon

SVG格式特點(diǎn):
1、SVG 指可伸縮矢量圖形 (Scalable Vector Graphics)
2、SVG 用來(lái)定義用于網(wǎng)絡(luò)的基于矢量的圖形
3、SVG 使用 XML 格式定義圖形
4、SVG 圖像在放大或改變尺寸的情況下其圖形質(zhì)量不會(huì)有所損失
5、SVG 是萬(wàn)維網(wǎng)聯(lián)盟的標(biāo)準(zhǔn)
6、SVG 與諸如 DOM和 XSL 之類(lèi)的W3C標(biāo)準(zhǔn)是一個(gè)整體

二、
webp格式
webp是谷歌開(kāi)發(fā)的一種新圖片格式,是同時(shí)支持有損和無(wú)損壓縮的、使用直接色的、點(diǎn)陣圖。

1.17 說(shuō)說(shuō)你是如何進(jìn)行單元測(cè)試的?以及如何應(yīng)用在MVP和MVVM中?

單元測(cè)試庫(kù) junit mockito Rebolectric
說(shuō)下mvp工程中的測(cè)試方法
測(cè)試主要有 三大部分

  1. 普通工具類(lèi) 使用junit 直接測(cè)試
  2. mvp的p使用@mock標(biāo)注view的接口,初始化真正的p,直接調(diào)用p的方法看看verify view的某些方法是否按照預(yù)期被調(diào)用
  3. mvp的v用rebolectric去setup一個(gè)Activity,然后用這個(gè)庫(kù)找到界面上的按鈕,或者觸發(fā)生命周期(onStart),判斷一下當(dāng)前界面的某些view是否被顯示或者TextView的值或者Dialog是否顯示,Toast是否彈出錯(cuò)誤
  4. 還有網(wǎng)絡(luò)部分的測(cè)試,可以直接使用junit進(jìn)行測(cè)試,判斷下返回值是否符合預(yù)期

1.18 對(duì)于GIF圖片加載有什么思路和建議?

gif圖實(shí)際上就是多幀合并的圖

參考Fresco內(nèi)部實(shí)現(xiàn):
1,View層使用一個(gè)Drawable,包含bitmap,并依據(jù)gif的信息不斷的更新并繪制bitmap
2,C層提供api功能,例如:輸入gif數(shù)據(jù)流,提供解析gif信息、更新bitmap等功能

1.19 為什么要將項(xiàng)目遷移到AndroidX?如何進(jìn)行遷移?

現(xiàn)在Android官方支持的最低系統(tǒng)版本已經(jīng)是4.0.1,對(duì)應(yīng)的API版本號(hào)是15。support-v4、appcompat-v7庫(kù)也不再支持那么久遠(yuǎn)的系統(tǒng)了,但是它們的名字卻一直保留了下來(lái),雖然它們現(xiàn)在的實(shí)際作用已經(jīng)對(duì)不上當(dāng)初命名的原因了。

那么很明顯,Android團(tuán)隊(duì)也意識(shí)到這種命名已經(jīng)非常不合適了,于是對(duì)這些API的架構(gòu)進(jìn)行了一次重新的劃分,推出了AndroidX。因此,AndroidX本質(zhì)上其實(shí)就是對(duì)Android Support Library進(jìn)行的一次升級(jí),升級(jí)內(nèi)容主要在于以下兩個(gè)方面。

第一,包名。之前Android Support Library中的API,它們的包名都是在android.support.下面的,而AndroidX庫(kù)中所有API的包名都變成了在androidx.下面。這是一個(gè)很大的變化,意味著以后凡是android.包下面的API都是隨著Android操作系統(tǒng)發(fā)布的,而androidx.包下面的API都是隨著擴(kuò)展庫(kù)發(fā)布的,這些API基本不會(huì)依賴于操作系統(tǒng)的具體版本。

第二,命名規(guī)則。吸取了之前命名規(guī)則的弊端,AndroidX所有庫(kù)的命名規(guī)則里都不會(huì)再包含具體操作系統(tǒng)API的版本號(hào)了。比如,像appcompat-v7庫(kù),在AndroidX中就變成了appcompat庫(kù)。

一個(gè)AndroidX完整的依賴庫(kù)格式如下所示:

implementation 'androidx.appcompat:appcompat:1.0.2'

了解了AndroidX是什么之后,現(xiàn)在你應(yīng)該放輕松了吧?它其實(shí)并不是什么全新的東西,而是對(duì)Android SupportLibrary的一次升級(jí)。因此,AndroidX上手起來(lái)也沒(méi)有任何困難的地方,比如之前你經(jīng)常使用的RecyclerView、ViewPager等等庫(kù),在AndroidX中都會(huì)有一個(gè)對(duì)應(yīng)的版本,只要改一下包名就可以完全無(wú)縫使用,用法方面基本上都沒(méi)有任何的變化。

但是有一點(diǎn)需要注意,AndroidX和Android Support Library中的庫(kù)是非常不建議混合在一起使用的,因?yàn)樗鼈兛赡軙?huì)產(chǎn)生很多不兼容的問(wèn)題。最好的做法是,要么全部使用AndroidX中的庫(kù),要么全部使用Android Support Library中的庫(kù)。

而現(xiàn)在Android團(tuán)隊(duì)官方的態(tài)度也很明確,未來(lái)都會(huì)為AndroidX為主,Android Support Library已經(jīng)不再建議使用,并會(huì)慢慢停止維護(hù)。另外,從Android Studio 3.4.2開(kāi)始,我發(fā)現(xiàn)新建的項(xiàng)目已經(jīng)強(qiáng)制勾選使用AndroidX架構(gòu)了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容