ลงชื่อเข้าใช้ APK โดยไม่ต้องใส่ข้อมูล keystore ใน build.gradle


152

ฉันกำลังพยายามตั้งค่ากระบวนการลงชื่อเพื่อให้รหัสผ่านที่เก็บคีย์และรหัสผ่านคีย์ไม่ถูกเก็บไว้ในbuild.gradleไฟล์ของโครงการ

ขณะนี้ฉันมีดังต่อไปนี้ในbuild.gradle:

android {
    ...
    signingConfigs {
        release {
            storeFile file("my.keystore")
            storePassword "store_password"
            keyAlias "my_key_alias"
            keyPassword "key_password"
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release            
        }
    }
}

มันทำงานได้อย่างสมบูรณ์แบบ แต่ฉันต้องไม่ใส่ค่าสำหรับstorePasswordและkeyPasswordในที่เก็บของฉัน ฉันไม่ต้องการใส่storeFileและkeyAliasมีทั้ง

มีวิธีการแก้ไขbuild.gradleเพื่อที่จะได้รับรหัสผ่านจากแหล่งภายนอก (เช่นไฟล์ที่อยู่ในคอมพิวเตอร์ของฉันเท่านั้น)?

และแน่นอนการเปลี่ยนแปลงbuild.gradleควรจะสามารถใช้ได้กับคอมพิวเตอร์เครื่องอื่น ๆ (แม้ว่าคอมพิวเตอร์จะไม่สามารถเข้าถึงรหัสผ่านได้)

ฉันใช้ Android Studio และใน Mac OS X Maverics ถ้ามันสำคัญ


"และแน่นอน build.gr ที่ปรับเปลี่ยนควรจะสามารถใช้งานได้บนคอมพิวเตอร์เครื่องอื่น ๆ (แม้ว่าคอมพิวเตอร์จะไม่สามารถเข้าถึงรหัสผ่านได้)" - หากข้อมูลไม่ได้อยู่build.gradleคุณจะต้องมีสิ่งอื่นนอกเหนือจากbuild.gradleนี้หรือไม่ นั่นคือการปรับเปลี่ยนตัวแปรสภาพแวดล้อม (ต่อหนึ่งคำตอบ), ไฟล์คุณสมบัติ (ต่อคำตอบอื่น) หรือวิธีการอื่น หากคุณมีความเต็มใจที่จะมีสิ่งที่อยู่นอกbuild.gradleแล้วโดยความหมายข้อมูลทั้งหมดที่ลงนามจะต้องมีภายใน buid.gradle
CommonsWare

2
@CommonsWare คุณพูดถูก ฉันไม่ได้บอกว่าฉันต้องการมีอะไรอยู่ใน build.gradle อย่างเคร่งครัด และฉันก็บอกว่า build.gradle สามารถรับรหัสผ่านจากแหล่งภายนอก (เช่นไฟล์ที่อยู่ในคอมพิวเตอร์ของฉันเท่านั้น
Bobrovsky

มีความเป็นไปได้ที่ซ้ำกันของวิธีสร้างไฟล์ apk ที่ลงนามแล้วโดยใช้ Gradle?
2768

ฉันตั้งค่าสถานะเป็นสำเนาซ้ำของstackoverflow.com/questions/18328730/…บนพื้นฐานของmeta.stackoverflow.com/questions/311044/…
2768

คำตอบ:


120

สิ่งที่ดีเกี่ยว Groovy คือการที่คุณได้อย่างอิสระสามารถผสมรหัส Java และก็สวยง่ายต่อการอ่านในไฟล์คีย์ / java.util.Propertiesค่าใช้ อาจมีวิธีที่ง่ายยิ่งขึ้นในการใช้ idiomatic Groovy แต่ Java ก็ยังค่อนข้างเรียบง่าย

สร้างkeystore.propertiesไฟล์ (ในตัวอย่างนี้ในไดเรกทอรีรากของโครงการของคุณถัดจากsettings.gradleแม้ว่าคุณสามารถวางไว้ทุกที่ที่คุณต้องการ:

storePassword=...
keyPassword=...
keyAlias=...
storeFile=...

เพิ่มลงในของคุณbuild.gradle:

allprojects {
    afterEvaluate { project ->
        def propsFile = rootProject.file('keystore.properties')
        def configName = 'release'

        if (propsFile.exists() && android.signingConfigs.hasProperty(configName)) {
            def props = new Properties()
            props.load(new FileInputStream(propsFile))
            android.signingConfigs[configName].storeFile = file(props['storeFile'])
            android.signingConfigs[configName].storePassword = props['storePassword']
            android.signingConfigs[configName].keyAlias = props['keyAlias']
            android.signingConfigs[configName].keyPassword = props['keyPassword']
        }
    }
}

29
ฉันต้องลบเครื่องหมายคำพูดออกจาก keystore.properties ของฉัน
Jacob Tabak

6
ไม่ได้สร้างเวอร์ชันที่เซ็นชื่อให้ฉันด้วยปลั๊กอินเวอร์ชัน 0.9 ฉันควรทำอย่างไรกับไอเท็มลงนาม blockConfig และ buildTypes.release.signingConfig ลบออกไหม
Fernando Gallego

1
ดูเหมือนว่าการตั้งค่า storeFile ให้เป็นค่าใด ๆ ที่ถูกต้อง (เช่นstoreFile file('AndroidManifest.xml')) จากนั้นการแทนที่ในภายหลังจะทำให้กระบวนการลงนามเกิดขึ้น
miracle2k

5
การสร้างผลลัพธ์ในข้อผิดพลาดError:(24, 0) Could not find property 'android' on root project 'RootProjectName'ที่บรรทัด 24 คืออันที่มี if-block การเพิ่มapply plugin: 'com.android.application'ไปยัง root build.gradle ทำให้การบิลด์ล้มเหลว ผมทำอะไรผิดหรือเปล่า?
PhilLab

2
ใช้งานไม่ได้ในปี 2561 ต้องเลิกใช้ไหม เกิดข้อผิดพลาดในการขว้างปาCould not get unknown property 'android' for root project
Neon Warge

106

หรือถ้าคุณต้องการใช้คำตอบของ Scott Barta ในลักษณะที่คล้ายกับโค้ด gradle ที่สร้างขึ้นอัตโนมัติคุณสามารถสร้างkeystore.propertiesไฟล์ในโฟลเดอร์รูทโปรเจคของคุณ:

storePassword=my.keystore
keyPassword=key_password
keyAlias=my_key_alias
storeFile=store_file  

และแก้ไขโค้ด gradle ของคุณเป็น:

// Load keystore
def keystorePropertiesFile = rootProject.file("keystore.properties");
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

...

android{

    ...

    signingConfigs {
        release {
            storeFile file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
        }
    }

    ...

}

คุณสามารถเก็บไฟล์คุณสมบัตินี้ในรูทของโมดูลของคุณซึ่งในกรณีนี้จะถูกละเว้นrootProjectและคุณยังสามารถแก้ไขโค้ดนี้เพื่อให้มีคุณสมบัติหลายชุดสำหรับที่เก็บคีย์และนามแฝงของคีย์ที่แตกต่างกัน


7
ใช้งานได้ดี ฉันเคยif ( keystorePropertiesFile.exists() )ตรวจสอบให้แน่ใจว่ามีไฟล์อยู่ก่อนที่จะพยายามรับแอตทริบิวต์และลองลงชื่อ
Joshua Pinter

และอย่าลืมเพิ่ม.txtนามสกุลท้าย keystore.propertiesไฟล์
Levon Petrosyan

11
คุณไม่ควรต้องมี.txtนามสกุลkeystore.propertiesไฟล์
Matt Zukowski

2
ดูเหมือนว่าข้อมูลนี้จะถูกเพิ่มที่นี่ - developer.android.com/studio/publish/
Vadim Kotov

36

วิธีที่ง่ายที่สุดคือการสร้าง~/.gradle/gradle.propertiesไฟล์

ANDROID_STORE_PASSWORD=hunter2
ANDROID_KEY_PASSWORD=hunter2

จากนั้นbuild.gradleไฟล์ของคุณจะมีลักษณะดังนี้:

android {
    signingConfigs {
        release {
            storeFile file('yourfile.keystore')
            storePassword ANDROID_STORE_PASSWORD
            keyAlias 'youralias'
            keyPassword ANDROID_KEY_PASSWORD
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}

1
ฉันควร gitignore ~ ​​/ .gradle / gradle.properties หรือไม่
2560

คำแนะนำแบบเต็มยังอยู่ในการตอบสนองเอกสารพื้นเมือง
Pencilcheck

23

หลังจากอ่านลิงค์สองสามข้อ:

http://blog.macromates.com/2006/keychain-access-from-shell/ http://www. buttworks.com/es/insights/blog/signing-open-source-android-apps-without-disclosing- รหัสผ่าน

เนื่องจากคุณใช้ Mac OSX คุณสามารถใช้ Keychain Access เพื่อจัดเก็บรหัสผ่านของคุณ

วิธีเพิ่มรหัสผ่านใน Keychain Access

จากนั้นในสคริปต์การไล่ระดับสีของคุณ:

/* Get password from Mac OSX Keychain */
def getPassword(String currentUser, String keyChain) {
    def stdout = new ByteArrayOutputStream()
    def stderr = new ByteArrayOutputStream()
    exec {
        commandLine 'security', '-q', 'find-generic-password', '-a', currentUser, '-gl', keyChain
        standardOutput = stdout
        errorOutput = stderr
        ignoreExitValue true
    }
    //noinspection GroovyAssignabilityCheck
    (stderr.toString().trim() =~ /password: '(.*)'/)[0][1]
}

ใช้แบบนี้:

getPassword (currentUser, "Android_Store_Password")

/* Plugins */
apply plugin: 'com.android.application'

/* Variables */
ext.currentUser = System.getenv("USER")
ext.userHome = System.getProperty("user.home")
ext.keystorePath = 'KEY_STORE_PATH'

/* Signing Configs */
android {  
    signingConfigs {
        release {
            storeFile file(userHome + keystorePath + project.name)
            storePassword getPassword(currentUser, "ANDROID_STORE_PASSWORD")
            keyAlias 'jaredburrows'
            keyPassword getPassword(currentUser, "ANDROID_KEY_PASSWORD")
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}

2
แม้ว่าคำตอบของคุณจะใช้ได้กับ Mac OSX เท่านั้นฉันชอบมาก! โปรดทราบว่าลิงก์ที่สองที่คุณระบุมีโซลูชันสำรองข้อมูลสำหรับแพลตฟอร์มอื่น ๆ ในกรณีที่มีคนต้องการใช้การสนับสนุนหลายแพลตฟอร์ม
Delblanco

คุณช่วยเสนอโซลูชันเดียวกันสำหรับ linux และ windows ได้หรือไม่? ขอบคุณ
Jay Mungara

18

นี่คือวิธีที่ฉันทำ ใช้ตัวแปรสภาพแวดล้อม

  signingConfigs {
    release {
        storeFile file(System.getenv("KEYSTORE"))
        storePassword System.getenv("KEYSTORE_PASSWORD")
        keyAlias System.getenv("KEY_ALIAS")
        keyPassword System.getenv("KEY_PASSWORD")
    }

3
แต่น่าเสียดายที่นี้ต้องมีสภาพแวดล้อมระบบจะถูกสร้างขึ้นสำหรับแต่ละโครงการบนคอมพิวเตอร์แต่ละเครื่อง มิฉะนั้นฉันได้รับข้อผิดพลาดดังต่อไปนี้Neither path nor baseDir may be null or empty string. path='null'
Bobrovsky

@Bobrovsky ฉันรู้ว่ามีคำตอบสำหรับคำถามนี้ แต่คุณสามารถใช้ตัวแปรสภาพแวดล้อมของระบบหรือไฟล์ gradle.properties คุณอาจต้องการใช้ไฟล์ gradle.properties คุณสามารถใช้มันสำหรับหลายโครงการ
Jared Burrows

3
สิ่งนี้ไม่สามารถใช้งานได้บน MacOSX ยกเว้นว่าคุณเรียกใช้ Android Studio จากบรรทัดคำสั่ง
Henrique de Sousa

ฉันเห็นด้วยกับทั้งหมดข้างต้น ฉันมีการกำหนดค่าเดียวกันและคุณไม่สามารถรวบรวมได้ในสตูดิโอ Android คุณต้องเรียกใช้จากบรรทัดคำสั่งเพื่อให้ทำงานได้ ฉันกำลังมองหาวิธีที่ดีกว่าดังนั้นฉันไม่จำเป็นต้องแสดงความคิดเห็นบรรทัดเหล่านี้เมื่อฉันเรียกใช้ใน Android Studio
Sayooj Valsan

@Bobrovsky: มันทำงานบน windows ได้หรือไม่ เราควรพูดถึงสิ่งเหล่านี้ในสภาพแวดล้อมระบบหรือไม่?
DKV

12

เป็นไปได้ที่จะใช้โครงการ Android Studio gradle ที่มีอยู่และสร้าง / เซ็นชื่อจากบรรทัดคำสั่งโดยไม่ต้องแก้ไขไฟล์ใด ๆ สิ่งนี้ทำให้เป็นเรื่องที่ดีมากสำหรับการจัดเก็บโครงการของคุณในการควบคุมเวอร์ชันในขณะที่แยกคีย์และรหัสผ่านของคุณแยกต่างหากและไม่อยู่ในไฟล์ build.gradle

./gradlew assembleRelease -Pandroid.injected.signing.store.file=$KEYFILE -Pandroid.injected.signing.store.password=$STORE_PASSWORD -Pandroid.injected.signing.key.alias=$KEY_ALIAS -Pandroid.injected.signing.key.password=$KEY_PASSWORD

9

คำตอบที่ยอมรับใช้ไฟล์เพื่อควบคุมที่เก็บคีย์เพื่อใช้ลงนาม APK ที่อยู่ในโฟลเดอร์รูทเดียวกันของโครงการ เมื่อเราใช้vcsเช่นGitอาจเป็นสิ่งที่ไม่ดีเมื่อเราลืมเพิ่มไฟล์คุณสมบัติเพื่อละเว้นรายการ เพราะเราจะเปิดเผยรหัสผ่านของเราไปทั่วโลก ปัญหายังคงอยู่

แทนที่จะทำไฟล์คุณสมบัติในไดเรกทอรีเดียวกันภายในโครงการของเราเราควรทำมันนอก เราสร้างมันไว้ข้างนอกโดยใช้ไฟล์ gradle.properties

นี่คือขั้นตอน:

1. แก้ไขหรือสร้าง gradle.properties ในโปรเจ็กต์รูทของคุณและเพิ่มโค้ดต่อไปนี้อย่าลืมแก้ไขพา ธ ด้วยตัวคุณเอง:

AndroidProject.signing=/your/path/androidproject.properties  

2. สร้าง androidproject.properties ใน / your / path / และเพิ่มรหัสต่อไปนี้อย่าลืมเปลี่ยน / your/path/to/android.keystore เป็นเส้นทาง keystore ของคุณ:

STORE_FILE=/your/path/to/android.keystore  
STORE_PASSWORD=yourstorepassword  
KEY_ALIAS=yourkeyalias  
KEY_PASSWORD=yourkeypassword  

3. ในโมดูลแอป build.gradle (ไม่ใช่รูท build.gradle โครงการของคุณ) เพิ่มรหัสต่อไปนี้หากไม่มีอยู่หรือปรับเปลี่ยนเป็น:

signingConfigs {  
     release  
   }  
   buildTypes {  
   debug {  
     debuggable true  
   }  
   release {  
     minifyEnabled true  
     proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'  
     signingConfig signingConfigs.release  
   }  
 }  

4. เพิ่มรหัสต่อไปนี้ด้านล่างรหัสในขั้นตอนที่ 3:

if (project.hasProperty("AndroidProject.signing")  
     && new File(project.property("AndroidProject.signing").toString()).exists()) {  
     def Properties props = new Properties()  
     def propFile = new File(project.property("AndroidProject.signing").toString())  
     if(propFile.canRead()) {  
      props.load(new FileInputStream(propFile))  
      if (props!=null && props.containsKey('STORE_FILE') && props.containsKey('STORE_PASSWORD') &&  
         props.containsKey('KEY_ALIAS') && props.containsKey('KEY_PASSWORD')) {  
         android.signingConfigs.release.storeFile = file(props['STORE_FILE'])  
         android.signingConfigs.release.storePassword = props['STORE_PASSWORD']  
         android.signingConfigs.release.keyAlias = props['KEY_ALIAS']  
         android.signingConfigs.release.keyPassword = props['KEY_PASSWORD']  
      } else {  
         println 'androidproject.properties found but some entries are missing'  
         android.buildTypes.release.signingConfig = null  
      }  
     } else {  
            println 'androidproject.properties file not found'  
          android.buildTypes.release.signingConfig = null  
     }  
   }  

รหัสนี้จะค้นหา AndroidProject.signing คุณสมบัติในgradle.propertiesจากขั้นตอนที่ 1 ถ้าคุณสมบัติที่พบก็จะแปลมูลค่าทรัพย์สินเป็นเส้นทางของไฟล์ที่ชี้ไป androidproject.properties ที่เราสร้างในขั้นตอนที่ 2 จากนั้นค่าคุณสมบัติทั้งหมดจากมันจะถูกใช้เป็นการกำหนดค่าการลงนามสำหรับ build.gradle ของเรา

ตอนนี้เราไม่ต้องกังวลอีกครั้งถึงความเสี่ยงที่จะเปิดเผยรหัสผ่านที่เก็บคีย์ของเรา

อ่านเพิ่มเติมที่Signing Android apk โดยไม่ต้องใส่ข้อมูล keystore ใน build.gradle


มันใช้งานได้ดีสำหรับฉัน เพิ่งรู้ว่าทำไมพวกเขาถึงใช้ไฟล์ storeFile (System.getenv ("KEYSTORE"))
DKV

9

สำหรับคนที่ต้องการใส่ข้อมูลประจำตัวของพวกเขาในไฟล์ JSONภายนอกและอ่านสิ่งนั้นจากระดับเสียงนี่คือสิ่งที่ฉันทำ:

my_project / credentials.json:

{
    "android": {
        "storeFile": "/path/to/acuity.jks",
        "storePassword": "your_store_password",
        "keyAlias": "your_android_alias",
        "keyPassword": "your_key_password"
    }
}

my_project / Android / แอป / build.gradle

// ...
signingConfigs {
        release {

            def credsFilePath = file("../../credentials.json").toString()
            def credsFile = new File(credsFilePath, "").getText('UTF-8')
            def json = new groovy.json.JsonSlurper().parseText(credsFile)
            storeFile file(json.android.storeFile)
            storePassword = json.android.storePassword
            keyAlias = json.android.keyAlias
            keyPassword = json.android.keyPassword
        }
        ...
        buildTypes {
            release {
                signingConfig signingConfigs.release //I added this
                // ...
            }
        }
    }
// ...
}

เหตุผลที่ฉันเลือก.jsonประเภทไฟล์ไม่ใช่.propertiesประเภทไฟล์ (ตามคำตอบที่ยอมรับ) ก็เพราะฉันต้องการเก็บข้อมูลอื่น ๆ (คุณสมบัติที่กำหนดเองอื่น ๆ ที่ฉันต้องการ) ไปยังไฟล์เดียวกัน ( my_project/credentials.json) และยังมีการแยกวิเคราะห์คำ ข้อมูลการลงชื่อจากภายในไฟล์นั้นเช่นกัน


ดูเหมือนว่าทางออกที่ดีที่สุดสำหรับฉัน
Dev

4

คำถามนี้ได้รับการตอบที่ถูกต้องมาก แต่ผมอยากจะแบ่งปันรหัสของฉันซึ่งอาจจะเป็นประโยชน์สำหรับผู้ดูแลห้องสมุดเพราะมันใบเดิมbuild.gradleค่อนข้างสะอาด

gitignoreฉันจะเพิ่มโฟลเดอร์ไปยังไดเรกทอรีโมดูลซึ่งผม ดูเหมือนว่านี้:

/signing
    /keystore.jks
    /signing.gradle
    /signing.properties

keystore.jksและsigning.propertiesควรอธิบายตนเอง และsigning.gradleมีลักษณะเช่นนี้:

def propsFile = file('signing/signing.properties')
def buildType = "release"

if (!propsFile.exists()) throw new IllegalStateException("signing/signing.properties file missing")

def props = new Properties()
props.load(new FileInputStream(propsFile))

def keystoreFile = file("signing/keystore.jks")
if (!keystoreFile.exists()) throw new IllegalStateException("signing/keystore.jks file missing")

android.signingConfigs.create(buildType, {
    storeFile = keystoreFile
    storePassword = props['storePassword']
    keyAlias = props['keyAlias']
    keyPassword = props['keyPassword']
})

android.buildTypes[buildType].signingConfig = android.signingConfigs[buildType]

และแบบดั้งเดิม build.gradle

apply plugin: 'com.android.application'
if (project.file('signing/signing.gradle').exists()) {
    apply from: 'signing/signing.gradle'
}

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId ...
    }
}

dependencies {
    implementation ...
}

อย่างที่คุณเห็นคุณไม่จำเป็นต้องระบุ buildTypes เลยหากผู้ใช้มีสิทธิ์เข้าถึงsigningไดเรกทอรีที่ถูกต้องเขาเพิ่งวางไว้ในโมดูลและเขาสามารถสร้างแอปพลิเคชั่นที่ได้รับการลงนามที่ถูกต้องไม่เช่นนั้น มันจะทำตามปกติ


ฉันชอบวิธีนี้มาก อย่างไรก็ตามapply fromควรมาหลังandroidบล็อก
mgray88

0

คุณสามารถขอรหัสผ่านจากบรรทัดคำสั่ง:

...

signingConfigs {
  if (gradle.startParameter.taskNames.any {it.contains('Release') }) {
    release {
      storeFile file("your.keystore")
      storePassword new String(System.console().readPassword("\n\$ Enter keystore password: "))
      keyAlias "key-alias"
      keyPassword new String(System.console().readPassword("\n\$ Enter keys password: "))
    } 
  } else {
    //Here be dragons: unreachable else-branch forces Gradle to create
    //install...Release tasks.
    release {
      keyAlias 'dummy'
      keyPassword 'dummy'
      storeFile file('dummy')
      storePassword 'dummy'
    } 
  }
}

...

buildTypes {
  release {

    ...

    signingConfig signingConfigs.release
  }

  ...
}

...

คำตอบนี้ปรากฏก่อนหน้านี้: https://stackoverflow.com/a/33765572/3664487


แม้ว่าลิงก์นี้อาจตอบคำถามได้ดีกว่าหากรวมส่วนสำคัญของคำตอบไว้ที่นี่และให้ลิงก์สำหรับการอ้างอิง คำตอบสำหรับลิงก์เท่านั้นอาจไม่ถูกต้องหากหน้าเว็บที่เชื่อมโยงมีการเปลี่ยนแปลง - จากรีวิว
mkobit

1
@mkobit นี่คือลิงค์ไปยังเนื้อหาใน Stack Overflow! แน่นอนว่าฉันสามารถคัดลอกและวางเนื้อหาที่เชื่อมโยง แต่นำไปสู่เนื้อหาที่ซ้ำกัน ดังนั้นฉันคิดและแก้ไขให้ถูกต้องหากฉันผิดการโพสต์ลิงค์เป็นทางออกที่ดีที่สุด อาร์กิวเมนต์ใด ๆ ที่ "การเชื่อมโยงไปยังหน้าการเปลี่ยนแปลง" ควรถูกยกเลิกบนพื้นฐานของเนื้อหาที่นี่สามารถเปลี่ยนแปลงได้เช่นกัน ฉันขอแนะนำอย่างมากกับโซลูชันของคุณเพื่อลบ! เพราะเนื้อหาที่เชื่อมโยงนำเสนอทางออกที่ดีเยี่ยม
2768

ฉันคิดว่าปัญหาคือว่านี่ยังเป็นคำตอบ "ลิงก์อย่างเดียว" ฉันคิดว่าวิธีแก้ปัญหาคือการโพสต์เป็นความคิดเห็นตั้งคำถามเป็นซ้ำหรือเขียนคำตอบใหม่ที่นี่เพื่อจัดการกับปัญหา
mkobit

ควรสนับสนุนคำตอบ "ลิงก์อย่างเดียว" ในบางกรณี อย่างไรก็ตามฉันได้ทำตามคำแนะนำของคุณและเนื้อหาที่ซ้ำกัน (เนื้อหาที่ซ้ำกันมีปัญหาชัดเจนเนื่องจากเนื้อหาบางส่วนอาจได้รับการอัปเดตในขณะที่เนื้อหาที่เหลืออาจไม่เป็นเช่นนั้น)
2768

แน่นอนฉันเพิ่งปรับปรุงคำตอบและเนื้อหาที่ซ้ำกันทำให้เกิดปัญหา! หากมีนโยบายต่อต้านคำตอบสำหรับลิงก์เท่านั้นควรปรับเปลี่ยนเพื่อพิจารณากรณีมุมดังกล่าว
2768

0

รหัสผ่านของฉันมีอักขระพิเศษที่ดอลลาร์ลงชื่อ$และฉันต้องหลีกเลี่ยงสิ่งนั้นในไฟล์ gradle.properties หลังจากนั้นการเซ็นชื่อใช้งานได้สำหรับฉัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.