真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Kotlin代碼檢查的示例分析

Kotlin代碼檢查的示例分析,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。

創(chuàng)新互聯(lián)主要從事網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)安吉,10年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來(lái)電咨詢建站服務(wù):028-86922220


背景

Kotlin有著諸多的特性,比如空指針安全、方法擴(kuò)展、支持函數(shù)式編程、豐富的語(yǔ)法糖等。這些特性使得Kotlin的代碼比Java簡(jiǎn)潔優(yōu)雅許多,提高了代碼的可讀性和可維護(hù)性,節(jié)省了開(kāi)發(fā)時(shí)間,提高了開(kāi)發(fā)效率。這也是我們團(tuán)隊(duì)轉(zhuǎn)向Kotlin的原因,但是在實(shí)際的使用過(guò)程中,我們發(fā)現(xiàn)看似寫法簡(jiǎn)單的Kotlin代碼,可能隱藏著不容忽視的額外開(kāi)銷。下面剖析了Kotlin的隱藏開(kāi)銷,并就如何避免開(kāi)銷進(jìn)行了探索和實(shí)踐。

Kotlin的隱藏開(kāi)銷

伴生對(duì)象

伴生對(duì)象通過(guò)在類中使用companion object來(lái)創(chuàng)建,用來(lái)替代靜態(tài)成員,類似于Java中的靜態(tài)內(nèi)部類。所以在伴生對(duì)象中聲明常量是很常見(jiàn)的做法,但如果寫法不對(duì),可能就會(huì)產(chǎn)生額外開(kāi)銷。比如下面這段聲明Version常量的代碼:

  • 調(diào)用伴生對(duì)象的靜態(tài)方法

  • 調(diào)用伴生對(duì)象的實(shí)例方法

  • 調(diào)用主類的靜態(tài)方法

  • 讀取主類中的靜態(tài)字段

為了訪問(wèn)一個(gè)常量,而多花費(fèi)調(diào)用4個(gè)方法的開(kāi)銷,這樣的Kotlin代碼無(wú)疑是低效的。

我們可以通過(guò)以下解決方法來(lái)減少生成的字節(jié)碼:

  1. 對(duì)于基本類型和字符串,可以使用const關(guān)鍵字將常量聲明為編譯時(shí)常量。

  2. 對(duì)于公共字段,可以使用@JvmField注解。

  3. 對(duì)于其他類型的常量,最好在它們自己的主類對(duì)象而不是伴生對(duì)象中來(lái)存儲(chǔ)公共的全局常量。

lazy()委托屬性

lazy()委托屬性可以用于只讀屬性的惰性加載,但是在使用lazy()時(shí)經(jīng)常被忽視的地方就是有一個(gè)可選的model參數(shù):

  • LazyThreadSafetyMode.SYNCHRONIZED:初始化屬性時(shí)會(huì)有雙重鎖檢查,保證該值只在一個(gè)線程中計(jì)算,并且所有線程會(huì)得到相同的值。

  • LazyThreadSafetyMode.PUBLICATION:多個(gè)線程會(huì)同時(shí)執(zhí)行,初始化屬性的函數(shù)會(huì)被多次調(diào)用,但是只有第一個(gè)返回的值被當(dāng)做委托屬性的值。

  • LazyThreadSafetyMode.NONE:沒(méi)有雙重鎖檢查,不應(yīng)該用在多線程下。

lazy()默認(rèn)情況下會(huì)指定LazyThreadSafetyMode.SYNCHRONIZED,這可能會(huì)造成不必要線程安全的開(kāi)銷,應(yīng)該根據(jù)實(shí)際情況,指定合適的model來(lái)避免不需要的同步鎖。

基本類型數(shù)組

在Kotlin中有3種數(shù)組類型:

  • IntArray,F(xiàn)loatArray,其他:基本類型數(shù)組,被編譯成int[],float[],其他

  • Array:非空對(duì)象數(shù)組

  • Array:可空對(duì)象數(shù)組

使用這三種類型來(lái)聲明數(shù)組,可以發(fā)現(xiàn)它們之間的區(qū)別:

Kotlin代碼檢查的示例分析

Kotlin聲明的數(shù)組

等同的Java代碼:

Kotlin代碼檢查的示例分析等同Java聲明的數(shù)組

后面兩種方法都對(duì)基本類型做了裝箱處理,產(chǎn)生了額外的開(kāi)銷。  
所以當(dāng)需要聲明非空的基本類型數(shù)組時(shí),應(yīng)該使用xxxArray,避免自動(dòng)裝箱。

for循環(huán)

Kotlin提供了downTo、step、until、       reversed等函數(shù)來(lái)幫助開(kāi)發(fā)者更簡(jiǎn)單的使用for循環(huán),如果單一的使用這些函數(shù)確實(shí)是方便簡(jiǎn)潔又高效,但要是將其中兩個(gè)結(jié)合呢?比如下面這樣:

Kotlin代碼檢查的示例分析

上面的for循環(huán)中結(jié)合使用了downTo和step,那么等同的Java代碼又是怎么實(shí)現(xiàn)的呢?

Kotlin代碼檢查的示例分析重點(diǎn)看這行代碼:

IntProgression var10000 = RangesKt.step(RangesKt.downTo(10, 1), 2);

這行代碼就創(chuàng)建了兩個(gè)IntProgression臨時(shí)對(duì)象,增加了額外的開(kāi)銷。

Kotlin檢查工具的探索

Kotlin的隱藏開(kāi)銷不止上面列舉的幾個(gè),為了避免開(kāi)銷,我們需要實(shí)現(xiàn)這樣一個(gè)工具,實(shí)現(xiàn)Kotlin語(yǔ)法的檢查,列出不規(guī)范的代碼并給出修改意見(jiàn)。同時(shí)為了保證開(kāi)發(fā)同學(xué)的代碼都是經(jīng)過(guò)工具檢查的,整個(gè)檢查流程應(yīng)該自動(dòng)化。

再進(jìn)一步考慮,Kotlin代碼的檢查規(guī)則應(yīng)該具有擴(kuò)展性,方便其他使用方定制自己的檢查規(guī)則。

基于此,整個(gè)工具主要包含下面三個(gè)方面的內(nèi)容:

  1. 解析Kotlin代碼

  2. 編寫可擴(kuò)展的自定義代碼檢查規(guī)則

  3. 檢查自動(dòng)化

結(jié)合對(duì)工具的需求,在經(jīng)過(guò)思考和查閱資料之后,確定了三種可供選擇的方案:

ktlint

ktlint是一款用來(lái)檢查Kotlin代碼風(fēng)格的工具,和我們的工具定位不同,需要經(jīng)過(guò)大量的改造工作才行。

detekt

detekt是一款用來(lái)靜態(tài)分析Kotlin代碼的工具,符合我們的需求,但是不太適合Android工程,比如無(wú)法指定variant(變種)檢查。另外,在整個(gè)檢查流程中,一份kt文件只能檢查一次,檢查結(jié)果(當(dāng)時(shí))只支持控制臺(tái)輸出,不便于閱讀。

改造Lint

改造Lint來(lái)增加Lint對(duì)Kotlin代碼檢查的支持,一方面Lint提供的功能完全可以滿足我們的需求,同時(shí)還能支持資源文件和class文件的檢查,另一方面改造后的Lint和Lint很相似,學(xué)習(xí)上手的成本低。

相對(duì)于前兩種方案,方案3的成本收益比最高,所以我們決定改造Lint成Kotlin Lint(KLint)插件。

先來(lái)大致了解下Lint的工作流程,如下圖:

Kotlin代碼檢查的示例分析Lint流程圖

很顯然,上圖中的紅框部分需要被改造以適配Kotlin,主要工作有以下3點(diǎn):

  • 創(chuàng)建KotlinParser對(duì)象,用來(lái)解析Kotlin代碼

  • 從aar中獲取自定義KLint規(guī)則的jar包

  • Detector類需要定義一套新的接口方法來(lái)適配遍歷Kotlin節(jié)點(diǎn)回調(diào)時(shí)的調(diào)用

Kotlin代碼解析

和Java一樣,Kotlin也有自己的抽象語(yǔ)法樹(shù)??上У氖悄壳斑€沒(méi)有解析Kotlin語(yǔ)法樹(shù)的單獨(dú)庫(kù),只能通過(guò)Kotlin編譯器這個(gè)庫(kù)中的相關(guān)類來(lái)解析。KLint用的是kotlin-compiler-embeddable:1.1.2-5庫(kù)。

public KtFile parseKotlinToPsi(@NonNull File file) {
        try {
        org.jetbrains.kotlin.com.intellij.openapi.project.Project ktProject = KotlinCoreEnvironment.Companion.createForProduction(() -> {
        }, new CompilerConfiguration(), CollectionsKt.emptyList()).getProject();
        this.psiFileFactory = PsiFileFactory.getInstance(ktProject);
        return (KtFile) psiFileFactory.createFileFromText(file.getName(), KotlinLanguage.INSTANCE, readFileToString(file, "UTF-8"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
     //可忽視,只是將文件轉(zhuǎn)成字符流      public static String readFileToString(File file, String encoding) throws IOException {
        FileInputStream stream = new FileInputStream(file);
        String result = null;
        try {
            result = readInputStreamToString(stream, encoding);
        } finally {
            try {
                stream.close();
            } catch (IOException e) {
                // ignore             }
        }
        return result;
    }

以上這段代碼可以封裝成KotlinParser類,主要作用是將.Kt文件轉(zhuǎn)化成KtFile對(duì)象。

在檢查Kotlin文件時(shí)調(diào)用KtFile.acceptChildren(KtVisitorVoid)后,KtVisitorVoid便會(huì)多次回調(diào)遍歷到的各個(gè)節(jié)點(diǎn)(Node)的方法:

KtVisitorVoid visitorVoid = new KtVisitorVoid(){
    @Override     public void visitClass(@NotNull KtClass klass) {
               super.visitClass(klass);
    }

    @Override     public void visitPrimaryConstructor(@NotNull KtPrimaryConstructor constructor) {
           super.visitPrimaryConstructor(constructor);
    }

    @Override     public void visitProperty(@NotNull KtProperty property) {
           super.visitProperty(property);
    }
    ...
};
ktPsiFile.acceptChildren(visitorVoid);

自定義KLint規(guī)則的實(shí)現(xiàn)

自定義KLint規(guī)則的實(shí)現(xiàn)參考了Android自定義Lint實(shí)踐這篇文章。

Kotlin代碼檢查的示例分析

上圖展示了aar中允許包含的文件,aar中可以包含lint.jar,這也是Android自定義Lint實(shí)踐這篇文章采用的實(shí)現(xiàn)方式。但是klint.jar不能直接放入aar中,當(dāng)然更不應(yīng)該將klint.jar重命名成lint.jar來(lái)實(shí)現(xiàn)目的。

最后采用的方案是:

  1. 通過(guò)創(chuàng)建klintrules這個(gè)空的aar,將klint.jar放入assets中;

  2. 修改KLint代碼實(shí)現(xiàn)從assets中讀取klint.jar;

  3. 項(xiàng)目依賴klintrulesaar時(shí)使用debugCompile來(lái)避免把klint.jar帶到release包。

Detector類中接口方法的定義

既然是對(duì)Kotlin代碼的檢查,自然Detector類要定義一套新的接口方法。先來(lái)看一下Java代碼檢查規(guī)則提供的方法:

Kotlin代碼檢查的示例分析

相信寫過(guò)Lint規(guī)則的同學(xué)對(duì)上面的方法應(yīng)該非常熟悉。為了盡量降低KLint檢查規(guī)則編寫的學(xué)習(xí)成本,我們參照J(rèn)avaPsiScanner接口,定義了一套非常相似的接口方法:

Kotlin代碼檢查的示例分析

KLint的實(shí)現(xiàn)

通過(guò)對(duì)上述3個(gè)主要方面的改造,完成了KLint插件。

Kotlin代碼檢查的示例分析

由于KLint和Lint的相似,KLint插件簡(jiǎn)單易上手:

  1. 和Lint相似的編寫規(guī)范(參考最后一節(jié)的代碼);

  2. 支持@SuppressWarnings("")等Lint支持的注解;

  3. 具有和Lint的Options相同功能的klintOptions,如下:

mtKlint {
    klintOptions {
        abortOnError false         htmlReport true         htmlOutput new File(project.getBuildDir(), "mtKLint.html")
    }
}

檢查自動(dòng)化

  • 關(guān)于自動(dòng)檢查有兩個(gè)方案:

    1. 在開(kāi)發(fā)同學(xué)commit/push代碼時(shí),觸發(fā)pre-commit/push-hook進(jìn)行檢查,檢查不通過(guò)不允許commit/push;

    2. 在創(chuàng)建pull request時(shí),觸發(fā)CI構(gòu)建進(jìn)行檢查,檢查不通過(guò)不允許merge。

這里更偏向于方案2,因?yàn)閜re-commit/push-hook可以通過(guò)--no-verify命令繞過(guò),我們希望所有的Kotlin代碼都是通過(guò)檢查的。

KLint插件本身支持通過(guò)./gradlew mtKLint命令運(yùn)行,但是考慮到幾乎所有的項(xiàng)目在CI構(gòu)建上都會(huì)執(zhí)行Lint檢查,把KLint和Lint綁定在一起可以省去CI構(gòu)建腳本接入KLint插件的成本。

通過(guò)以下代碼,將lint task依賴klint task,實(shí)現(xiàn)在執(zhí)行Lint之前先執(zhí)行KLint檢查:

//創(chuàng)建KLint task,并設(shè)置被Lint task依賴 KLint klintTask = project.getTasks().create(String.format(TASK_NAME, ""), KLint.class, new KLint.GlobalConfigAction(globalScope, null, KLintOptions.create(project))) Set lintTasks = project.tasks.findAll {
    it.name.toLowerCase().equals("lint")
}
lintTasks.each { lint ->
    klintTask.dependsOn lint.taskDependencies.getDependencies(lint)
    lint.dependsOn klintTask
} //創(chuàng)建Klint變種task,并設(shè)置被Lint變種task依賴 for (Variant variant : androidProject.variants) {
     klintTask = project.getTasks().create(String.format(TASK_NAME, variant.name.capitalize()), KLint.class, new KLint.GlobalConfigAction(globalScope, variant, KLintOptions.create(project)))
     lintTasks = project.tasks.findAll {
         it.name.startsWith("lint") && it.name.toLowerCase().endsWith(variant.name.toLowerCase())
     }
     lintTasks.each { lint ->
         klintTask.dependsOn lint.taskDependencies.getDependencies(lint)
              lint.dependsOn klintTask
     }
}

檢查實(shí)時(shí)化

雖然實(shí)現(xiàn)了檢查的自動(dòng)化,但是可以發(fā)現(xiàn)執(zhí)行自動(dòng)檢查的時(shí)機(jī)相對(duì)滯后,往往是開(kāi)發(fā)同學(xué)準(zhǔn)備合代碼的時(shí)候,這時(shí)再去修改代碼成本高并且存在風(fēng)險(xiǎn)。CI上的自動(dòng)檢查應(yīng)該是作為是否有“漏網(wǎng)之魚(yú)”的最后一道關(guān)卡,而問(wèn)題應(yīng)該暴露在代碼編寫的過(guò)程中?;诖耍覀冮_(kāi)發(fā)了Kotlin代碼實(shí)時(shí)檢查的IDE插件。

Kotlin代碼檢查的示例分析

KLint IDE插件

通過(guò)這款工具,實(shí)現(xiàn)在Android Studio的窗口實(shí)時(shí)報(bào)錯(cuò),幫助開(kāi)發(fā)同學(xué)第一時(shí)間發(fā)現(xiàn)問(wèn)題及時(shí)解決。

Kotlin代碼檢查實(shí)踐

KLint插件分為Gradle插件和IDE插件兩部分,前者在build.gradle中引入,后者通過(guò)Android  Studio安裝使用。

KLint規(guī)則的編寫

針對(duì)上面列舉的lazy()中未指定mode的case,KLint實(shí)現(xiàn)了對(duì)應(yīng)的檢查規(guī)則:

public class LazyDetector extends Detector implements Detector.KtPsiScanner {
    public static final Issue ISSUE = Issue.create(
            "Lazy Warning", 
            "Missing specify `lazy` mode ",

            "see detail: https://wiki.sankuai.com/pages/viewpage.action?pageId=1322215247",

            Category.CORRECTNESS,
            6,
            Severity.ERROR,
            new Implementation(
                    LazyDetector.class,
                    EnumSet.of(Scope.KOTLIN_FILE)));
    @Override     public List> getApplicableKtPsiTypes() {
        return Arrays.asList(KtPropertyDelegate.class);
    }
    @Override     public KtVisitorVoid createKtPsiVisitor(KotlinContext context) {
        return new KtVisitorVoid() {

            @Override             public void visitPropertyDelegate(@NotNull KtPropertyDelegate delegate) {
                boolean isLazy = false;
                boolean isSpeifyMode = false;
                KtExpression expression = delegate.getExpression();
                if (expression != null) {
                    PsiElement[] psiElements = expression.getChildren();
                    for (PsiElement psiElement : psiElements) {
                        if (psiElement instanceof KtNameReferenceExpression) {
                            if ("lazy".equals(((KtNameReferenceExpression) psiElement).getReferencedName())) {
                                isLazy = true;
                            }
                        } else if (psiElement instanceof KtValueArgumentList) {
                            List valueArguments = ((KtValueArgumentList) psiElement).getArguments();
                            for (KtValueArgument valueArgument : valueArguments) {
                                KtExpression argumentValue = valueArgument.getArgumentExpression();
                                if (argumentValue != null) {
                                    if (argumentValue.getText().contains("SYNCHRONIZED") ||
                                            argumentValue.getText().contains("PUBLICATION") ||
                                            argumentValue.getText().contains("NONE")) {
                                        isSpeifyMode = true;
                                    }
                                }
                            }
                        }
                    }
                    if (isLazy && !isSpeifyMode) {
                        context.report(ISSUE, expression,context.getLocation(expression.getContext()), "Specify the appropriate thread safety mode to avoid locking when it’s not needed.");
                    }
                }
            }
        };
    }
}

檢查結(jié)果

Gradle插件和IDE插件共用一套規(guī)則,所以上面的規(guī)則編寫一次,就可以同時(shí)在兩個(gè)插件中使用:

  • CI上自動(dòng)檢查對(duì)應(yīng)的檢測(cè)結(jié)果的html頁(yè)面:  
    Kotlin代碼檢查的示例分析

檢測(cè)結(jié)果的html頁(yè)面

  • Android Studio上對(duì)應(yīng)的實(shí)時(shí)報(bào)錯(cuò)信息:

    Kotlin代碼檢查的示例分析

實(shí)時(shí)報(bào)錯(cuò)信息

借助KLint插件,編寫檢查規(guī)則來(lái)約束不規(guī)范的Kotlin代碼,一方面避免了隱藏開(kāi)銷,提高了Kotlin代碼的性能,另一方面也幫助開(kāi)發(fā)同學(xué)更好的理解Kotlin。

看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對(duì)創(chuàng)新互聯(lián)的支持。


文章名稱:Kotlin代碼檢查的示例分析
文章地址:http://weahome.cn/article/ipjsdc.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部