Gradle腳本打包AndroidStudio依賴庫的問題


  古人雲:“以銅為鏡,可以正衣冠;以古為鏡,可以知興替;以人為鏡,可以明得失;而以法為鏡,可以斷曲直。”  

  目前,國內大多數(99%)渠道在提供給CP渠道SDK時,都會有eclipse的接入方式。但畢竟現在google爸爸已經棄用了eclipse開發方式,AndroidStudio是官方指定使用的開發工具,有一些渠道,特別是剛開始做SDK的一些小渠道,為了方便快捷,使用AndroidStudio進行開發,且提供給CP接入方式也僅僅有一個AndroidStudio接入方式,這個問題對於目前的聚合SDK接入就是一個比較麻煩的問題。比如U8,quick,主要都是eclipse方式的聚合SDK。AndroidStudio對於我們這種eclipse接入方式的最大問題是什么呢。是因為AndroidStuido使用的是gradle自動構建工具,使用的第三方依賴庫,直接在項目中gradle中依賴即可,而且依賴庫麻煩的是內部依賴,一個依賴庫可能內部依賴有其他第三方庫。這就使我們獲取jar包有一定的難度了。誰知道什么依賴庫下依賴着什么呢。

  問題解決,當我遇見這第一個渠道的時候,思考,當前渠道提供給我的有什么,而我需要什么,還缺少什么。在互聯網的知識海里遨游,希望尋找到遇到同樣困境並已解決的知己伙伴。果真,在隨着技術進步的發展下,更多人也喜歡使用AndroidStudio的方式進行開發,那么有人也提出了一些解決辦法。如U8解決方案:在AndroidStudio正常接入,該依賴的依賴,該配置的配置,該寫的代碼寫上,然后將整個項目編譯成一個apk,反編譯,獲取里面的資源文件,然后,將項目打包成jar,提供給我們的聚合SDK依賴。沒錯,這的確是一個好辦法,但是對於我的需求來說,算是比較麻煩,首先,我維護更新渠道SDK都得AndroidStudio下,那么就需要Eclipse和AndroidStudio兩個開發工具下跑,增加了自己開發維護成本和以后工作交接后,同事在背后的種種譴責;其次,在我們大陸,有幸的隔離了紛紛擾擾的牆外,我們渠道SDK也無需用到google服務等可以通過AndroidStudio特殊打包方式編譯的,所以在這個方案的前提下,思考既然可以將項目打包成jar,那我是不是可以將我現在缺少的渠道SDK提供過來的依賴庫生成jar給我聚合SDK進行依賴呢。然后就又是一頓遨游。例如gradle打包jar,gradle打包依賴庫等等。果真,結合各種答案,在親測下,找到一個符合我自己目前需求的解決辦法。講了那么多廢話,下面開始正題:

  使用shadow插件進行依賴庫打包

  首先,還是正常將第三方庫依賴,同步跑起來,將第三方庫的文件都下載到本地,項目無報錯。然后修改自己的gradle,怎樣修改呢,如下:

apply plugin: 'java'
apply plugin: 'com.github.johnrengelman.shadow'

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.1'
    }
}

repositories {
    mavenCentral()
    flatDir {
        dirs'libs'
    }
}

shadowJar {
    baseName = 'library'
    classifier = null
    version = null
    dependencies {
        include(dependency('com.squareup.retrofit2:retrofit:2.2.0'))
        include(dependency('com.squareup.retrofit2:converter-gson:2.2.0'))
        include(dependency('com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'))
        include(dependency('com.squareup.okhttp3:okhttp:3.10.0'))
        include(dependency('com.squareup.okhttp3:logging-interceptor:3.8.0'))
        include(dependency('io.reactivex.rxjava2:rxjava:2.0.5'))
        include(dependency('io.reactivex.rxjava2:rxandroid:2.0.1'))
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    // 添加Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.2.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.2.0'
    implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
    // 添加okhttp
    implementation 'com.squareup.okhttp3:okhttp:3.10.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.8.0'
    // RxJava
    implementation 'io.reactivex.rxjava2:rxjava:2.0.5'
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
}

  如上,這就是我當前遇到的一個只提供AndroidStudio方式提供過來的,dependencies就是渠道SDK需要依賴的第三庫,然后在shadowJar方法中dependencies加入我們需要打包的第三方依賴庫,如上。OK,通過這個腳本,我們在AndroidStudio Terminal窗口下執行”gradlew shadowJar“,我們可以看到BUILD SUCCESSFUL,歡呼吧,雀躍吧。我覺得我解決了天大的問題,是能比肩中國四大發明的創舉。然后拿到build/libs下的我通過腳本生成的這個library.jar,使用壓縮軟件打開看下,嗯嗯,確實是每個依賴庫的代碼也在,但是rxandroid這個依賴庫是以aar形式進行依賴的,這需要我們手動拷貝出來,然后將對應的依賴庫classes.jar獲取。行,我想現在所要用到的第三方庫就齊全了吧,正常接入,跑起來。What the fuck。Ru'ntimeException,未找到okio的什么類。咦,確實是,okhttp似乎是依賴okio使用的,但是gradle依賴無需再次引用該依賴,這就使我們打包的時候缺失了該第三方庫了。尋找解決辦法,我現在需要知道所有依賴庫的依賴關系,我不可能一個個依賴去查對應的資料,是否有進行依賴吧。怎樣可以知道整個依賴樹關系呢,gradle通過命令可以查找,但是這個命令實在繁瑣,AndroidStudio有個插件叫gradle view,可以看到當前項目的依賴樹關系。如圖:

  原來,我剛剛打包的jar確實是有依賴其他第三方庫的,得到答案,那我們就查缺補漏,將依賴關系樹中未依賴的第三方庫進行在gradlr腳本中進行依賴,然后再次執行腳本文件,同樣,build成功,拿到jar替換剛剛生成的不全面的jar包,打包,運行,運行無誤。

  以上,就是我對一些需求不復雜且未使用特殊第三方庫的渠道SDK的依賴庫打包獲取,從而得到一個完整的SDK依賴。在這里有那么幾個缺點:

  1、例如v7這些依賴,使用很多android資源的,見仁見智吧,可以不依賴,單獨去sdk里面拿v7這個jar和資源,也可以依賴打包,但資源文件還是需要去sdk里拿到的;

  2、例如rxandroid依賴的aar,我們通過腳本打包進去的也是rxandroid的aar文件,我們還需要將他單獨拿出來,並確認aar中res下有沒有資源文件,有則也需要獲取。這個問題,我們可以寫個腳本,解壓aar,拷貝jar,如果有資源則復制合並資源,在我印象中,依賴庫除了android本身的帶有資源,其他還沒見到過帶有res資源文件的,所以這不是一個很大的問題,自己有sdk,有腳本,需要的都是能拿到的,這個腳本后面有空再更新上來

  3、例如海外渠道,用到google服務的等一些特殊需求時,比如google-service.json文件,在gradle下會打包自動編譯的,這些問題就相對比較麻煩,雖然是有方法集成到eclipse下,還是建議U8的解決方案

  處理aar的工具腳本如下:

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Desc:處理aar工具

import argparse
import os
import os.path
import zipfile
import sys
import platform as platform_lib
import codecs
import shutil
import zipfile
import re
import subprocess

platform = 'nano_default'

curDir = os.getcwd()

def is_include(fpath, include_path):
    for ig in include_path:
        if ig in fpath:
            return True
    return False

def un_zip(file_name):
    """unzip zip file"""
    zip_file = zipfile.ZipFile(file_name)
    file_path = file_name[0:-4]
    if os.path.isdir(file_path):
        pass
    else:
        os.mkdir(file_path)
    print(u'Now to unzip aar to dir::'+file_path)
    for names in zip_file.namelist():
        zip_file.extract(names, file_path+"/")
    zip_file.close()

def getCurrDir():
    global curDir
    retPath = curDir
    if platform_lib.system() == "Windows":
        retPath = retPath.decode('gbk')
    return retPath


def getFullPath(filename):
    if os.path.isabs(filename):
        return filename
    currdir = getCurrDir()
    filename = os.path.join(currdir, filename)
    filename = filename.replace('\\', '/')
    filename = re.sub('/+', '/', filename)
    return filename


def copy_files(src, dest):
    if not os.path.exists(src):
        print(u"copy files . the src is not exists.path:"+src)
        return

    if os.path.isfile(src):
        copy_file(src, dest)
        return

    for f in os.listdir(src):
        sourcefile = os.path.join(src, f)
        targetfile = os.path.join(dest, f)
        if os.path.isfile(sourcefile):
            copy_file(sourcefile, targetfile)
        else:
            copy_files(sourcefile, targetfile)


def copy_file(src, dest):
    sourcefile = getFullPath(src)
    destfile = getFullPath(dest)
    if not os.path.exists(sourcefile):
        return
    if not os.path.exists(destfile) or os.path.getsize(destfile) != os.path.getsize(sourcefile):
        destdir = os.path.dirname(destfile)
        if not os.path.exists(destdir):
            os.makedirs(destdir)
        destfilestream = open(destfile, 'wb')
        sourcefilestream = open(sourcefile, 'rb')
        destfilestream.write(sourcefilestream.read())
        destfilestream.close()
        sourcefilestream.close()


if __name__ == '__main__':

    print(u"1、Now to handle aar file.")

    includes = ["assets", "res", "libs", "classes.jar", "AndroidManifest.xml"]

    xmlList = ['strings.xml', 'styles.xml', 'colors.xml', 'dimens.xml', 'ids.xml', 'attrs.xml', 'integers.xml',
               'arrays.xml', 'bools.xml', 'drawables.xml', 'values.xml']

    parser = argparse.ArgumentParser(u"aar處理工具")

    parser.add_argument('-p', '--platform', help=u"渠道名稱或渠道標識,用於修改部分res文件名稱,避免沖突覆蓋", default="nano_default")
    args = parser.parse_args()

    platform = args.platform
    print(u"2、Handle channel is " + platform)

    path = "."
    targetPath = "../aar"

    if not os.path.exists(targetPath):
        os.makedirs(targetPath)

    index = 2
    for root, dirs, files in os.walk(path):
        for f in files:
            if f.endswith(".aar"):
                aarName = f[:-4]
                index = index+1
                print(index.__str__() + u"、Now to handle aar::" + f)
                fpath = os.path.join(root, f)
                un_zip(fpath)
                print(u"Now to copy file to targetPath")
                aarDirPath = fpath[:-4]
                for aarRoot, aarDirs, aarFiles in os.walk(aarDirPath):
                    for sdkFile in aarFiles:
                        sdkFile = os.path.join(aarRoot, sdkFile)
                        if is_include(sdkFile, includes):
                            ftargetpath = sdkFile[len(aarDirPath):]
                            # 修改jar文件為aar文件名稱
                            if "classes.jar" in ftargetpath:
                                ftargetpath = ftargetpath.replace('classes', aarName)

                            # 修改xmlList列表中的文件的文件名稱,避免多個aar文件名稱沖突或與v7包下的資源名稱沖突
                            if is_include(ftargetpath, xmlList):
                                ftargetpathList = list(ftargetpath)
                                ftargetpathList.insert(-4, "_"+aarName)
                                ftargetpath = ''.join(ftargetpathList)

                            # 修改AndroidManifest.xml文件名稱
                            if "AndroidManifest.xml" in ftargetpath:
                                ftargetpathList = list(ftargetpath)
                                ftargetpathList.insert(-4, "_" + aarName)
                                ftargetpath = ''.join(ftargetpathList)

                            ftargetpath = targetPath + ftargetpath
                            print ftargetpath
                            copy_files(sdkFile, ftargetpath)

                shutil.rmtree(aarDirPath)

  例如愛奇藝渠道,就是強烈推薦使用AndroidStudio方式接入的一個渠道,SDK資源有兩個aar,那么我們可以創建一個文件夾,或者就在自己的聚合SDK接入lib文件夾下,將腳本拷入:

  

  執行腳本,在當前文件夾的父目錄同級下生成一個aar文件夾,這個文件夾下就是拷貝出了aar內中所有的資源,當然拷貝的路徑完全可以自己選擇,可以像我一樣aar資源先統一拷貝到一個文件夾,也可以你直接就往項目里面拷,都是可以的:

  

  OK,那么我們該拷貝的拷貝,AndroidManifest.xml這些我們就手動一下吧,聚合SDK合並AndroidManifest還是需要注意一點的,這里就不做簡單的兩個文件合並,而是在文件名后面添加對應的aar名字,自行手動合並吧。

  雖然這個方案不是一個最棒的方案,小小的特殊需求尋求小小的特殊方法解決,對於國內渠道來說解決了公司聚合SDK接入方式困境,最主要的是方便,能統一在eclipse下維護更新SDK。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM