内网搭建Android环境

  前言:由于最近的工作环境是处于内网环境下,所以没法使用公开的网络,导致了无法正常使用android studio和下载三方库,为此折腾了很久,踩了许多坑,网上找的很多资料不仅少,而且不全,很难解决,最后得出的经验希望能够帮助遇到同样问题的同学。

  关键字:Android Studio,Android SDK,offline-gmaven-stable,gradle,maven
  内网:可以理解成断网,即不能连接公网,但是用网线连接的设备之间可以通信。

  由于搭建的流程比较复杂,我这里用一幅图展示,给大家梳理一下。


内网配置Android环境

一 安装Android Studio配置SDK

  安装后提示需要配置sdk路径,如图

提示没有sdk.png

  我们的sdk可以从我们公网中的设备里面复制过来,然后我们点击右下角,找到配置sdk路径的位置,如图

配置sdk1

  点击edit,选择我们之前获取到的sdk的位置
  正常情况下sdk是可以直接下载,因为这是内网环境,所以我们只能直接从在公网环境的电脑中把sdk复制过来进行使用,公网中的sdk放置的位置同样参考图-配置sdk1即可找到。
  如下图
配置sdk2

  点击确认之后,成功之后如下图
配置sdk3

  配置好sdk后,点击运行,依然会提示错误,告诉我们没有当前gradle版本,如图
gradle配置1

  当前使用的gradle版本是6.5,会提示错误
gradle配置1

  需要注意的是android studio中的gradle涉及俩个概念,一个gradle,一个是gradle插件,分别都有不同的版本。
  gradle的版本配置在gradle-wrapper.properties文件中,形如

  gradle插件的版本配置在build.gradle文件中,形如

  gradle版本和gradle插件之间的对应关系,一个项目中的gradle和gradle插件需要相互对应

  更多的可以参考:Android Gradle 插件版本说明
  小插曲结束,我们gradle依然可以用我们公网中的设备,找到gradle,然后复制到内网机器,从android studio的设置中可以查看gradle的路径配置,如图
gradle路径配置

  如图,默认在用户盘的根目录下,我们把.gradle文件中的内容全部复制出来,里面文件形如

.gradle文件夹目录

  caches文件里面存储了就是gradle解压后的版本,我这里的gradle的版本写的是

distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip

  我们查看caches里面有什么

caches文件夹目录

  没错,有文件夹6.1.1,里面存储正是我们使用的gradle版本,还有其余的版本,都是其他的项目创建的时候缓存下来的,所以随着项目的越来越多,gradle版本的变化,caches文件也会越来越大。
如果不想用这个gradle版本的同学,可以下载其他版本,前往Gradle下载地址
  示意图
Gradle版本

  接下来我们用内网机器配置下复制过来的gradle路径,然后观察下caches里面有哪些可用的版本,然后更改一下gradle-wrapper.properties文件内的gradle版本,顺便把build.gradle文件里面的gradle插件版本更改成对应的版本,
  我这里gradle插件版本写的是

classpath "com.android.tools.build:gradle:3.1.0"

  然后点击sync now
  不出意外,到这里项目就是可运行状态了
  点击run app,可以正常安装运行

  但是正常情况我们都会引用第三方库,我们这里试一下,添加一个依赖,如下
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.50'
  然后点击sync now,嗯,没有问题,然后点击run app,提示错误了


gradle配置3

  大致意思就是找不到这个库,当然了,在内网环境下,根本没法下载,自然没有这个库,我们看看怎么把这个库放进来。

二 下载官方库和普通三方库

  仓库里面的库分为俩种类,一种是类似Glide,Rxjava,BaseRecyclerViewAdapterHelper这种第三方库,另一种是官方库,比如RecyclerView,Fragment这种,俩种库放在一起,但是获取方式却不一样,下面分别介绍:

  获取官方库

  我们来到官网offline-gmaven-stable
  还是官方聪明,都提供了一个现成的库让我们直接下载使用,我们找到

offline-gmaven-stable

  然后点击下载
offline-gmaven-stable下载页

  文件大概有3个G左右,下载好之后解压,里面有俩个压缩包,  暂时只用到gmaven-stable,里面就是我们需要的官方库,我们可以随便点一个进去看看,如图
官方库实例

  最多就四个文件类型,好了,到这里我们获取到了官方库。

  获取普通三方库

  有俩种方法,方法二也可以用来获取官方库

  方法一:从中心仓库下载
  中心仓库有很多,maven其中一个网址地址:maven仓库
  直接在上面搜需要使用的库的名称,我用上面的BaseRecyclerViewAdapterHelper去搜,没有搜到,可能是这个库不在这个仓库,因为还有很多其他仓库,我为了演示随便搜另一个库Snackbar,点击搜索如下:

SnakeBar示意图

  有很多匹配的,正常情况下我们应该怎么选呢,我们用BaseRecyclerViewAdapterHelper 这个库作示范,全名为

com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.50

  实际上可以拆成三部分,

group ID:com.github.CymChad
Artifact Id : BaseRecyclerViewAdapterHelper:
Version : 2.9.50

  这样我们就知道从下面的列表中选择哪一个了,这里我们就随便选一个
  点击进去,如下图

SnakeBar下载页

  我们点击右边的download按钮,可能会提示如下
SnakeBar下载类型

  我们全部下载下来,就是三个文件,有的库最多会有四个文件,分别是.aar,.pom,-javadoc.jar,sources.jar结尾的文件,至此获取到一个库的源文件。
  方法二:公网设备内复制粘贴
  除了从仓库下载,我们还可以从公网安装了android studio的机器里面复制已经添加依赖并且已经下载文件的项目里面复制我们需要的库,步骤如下,我们在三方库的位置找到我们需要用的库:
  Mac电脑:找到路径/Users/mac/.gradle/caches/modules-2/files-2.1
  window电脑:同理,在用户盘(一般是C盘),下面找到用户,然后进去,再看到用户名称,再进去即可找到.gradle,后面路径和mac一致,然后可以看到很多已经下载好的库在里面,如下图
三方库集合列表

  找到我们需要库,打开可以看到


三方库详情

  可以看到里面也有四个不同后缀的文件,但是和我们方法1下载后不一样的是,这些文件分别在一些命名长长的文件夹里面,这些文件夹的名称是通过里面文件计算SHA1得到的,但是这里不影响,我们把里面的四个文件复制出来即可。

  通过方法一,二获取普通三方库文件的方法到此结束。

三 配置本地仓库

  到这里需要说一下仓库的概念了,正常我们在添加依赖后,点击sync now后,android studio就会从仓库下载我们添加引用的仓库,常用的仓库有俩个google和jcenter,但是内网没法通过网络直接下载,所以我们就需要配置一个本地仓库。

步骤一 把库放在指定位置
  具体操作:我们可以在本地磁盘建一个文件夹,里面用来存储我们需要添加依赖,我这里建在了F盘下的android文件下,起名repo。

  官方库
  我们直接将之前下载的gmaven-stable文件里面的内容全部复制进repo里面即可,即本地库有了官方库。

  普通三方库
  用com.nispok.slidingtabs.1.0.0这个库举例,把已经下载好的这个库的几个文件放进去,不过不是直接放到repo里面进去,需要按照次序,建立三个文件,先建com文件夹,然后进去,再建nispok文件夹,再建slidingtabs文件夹,再进去,再建立文件夹1.0.0,再进去,最后再把三个文件放进去,细心的同学注意到了,最后的文件夹嵌套就是com/nispok/slidingtabs/1.0.0,如果是像我们用的BaseRecyclerViewAdapterHelper,那文件夹嵌套就是com/github/CymChad/BaseRecyclerViewAdapterHelper/2.9.50,和我们上面的官方库文件夹嵌套模式保持一摸一样,如果官方库已经建立了某些文件夹,普通三方库就不用建,此时本地库里面也就有普通三方库。
  有的同学觉得这种一层层的建立文件夹太复杂了,后面会提供一个工具,参考第四模块,学到了什么里面的公网设备三方库代码转换。
  总而言之,我们获得了需要用到的官方库和第三方库,通过合理的文件夹嵌套放在了指定的位置,这里就是F:android/repo文件夹下

步骤二 android studio添加本地库
  具体操作:需要更改我们项目的配置,找到整个项目的build.gralde,正常情况下,allprojects里面只有google()和jcenter(),但是这里我们获取到了库并按指定方式放了进去,得到了本地库,我们的gradle里面就需要添加我们这个本地库,在google()或jcenter()后面添加 maven{ url 'file:/F:/android/repo'},即可,点击sync now,本地库就添加成功了,我们新建一个MyAdapter,发现就可以继承BaseQuickAdapter这个第三方库了,终于成功了?

  谁知道我踩了多少坑!!?

  点击run app,嘿嘿,又报错了,如下图


adapter报错

  错误还是乱码,气人,看下解决办法
  第一步:Help—>Edit Custom VM Options


Edit Custom VM Options

  第二步:在打开的文件里(如果没有,就新建),增加 -Dfile.encoding=UTF-8


-Dfile.encoding=UTF-8

  第三步:重启Android studio 就行,对比下修复前和修复后

  好了,不乱码了,看错误提示是缺少v7包下的RecyclerView,那么问题来了,这android studio都什么版本了,还用什么v7,解决办法俩个

  方法1:提升引用的BaseRecyclerViewAdapterHelper的版本,使其引用的不再是v7下的包,而是androidx下的包即可,我用的就是这个方法。
  方法2:下载v7包,按照上面的方法,依次放到文件夹下,这个方法我没试
然后接着运行,可能会提示类似

program type already present:xxx.xxx.xx

  这样的错误,意思就是android和各种support的冲突,我们把项目完全转成androidx即可,删除build.gradle里面的各种的support包即可,如果没法删除,勇士你可以不用androidx,把项目里面的androidx东西全部删除,网上资料很多,这里不做阐述。

  解决androidx和support的冲突后,可能依然会提示缺少其他库,这是因为我们引用的普通第三方库还引用了别的库,那怎么办呢?
  还记得我们上面说的下载普通三方库的方法么,没错,自此开始缺少什么库,都重新拿过来,再放进repo这个本地仓库里面即可,然后运行可能依然会报一些关于主题找不到的错,我这里是找不到的主题直接就不用了,更多解决方法也没做研究,到这里的,后续出现的问题已经和本文要解决的中心问题无关,所以这里不作更多阐述,等到全部库都放进去完毕之后,最终我们即使把build.gradle里面的allprojects下面的google和jcenter删除发现也能正常运行了(天啦~终于结束了~)。

  慢着,你到这里解决了问题,但是你真的理解了么,下面几个问题你能回答出来么?

四 学到了什么?

  问题一 公网设备三方库代码如何转换成可使用的
  上面说了配置本地仓库中的获取普通三方库,除了从官网下载库,然后通过手动建立文件夹的方式,还可以从公网的设备中复制过来,具体路径在上面的获取普通三方库中已经作了介绍,但是我们发现看到的文件夹形式总是com.github.xxxx类似这种,但是我们自定义的仓库里面的文件是不可以这样直接使用的,需要文件夹一层层嵌套才可以正常使用,这里提供一个工具,代码如下:

/**
 * 将Android中使用的gradle缓存目录中的jar包重新处理路径,用于内网离线构建
 **/

public class CopyTool {

    static String path = "C:/Users/Administrator/.gradle/caches/modules-2/files-2.1";

    static String stopName = "files-2.1";


    public static void main(String[] args) {
        System.out.println("Begin to copy");
        processDotForld();
        copyToLastForld();
        System.out.println("Copy finished");
    }

    /**
     * 处理文件夹中带点好的。;例如:D:/test/com.ifind.android/
     */
    public static void processDotForld() {
        File file = new File(path);
        if (file.exists()) {
            LinkedList<File> list = new LinkedList<>();
            File[] files = file.listFiles();
            for (int i = 0; i < files.length; i++) {
                File file2 = files[I];
                if (file2.isDirectory()) {
                    //文件夹
                    File desFile = creatforld(file2);
                    copyFileToDes(file2, desFile);
                } else {
                    //文件//目前不存在
                }
            }
        }
    }

    /**
     * 文件夹拷贝
     *
     * @param source
     * @param des
     */
    public static void copyFileToDes(File source, File des) {
        try {
            copyDir(source.getPath(), des.getPath());
        } catch (Exception e) {
        }
    }

    /**
     * 文件夹拷贝
     *
     * @param sourcePath
     * @param newPath
     * @throws IOException
     */
    public static void copyDir(String sourcePath, String newPath) throws IOException {
        File file = new File(sourcePath);
        String[] filePath = file.list();

        if (!(new File(newPath)).exists()) {
            (new File(newPath)).mkdir();
        }

        for (int i = 0; i < filePath.length; i++) {
            if ((new File(sourcePath + file.separator + filePath[i])).isDirectory()) {
                copyDir(sourcePath + file.separator + filePath[i], newPath
                        + file.separator + filePath[I]);
            }

            if (new File(sourcePath + file.separator + filePath[i]).isFile()) {
                copyFile(sourcePath + file.separator + filePath[i], newPath + file.separator + filePath[I]);
            }

        }
    }


    public static void copyFile(String oldPath, String newPath) throws IOException {
        File oldFile = new File(oldPath);
        File file = new File(newPath);
        FileInputStream in = new FileInputStream(oldFile);
        FileOutputStream out = new FileOutputStream(file);

        byte[] buffer = new byte[2097152];

        DataInputStream dis = new DataInputStream(new BufferedInputStream(in));
        DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(out));

        int length;
        while ((length = dis.read(buffer)) != -1) {
            dos.write(buffer, 0, length);
        }
        dos.flush();
        dos.close();
        dis.close();
    }


    /**
     * 创建文件夹
     *
     * @param file
     */
    public static File creatforld(File file) {
        String path = file.getAbsolutePath();
        if (path != null) {
            String temp = "files-2.1";
            temp = stopName;
            String temS[] = path.split(temp);

            if (temS != null && temS.length == 2) {
                String firstName = temS[0];
                String dotName = temS[1];
                if (dotName != null) {
                    String[] lasts = dotName.split("\\.");
                    int count = lasts.length;
                    if (count < 2) {
                        return null;
                    }
                    String pathNew = firstName + temp;
                    for (int i = 0; i < count; i++) {
                        if (i == 0) {
                            pathNew = pathNew + lasts[I];
                        } else {
                            pathNew = pathNew + "\\" + lasts[I];
                        }
                    }
                    if (pathNew != null && !pathNew.equals("")) {
                        File fileForld = new File(pathNew);
                        if (!fileForld.exists()) {
                            fileForld.mkdirs();
                        }
                        return fileForld;
                    }
                }
            }
        }
        return null;
    }


    public static ArrayList<File> getFile(File file) {
        ArrayList<File> list = new ArrayList<>();
        if (file.isDirectory()) {
            File[] filesTemp = file.listFiles();
            for (int i = 0; i < filesTemp.length; i++) {
                ArrayList<File> result = getFile(filesTemp[I]);
                list.addAll(result);
            }

        } else {
            list.add(file);
        }
        return list;
    }


    // 创建目录
    public static boolean createDir(String destDirName) {
        File dir = new File(destDirName);
        if (dir.exists()) {
            // 判断目录是否存在
            System.out.println("创建目录失败,目标目录已存在!");
            return false;
        }
        if (!destDirName.endsWith(File.separator)) {
            // 结尾是否以"/"结束
            destDirName = destDirName + File.separator;
        }
        if (dir.mkdirs()) {
            // 创建目标目录
            System.out.println("创建目录成功!" + destDirName);
            return true;
        } else {
            System.out.println("创建目录失败!");
            return false;
        }
    }


    public static void copyToLastForld() {
        File file = new File(path);
        if (file.exists()) {
            LinkedList<File> list = new LinkedList<>();
            File[] files = file.listFiles();
            for (int i = 0; i < files.length; i++) {
                File file2 = files[I];
                if (file2.isDirectory()) {
                    //文件夹
                    proceessForld(file2);
                } else {
                    //文件//目前不存在
                }
            }
        }
    }


    private static void proceessForld(File file) {
        File[] files = file.listFiles();
        for (int i = 0; i < files.length; i++) {
            File file2 = files[I];
            if (file2.isDirectory()) {
                //文件夹
                proceessForld(file2);
            } else {
                //文件//目前不存在//判断是否进行拷贝
                try {
                    proceessFile(file2);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    private static void proceessFile(File file) throws FileNotFoundException {
        if (file != null) {
            String path = file.getAbsolutePath();
            if (path != null) {
                String[] lasts = splitString(path);
                if (lasts != null && lasts.length > 0) {
                    int count = lasts.length;
                    String last = lasts[count - 1];
                    String last2 = lasts[count - 2];


                    if (last2 != null && last2.length() > 20) {
                        //拷贝到上一级目录
                        String des = null;
                        if (count < 2) {
                            return;
                        }
                        for (int i = 0; i < count - 2; i++) {
                            if (i == 0) {
                                des = lasts[I];
                            } else {
                                des = des + "\\\\" + lasts[I];
                            }
                        }
                        String strParentDirectory = file.getParent();
                        File parentFile = new File(strParentDirectory);
                        strParentDirectory = parentFile.getParent() + "\\" + last;
                        copy(file, path, strParentDirectory);
                    } else {
                    }
                }
            }
        }
    }


    private static String[] splitString(String path) {
        String[] lasts = null;
        lasts = path.split("\\\\");
        int count = lasts.length;
        boolean isFirst = true;
        for (int i = 0; i < count; i++) {
            String str = lasts[I];
            if (str != null && str.contains(".")) {
                if (isFirst) {
                    isFirst = false;
                    System.out.println("\n\n\n\n");
                    System.out.println("path=" + path + "");
                }
                System.out.println("str=" + str + "");
            }
        }
        return lasts;
    }


    /**
     * copy动作
     *
     * @param file
     * @param source
     * @param des
     * @throws FileNotFoundException
     */
    private static void copy(File file, String source, String des) throws FileNotFoundException {
        if (file != null) {
            FileInputStream fis = null;
            FileOutputStream fot = null;
            byte[] bytes = new byte[1024];
            int temp = 0;
            File desFile = new File(des);
            if (desFile.exists()) {
                return;
            }
            try {
                fis = new FileInputStream(file);
                fot = new FileOutputStream(desFile);
                while ((temp = fis.read(bytes)) != -1) {
                    fot.write(bytes, 0, temp);
                    fot.flush();


                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (fis != null) {
                    try {
                        fis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (fot != null) {
                    try {
                        fot.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }


        }
    }


    private static String getContent(String content) {
        String str = content;
        if (content != null && content.length() > 4) {
            str = content.substring(0, 4);
        }
        return str;
    }

}

  我们可以上面的path改成我们的公网设备里面的三方库路径,然后点击运行,会把这些带点号命名的文件夹拆分成嵌套形,这样我们就不用手动就建立文件夹了。

问题二 Gradle安装配置在什么位置,解压后去哪了?

  首先我们去官网下载我们需要的版本,Gradle下载地址
  下载好之后我们可以放在本地路径,还记得我们的gradle-wrapper.properties里面是什么样子么,如下

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip

  最后一行就是配置我们Gradle版本的地方,实际上我们可以更改成我们本地路径,例如我们下载了gradle-4.2.1-all.zip版本,放在了F盘的android文件夹下的gradle文件夹里面,那这里我们就可以更改成

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=file:///F:/android/gradle/gradle-4.2.1-all.zip

  然后点击sycn now,发现可以正常使用,当然还要注意和Gradle 插件的对应,实际上点击sync now就是解压这个压缩包,我么可以前往用户C盘用户名文件夹下的.gradle里面,看到caches文件夹,里面就有我们刚刚解压的Gradle,所以这也是为什么我们内网的设备直接从外网的设备把.Gradle下的文件全部复制过来就能使用的原因。

问题三 如何知道当前项目使用库是从什么地方来的

  上面我们搭建了一个本地仓库repo,但实际上我们还有默认的,也就是google和jcenter仓库,我们可以打开Android studio,打开Settings,找到Build,Execution,Deployment目录下的Remote Jar Repositories,如下图


Remote Jar Repositories

  这里展示了我们使用库全部会被索引的仓库,如果所有的仓库都没有找到我们需要用到的类或组件,就会报错,就如我们上面说的RecyclerView找不到错误一致
  在Remote Jar Repositories上面还有一个Gradle目录,如下图


Gradle

  里面可以设置Gradle解压后的目录,这个一般不更改,还有Gradle版本的设置,可以指定是否使用gradle-wrapper.properties文件里面的配置或者手动指定目录和问题二效果一致。

  前人种树后人乘凉,这里提供参考的相关资料,希望也能给大家一些帮助,如果依然不能解决,请私聊或移步Q群:561176094.

公司内网离线环境搭建Android Studio
AndroidStudio中的内网开发离线配置
Android 手动下载Gradle的cache依赖包

推荐阅读更多精彩内容