搜索
您的当前位置:首页正文

Android从零单排(一)项目创建

来源:知库网

写在前头

  1. 本文编辑于2016年10月2日,请注意技术的时效性
  2. 请带着批判的态度阅读
  3. 项目工程地址:

读完本文的收获

本文将假设你是入门安卓不久的新手,引导你获取版本规范、代码规范、签名生成以及手动发版的流程。


丑陋的目录

开发环境

工具 版本
Window 10
2.2
2.14.1
1.8.0
2.9.0.windows.1
2.2.0.0
2.5.2

1. 项目初始化

1.1 项目构建

项目的构建过程还是很简单的,打开我们的Android Studio,如图1.1.1所示:

1.1.1 Android Stuido主界面
先去设置代理,有助于翻墙,点选Configure,在下拉菜单中选择Settings,在弹出的设置界面,进行如图1.1.2所示的配置(作者使用的是,代理端口为1080,请根据具体的VPN自行配置):
1.1.2 代理配置

设置完毕后,返回主界面,选择Start a new Android Studio project选项卡,填写相应的数据信息,如图1.1.3所示:


1.1.3 创建一个新项目

OK这个名字是Dota里面全能骑士的简称,不过无所谓了,继续下一步是设置最小支持版本,如图1.1.4所示:

1.1.4 设置最小支持版本

为什么要设置4.0以上呢,因为4.0以上的机器已经占据了97.4%,现在除非是一些大厂,其他很多应用已经不支持2.3以下了。后续的操作,因为不影响开发,一路next即可,最后完成时,会弹出代理设置,也点选OK即可,如图1.1.5所示。


1.1.5 代理设置

1.2 版本规范

1.2.1 生成.gitignore文件

做完这些操作以后,创建第一次提交,如图1.2.2所示:


1.2.2 代码提交

其后的分支管理规则可以根据以下制定:

  1. master分支,稳定发布的分支,代表最后一次稳定的全量发版分支。
  2. develop分支,表示开发分支,用于代码合并、回滚,代码审查。
  3. release分支,发布分支,往往伴随tag标签一起标注。
  4. 其他分支,可以用版本区分,主要用于开发。

作为技术主管,只要管控好master,develop,release分支即可。

2. 开发之前

2.1 生成签名

第一步,进入Android Studio项目界面,Build->Generate Signed APK... 如图2.1.1所示:

2.1.1 生成签名
第二步,填写相应的信息,如图2.1.2所示:
2.1.2 填写信息
需要注意的是,一般需要生成两个签名,一个用于debug,一个用于release;组内成员使用debug版本,release签名由技术主管之类的管控。

2.2 代码规范

2.2.1 查找插件

在弹出的对话框中,搜索CheckStyle,并点击Install安装,如图2.2.2所示


2.2.2 安装插件

安装完毕后,按照操作提示,需要重启AS,如图2.2.3所示:


2.2.3 重启AS

<br />
接下来,添加规则,打开设置界面,进行设置,如图2.2.4所示:


2.2.4 设置代码规范

设置完成后,就可以在工程中使用了,有什么好处呢?举个例子来说,如图2.2.5所示:


2.2.5 一个例子

不规范的地方会出现高亮提示,同时鼠标放上去会有提示语,你也可以通过CheckStyle窗口来查看,在代码区域,右键,点击Check Current File,如图2.2.6所示:


2.2.6 Check Current File

那么在CheckStyle选项卡中就能看到关联信息了,如图2.2.7所示:


2.2.7 CheckStyle选项卡

然后还要设置CodeStyle,在设置页面,如图2.2.8所示:


2.2.8 CodeStyle

导入之前的CheckStyle.xml到CodeStyle,如图2.2.9所示:


2.2.9导入CodeStyle

最后,我们需要设置文件头,在设置界面操作,如图2.2.10所示:


2.2.10 设置文件头

别忘了,将关联的资料备份到工程中,如图2.2.11所示:


2.2.11 备份资料

2.3 Gradle配置

Gradle中配置有以下需要注意的事项:

  1. 将全局变量、密钥等抽取出来
  2. 区分debug版本与release版本,使其可以共存在同一个手机上
  3. 代码、资源开启混淆

这一部分工作,主要参考了以下链接:

参见详细代码如下:

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

// 配置全局变量
ext {
    compileSdkVersion = 24
    buildToolsVersion = "24.0.0"
    minSdkVersion = 14
    targetSdkVersion = 24
    versionCode = 1
    versionName = "1.0"
}
apply plugin: 'com.android.application'

android {

    compileSdkVersion 
    buildToolsVersion rootProject.ext.buildToolsVersion

    defaultConfig {
        applicationId "com.example.whr.ok"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode rootProject.ext.versionCode
        versionName rootProject.ext.versionName
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        // 设置应用名称,注意删除res目录下的app_name
        resValue("string","app_name","OK")
    }

    // lint 检查出错后 不停止构建任务
    lintOptions {
        abortOnError false
    }

    signingConfigs {
        sign {
            // 默认local.properties不加入版本控制中
            Properties properties = new Properties()
            properties.load(project.rootProject.file('local.properties').newDataInputStream())
            def keystore = properties.getProperty("KEY_STORE")
            def storepasswd = properties.getProperty("KEY_STORE_PASSWORD")
            def alias = properties.getProperty("KEY_ALIAS")
            def aliaspasswd = properties.getProperty("KEY_ALIAS_PASSWORD")

            storeFile file(keystore)
            storePassword(storepasswd)
            keyAlias alias
            keyPassword aliaspasswd
        }
    }

    buildTypes {
        debug {
            // 读取设置的签名文件
            signingConfig signingConfigs.sign
            // 不进行代码优化
            minifyEnabled false
            // 不进行zip对齐
            zipAlignEnabled false
            // 为包名添加后缀,使调试与正式包可以并存
            applicationIdSuffix ".debug"

            // 设置应用名称,注意删除res目录下的app_name
            resValue("string","app_name","Test_" + new Date().format("MM-dd HH:mm"))

            // 在调试包中禁用lint
            project.gradle.startParameter.excludedTaskNames.add("lint")
        }

        release {
            // 读取设置的签名文件
            signingConfig signingConfigs.sign
            minifyEnabled true
            shrinkResources true
            proguardFiles 'proguard-rules.pro'
        }

        android.applicationVariants.all { variant ->
            variant.assemble.doLast {
                String outname = "";
                variant.productFlavors.each { productFlavor ->
                    println("productFlavor:" + productFlavor.name);
                    outname += productFlavor.name;
                }

                println("buildType:" + variant.buildType.name);
                // 是正式版本才进行资源混淆
                if(variant.buildType.name != 'release') {
                    return;
                }

                variant.outputs.each { output ->
                    // 注释以下一行可以取消自动资源混淆的方法
                    runResourceGuard(output.outputFile, outname);
                }

            }
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:24.2.1'
    testCompile 'junit:junit:4.12'
}

/** 执行资源混淆的方法*/
void runResourceGuard(File outFile, String outName) {
    String finalOutDir = outFile.getParentFile().getAbsolutePath();

    String name = getFileNameWithoutExt(outFile.getName());
    if(outName == null || "".equals(outName)) {
        outName = name;
    }
    String outDirPath = "build/outputs/resguard/" + outName;

    println("输出目录:" + outDirPath);

    File outDir = file(outDirPath);
    if(outDir.exists()) {
        delete(outDir.getAbsolutePath());
    }
    outDir.mkdirs();

    //读取签名信息
    String storeFile = android.signingConfigs.sign.storeFile.getAbsolutePath();
    String storePassword = android.signingConfigs.sign.storePassword;
    String keyAlias = android.signingConfigs.sign.keyAlias;
    String keyPassword = android.signingConfigs.sign.keyPassword;

    println("签名:" + storeFile + " " + storePassword + " " + keyAlias + " " + keyPassword);

    String commandExt = isWindowsOS() ? ".exe" : "";

    String zipalignPath = android.getSdkDirectory().getAbsolutePath() + "/build-tools/" + android.buildToolsVersion + "/zipalign" + commandExt;

    println("zipalignPath=" + zipalignPath);

    //执行资源混淆方法
    def exit = javaexec {
        main = "-jar"
        args = [
                "../resources/AndResGuard/resourcesproguard.jar",
                outFile.getAbsolutePath(),
                "-config", "../resources/AndResGuard/config.xml",
                "-out", outDirPath,
                "-7zip", "../resources/command/7za" + commandExt,
                "-zipalign", zipalignPath,
                "-mapping", "../resources/AndResGuard/resource_mapping.txt",
                "-signature", storeFile, storePassword, keyPassword, keyAlias
        ].toList()
    }

    println("运行结果:" + exit);
    if(exit.exitValue != 0) {
        return;
    }

    //将混淆后apk包替换原来的apk包
    name += "_signed_aligned.apk";
    File outApkFile = new File(outDir, name);

    outFile.delete();
    if(!outApkFile.exists()) {
        println("资源混淆文件失败!删除原文件!");
    } else {
        //自定义输出的文件名,如 demo_100_1.0.0.apk
        String outputname = "OK_" + android.defaultConfig.versionCode + "_" + android.defaultConfig.versionName;

        String tempExt = "";

        int count = 0;
        while(true) {
            File temp = new File(finalOutDir, outputname + tempExt + ".apk");
            if(!temp.exists()) {
                break;
            }
            tempExt = "(" + (++count) + ")";
        }

        outputname += tempExt + ".apk";

        println("最终文件名为:" + outputname);

        copy {
            from(outDirPath) {
                include name
            }
            into finalOutDir
            rename(name, outputname)
        }
    }
}

/**
 * <p>获取文件名,不带扩展名</p>
 * 如path=/sdcard/image.jpg --> image
 *
 * @param filePath
 * @return
 */
String getFileNameWithoutExt(String filePath) {
    if (filePath == null || "".equals(filePath)) {
        return null;
    }
    int last = filePath.lastIndexOf("/");
    int index = filePath.lastIndexOf(".");
    if (last == -1 && index == -1) {
        return filePath;
    } else if (index > last) {
        return filePath.substring(last + 1, index);
    } else {
        return filePath.substring(last);
    }
}

/**
 * @return true---是Windows操作系统
 */
boolean isWindowsOS(){
    boolean isWindowsOS = false;
    String osName = System.getProperty("os.name");
    println("os.name=" + osName);
    if(osName == null || "".equals(osName)) {
        return false;
    }
    if(osName.toLowerCase().indexOf("windows") > -1){
        isWindowsOS = true;
    }
    return isWindowsOS;
}

至于local.properties文件,它需要添加如下代码:

KEY_STORE=../resources/debug.jks
KEY_STORE_PASSWORD=123456
KEY_ALIAS=debug
KEY_ALIAS_PASSWORD=123456

2.4 混淆配置

这一部分工作,主要参考了以下链接:

##############################################通用配置##############################################
## 常规不用混淆的类
-keep public class * extends android.app.Fragment
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.preference.Preference
-keep public class * extends android.content.ContentProvider

# 保留继承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**

-dontwarn android.support.v4.**

# 保留support下的所有类及其内部类
-keep class android.support.** {*;}

# 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度。
-dontpreverify

# 保留Annotation不混淆
-keepattributes *Annotation*,InnerClasses
# 避免混淆泛型
-keepattributes Signature
# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
# 保留本地native方法不被混淆
-keepclasseswithmembernames class * {
    native <methods>;
}

# 保留在Activity中的方法参数是view的方法,
# 这样以来我们在layout中写的onClick就不会被影响
-keepclassmembers class * extends android.app.Activity{
    public void *(android.view.View);
}
# 保留枚举类不被混淆
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
# 保留Parcelable序列化类不被混淆
-keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}
# 保留Serializable序列化的类不被混淆
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    !static !transient <fields>;
    !private <fields>;
    !private <methods>;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

# webView处理,项目中没有使用到webView忽略即可
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
    public *;
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
    public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.webView, jav.lang.String);
}


# 去除调试的日志
-assumenosideeffects class android.util.Log{ public static *** d(...); public static *** i(...); public static *** e(...); public static *** v(...); public static *** w(...);}
#################################################################################################

3. 手动发版

3.1 生成签名APK

先点击Build->Generate Signed APK..., 如图3.1.1所示:


3.1.1 点击生成签名APK

填写关联信息,如图3.1.2所示:


3.1.2 填写关联信息

选择release选项卡,点击Finish即可,如图3.1.3所示:


3.1.3 确认生成
Top