Gradle配置构建多Module项目

Gradle配置构建多Module项目

0x01 配置远程代码库

可以按如下方式声明特定的 Maven 或 Ivy 代码库:

1
2
3
4
5
6
7
8
allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/public' } // public仓是包含central仓和jcenter仓的聚合仓
maven { url 'https://maven.aliyun.com/repository/google' } // 阿里镜像库
maven { url "file://local/repo/" } // 本地文件代码库
ivy { url "https://repo.example.com/ivy" } // Ivy代码库
}
}

0x02 统一配置Gradle依赖库版本

随着项目采用模块化,组件化开发,moudle 的个数也会随着增加,统一管理配置gradle就显得比较重要了。

1、在 project 根目录创建一个 config.gradle 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ext {  
// app 相关版本控制
versions = [
compileVersion : 26,
buildVersion : "26.0.2",

sdkMinVersion : 15,
sdkTargetVersion : 26,
appVersionCode : 520,
appVersionName : "1.0.0"
]
// support依赖
support = [
appcompat : "com.android.support:appcompat-v7:26.+",
recyclerview: "com.android.support:recyclerview-v7:26.+"
]
// 依赖
deps = [
glide : "com.github.bumptech.glide:glide:4.11.0"
]
}

2、在 Project 根目录下的 build.gradle 添加apply

1
apply from: 'config.gradle' 

3、在相应Moudle中调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
android {  
def versions = rootProject.ext.versions
compileSdkVersion versions.compileVersion
buildToolsVersion versions.buildVersion
defaultConfig {
applicationId "com.dench.wanandroid"
minSdkVersion versions.sdkMinVersion
targetSdkVersion versions.sdkTargetVersion
versionCode versions.appVersionCode
versionName versions.appVersionName
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
def dependencies = rootProject.ext.deps
def support = rootProject.ext.support

implementation support.appcompat
implementation support.recyclerview
implementation dependencies.glide
}

0x03 配置Flavor

创建产品变种与创建构建类型类似:将其添加到构建配置中的 productFlavors 代码块并添加所需的设置。产品变种支持与 defaultConfig 相同的属性,这是因为,defaultConfig 实际上属于 ProductFlavor 类。这意味着,您可以在 defaultConfig 代码块中提供所有变种的基本配置,每个变种均可更改其中任何默认值,如 applicationId

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
android {
defaultConfig {...}
buildTypes {
debug{...}
release{...}
}
// Specifies one flavor dimension.
flavorDimensions "version"
productFlavors {
demo {
dimension "version"
applicationIdSuffix ".demo"
versionNameSuffix "-demo"
versionCode 30000 + android.defaultConfig.versionCode
}
full {
dimension "version"
applicationIdSuffix ".full"
versionNameSuffix "-full"
versionCode 20000 + android.defaultConfig.versionCode
}
}
}

0x04 创建源代码集

1、Gradle 要求:

在所有构建变体之间共享的所有内容创建 main/ 源代码集和目录。

将“debug”构建类型特有的 Java 类文件放在 src/debug/java/ 目录中。

1
2
3
4
5
6
7
8
9
10
11
12
13
debug
----
Compile configuration: compile
build.gradle name: android.sourceSets.debug
Java sources: [app/src/debug/java]
Manifest file: app/src/debug/AndroidManifest.xml
Android resources: [app/src/debug/res]
Assets: [app/src/debug/assets]
AIDL sources: [app/src/debug/aidl]
RenderScript sources: [app/src/debug/rs]
JNI sources: [app/src/debug/jni]
JNI libraries: [app/src/debug/jniLibs]
Java-style resources: [app/src/debug/resources]

依次转到 MyApplication > Tasks > android,然后双击 sourceSets。Gradle 执行该任务后,系统应该会打开 Run 窗口以显示输出。

2、更改默认源代码集配置

1
2
3
4
5
6
7
8
9
android {
sourceSets {
main {
java.srcDirs = ['other/java']
res.srcDirs = ['other/res1', 'other/res2']
manifest.srcFile 'other/AndroidManifest.xml'
}
}
}

0x05 声明依赖项

1
2
3
4
5
6
dependencies {
// Adds the local "mylibrary" module as a dependency to the "free" flavor.
freeImplementation project(":mylibrary")
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

0x06 配置签名

1、在项目的根目录下创建一个名为 keystore.properties 的文件,并使其包含以下信息:

1
2
3
4
storePassword=myStorePassword
keyPassword=myKeyPassword
keyAlias=myKeyAlias
storeFile=myStoreFileLocation

2、在 build.gradle 文件中,按如下方式加载 keystore.properties 文件(必须在 android 代码块前面):

1
2
3
4
5
6
7
8

def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
// before android
android {

}

3、输入存储在 keystoreProperties 对象中的签名信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
android {
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}

如需从环境变量获取这些密码,请添加以下代码:

1
2
storePassword System.getenv("KSTOREPWD")
keyPassword System.getenv("KEYPWD")

0x07 apk重命名

1
2
3
4
5
6
7
8
9
android {
applicationVariants.all { variant ->
if (variant.buildType.name == 'release') {
variant.outputs.all {
outputFileName = "app_v${variant.versionName}.${buildTime}_${variant.productFlavors[0].name}_${variant.buildType.name}.apk"
}
}
}
}

or

1
2
3
outputFileName = "app_v${versionName}.${buildTime}_${flavorName}_${buildType.name}.apk"
outputFileName = "../../${outputFileName}"
println outputFileName

0x08 将构建变量注入清单

1、如果您需要将变量插入在 build.gradle 文件中定义的 AndroidManifest.xml 文件,可以使用 manifestPlaceholders 属性执行此操作。此属性采用键值对的映射,如下所示:

1
2
3
4
5
6
android {
defaultConfig {
manifestPlaceholders = [hostName:"www.example.com"]
applicationId "com.example.myapp"
}
}

2、您可以将某个占位符作为属性值插入清单文件,如下所示:

1
2
3
4
<intent-filter ... >
<data android:scheme="http" android:host="${hostName}" ... />
<action android:name="${applicationId}.TRANSMOGRIFY" />
</intent-filter>

0x09 gradle自定义Java变量和资源值

在构建时,Gradle 将生成 BuildConfig 类,以便应用代码可以检查与当前构建有关的信息。您也可以从 Gradle 构建配置文件中使用 buildConfigField() 方法将自定义字段添加到 BuildConfig 类中,然后在应用的运行时代码中访问这些值。同样,您也可以使用 resValue() 添加应用资源值。

1
2
3
4
5
6
7
8
9
10
11
12
13
def buildTime = new Data().format("yyyyMMddHHmm", TimeZone.getTimeZone("GTM+08:00"))
android {
buildTypes {
release {
buildConfigField("String", "BUILD_TIME", "\"${buildTime}\"")
resValue("string", "build_time", "${buildTime}")
}
debug {
buildConfigField("String", "BUILD_TIME", "\"0\"")
resValue("string", "build_time", "0")
}
}
}

在应用代码中,您可以按如下方式访问属性:

1
2
Log.i(TAG, BuildConfig.BUILD_TIME);
Log.i(TAG, getString(R.string.build_time));

0x10 设定编码

1
2
3
4
5
allprojects {
tasks.withType(JavaCompile){
options.encoding = "UTF-8"
}
}

DataBinding踩坑指南

0x01 ViewBinding

1.使用 View Binding 先要在Module 的 build.gradle 文件注册

1
2
3
4
5
6
android {
...
buildFeatures {
viewBinding true
}
}

2.会根据布局文件,编译之后自动生成对应的Binding class,可以在Activity 和 Fragment 直接调用 inflate 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

0x02 DataBinding

1.在 build.gradle 文件中开启lib

1
2
3
4
5
6
android {
...
buildFeatures {
dataBinding true
}
}

2.布局文件start with a root tag of layout followed by a data element

1
2
3
4
5
6
7
8
9
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewmodel"
type="com.myapp.data.ViewModel" />
</data>
<ConstraintLayout... /> <!-- UI layout's root element -->
</layout>

3.在Activity中使用

1
2
3
4
5
6
7
8
9
10
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val binding: ActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater())
// or
val binding: ActivityMainBinding = DataBindingUtil.setContentView(
this, R.layout.activity_main)

binding.user = User("Test", "User")
}

Fragment, ListView, or RecyclerView adapter, you may prefer to use the inflate()

1
2
3
val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)

0x03 view binding 和 data binding 比较

View binding and data binding both generate binding classes that you can use to reference views directly. However, view binding is intended to handle simpler use cases and provides the following benefits over data binding:

  • Faster compilation: View binding requires no annotation processing, so compile times are faster.
  • Ease of use: View binding does not require specially-tagged XML layout files, so it is faster to adopt in your apps. Once you enable view binding in a module, it applies to all of that module’s layouts automatically.

Conversely, view binding has the following limitations compared to data binding:

Because of these considerations, it is best in some cases to use both view binding and data binding in a project. You can use data binding in layouts that require advanced features and use view binding in layouts that do not.

0x04 遇到的坑

  • 等标签,如果使用databinding ,子布局xml的 root tag 依旧需要layout 标签嵌套 data 标签。否者编译报错,找不到对应的属性

自定义Notification遇到的坑

自定义Notification 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// RemoteViews for notification
private var rv: RemoteViews? = null
private var rvExpanded: RemoteViews? = null
private fun customNotification(process: Int) {
// custom RemoteViews
if (rv == null) rv = RemoteViews(packageName, R.layout.notification_small)
rv?.setTextViewText(R.id.notification_title, "这是一个小标题")
if (rvExpanded == null) rvExpanded = RemoteViews(packageName, R.layout.notification_large)
rvExpanded?.setTextViewText(R.id.large_notification_title, "这是一个大标题,支持很多的内容: $process%")

// PendingIntent
val intentNotification = Intent(this, PlayMusicActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
}
val pendIntent = PendingIntent.getActivity(
this,
0,
intentNotification,
PendingIntent.FLAG_UPDATE_CURRENT
)

// build notification
val customNotification =
NotificationCompat.Builder(applicationContext, CHANNEL_ID)
.setSmallIcon(R.drawable.bravo) // small Icon
.setStyle(NotificationCompat.DecoratedCustomViewStyle()) // 自定义contentView
.setCustomContentView(rv!!)
.setContent(rvExpanded!!)
.setCustomBigContentView(rvExpanded!!)
.setCustomHeadsUpContentView(rvExpanded!!)
.setOngoing(true) // 一直显示
.setAutoCancel(false) // 点击后消失
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) // 锁屏显示,需要配合权限设置
.setPriority(NotificationCompat.PRIORITY_HIGH) // Priority
.setOnlyAlertOnce(true) // 声音,震动,仅弹出一次
.setContentIntent(pendIntent)
.build()
startForeground(NOTIFICATION_ID, customNotification)
}

1.使用 NotificationCompat 兼容各个版本差异性

2.RemoteViews 布局文件不支持 constraintlayout ,切记

3.在SDK 26之后必须要绑定Channel,所以通知要先创建Channel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
fun checkAndCreateChannel(
context: Context,
channelId: String,
channelName: String,
desc: String = channelName
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
try {
// 查找分组
val nc = notificationManager.getNotificationChannel(channelId)
Log.d("ChannelHelper", "${nc.id} Notification Channel exist.")
} catch (e: Exception) {
Log.d("ChannelHelper", "empty channel need create.")
// 创建分组
val mChannel =
NotificationChannel(
channelId,
channelName,
NotificationManager.IMPORTANCE_HIGH
).apply {
description = desc
enableLights(true)
enableVibration(true)
}
notificationManager.createNotificationChannel(mChannel)
}
}
}

Android-JsBridge实现本地H5混合开发

Android JsBridge 混合开发框架

0x01 Java 调用 Js

我们知道,native层调用h5,在WebView中,如果java要调用js的方法,

0x0101 loadUrl <4.4

Android4.4以前使用WebView.loadUrl("javascript:function()")

0x0102 evaluateJavascript >4.4

Android4.4以后,使用以下方式

1
2
3
4
5
6
webView.evaluateJavascript("javascript:function()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Toast.makeText(MainActivity.this, "onReceiveValue From JS: " + value, Toast.LENGTH_SHORT).show();
}
});

0x02 Js 调用 Java

h5层如何调native,有以下几种形式

0x0201 addJavascriptInterface >4.2

在4.2之前有安全隐患,JS可以动态获取到整个底层的实例信息,漏洞已经在Android 4.2上修复了,即使用@JavascriptInterface注解。

0x0202 shouldOverrideUrlLoading拦截自定义scheme

0x0203 onJsAlert,onJsConfirm,onJsPrompt

WebChromeClient对象中有三个方法,分别是onJsAlert,onJsConfirm,onJsPrompt,当js调用window对象的alert,confirm,prompt,WebChromeClient对象中的三个方法对应的就会被触发,进行拦截处理。

推荐使用onJsPrompt,使用频次最少,支持返回值。

1
2
3
4
5
6
7
8
9
10
11
override fun onJsPrompt(
view: WebView?,
url: String?,
message: String?,
defaultValue: String?,
result: JsPromptResult?
): Boolean {
val msg = handleMessage(message)
result?.confirm("Java 处理之后的 Json 数据:$msg")
return true
}

0x0204 onConsoleMessage

这是Android提供给Js调试在Native代码里面打印日志信息的API,同时这也成了其中一种Js与Native代码通信的方法。在Js代码中调用console.log(‘xxx’)方法。

0x03 自定义通信协议

jsbridge://className:callbackId/methodName?json

假设我们需要调用native层的Logger类的log方法,参数是msg,执行完成后js层要有一个回调,那么地址就如下

jsbridge://Logger:callbackId/log?{"msg":"message from js."}

RecyclerView实现瀑布流以及细节问题

RecyclerView实现瀑布流以及细节问题

1)使用 StaggeredGridLayoutManager

1
2
3
4
5
6
7
8
9
10
val layoutManager = StaggeredGridLayoutManager(2, RecyclerView.VERTICAL)
layoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_NONE// 禁止左右交换
recyclerView.layoutManager = layoutManager
// decoration
recyclerView.addItemDecoration(StaggeredDividerItemDecoration(16, true))
// adapter
mAdapter = PjListVPAdapter()
recyclerView.adapter = mAdapter
// animator
recyclerView.itemAnimator = DefaultItemAnimator()

2)如果需要Item之间的间隔,就需要自定义 ItemDecoration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 瀑布流ItemDecoration
* 必须配合RecyclerView的StaggeredGridLayoutManager一起使用
*
* @author Dench
* @data 2020-09-04
*/
class StaggeredDividerItemDecoration(
space: Int, // 间隔 pix
private val includeEdge: Boolean = false // 是否显示边距
) : ItemDecoration() {
private val space = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
space.toFloat(),
Resources.getSystem().displayMetrics
).toInt()

override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val lp = view.layoutParams as StaggeredGridLayoutManager.LayoutParams
val lm = parent.layoutManager as StaggeredGridLayoutManager
val spanIndex: Int = lp.spanIndex
val spanCount: Int = lm.spanCount
if (includeEdge) {
outRect.left = space * (spanCount - spanIndex) / spanCount
outRect.right = space * (spanIndex + 1) / spanCount
outRect.top = space
} else {
outRect.left = space * spanIndex / spanCount
outRect.right = space * (spanCount - spanIndex - 1) / spanCount
outRect.bottom = space
}
}
}
阅读更多

ARouter源码解析

ARouter源码解析

0x01 init ()

ARouter 的入口,初始化SDK ARouter.init(mApplication);

1
2
3
4
5
6
7
8
9
10
11
public static void init(Application application) {
if (!hasInit) { // 避免重复初始化
logger = _ARouter.logger; // 日志初始化
_ARouter.logger.info(Consts.TAG, "ARouter init start.");
hasInit = _ARouter.init(application); // 正式初始化
if (hasInit) { // 初始化之后
_ARouter.afterInit(); // 管理拦截器的服务 InterceptorService 初始化
}
_ARouter.logger.info(Consts.TAG, "ARouter init over.");
}
}

_ARouter.init()

阅读更多

implementation compileOnly和api

implementation和api

implementationapi是取代之前的compile的,其中apicompile是一样的效果,implementation有所不同,通过implementation依赖的库只能自己库本身访问,举个例子,A依赖B,B依赖C,如果B依赖C是使用的implementation依赖,那么在A中是访问不到C中的方法的,如果需要访问,请使用api依赖

compile only

compile onlyprovided效果是一样的,只在编译的时候有效, 不参与打包

runtime only

runtimeOnlyapk效果一样,只在打包的时候有效,编译不参与

跟编译环境相关

  • test implementation

testImplementationtestCompile效果一样,在单元测试和打包测试apk的时候有效

  • debug implementation

debugImplementationdebugCompile效果相同, 在debug模式下有效

  • release implementation

releaseImplementationreleaseCompile效果相同,只在release模式和打包release包情况下有效

StatusBarHelper

StatusBarHelper

StatusBar 工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
object StatusBarHelper {
/**
* 内容显示在状态栏下面(LAYOUT_FULLSCREEN) > 6.0
* true:白底黑字,false:黑底白字
*/
fun fitSystemBar(activity: Activity, light: Boolean = true) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return
val window = activity.window
val decorView = window.decorView
var visibility = decorView.systemUiVisibility
visibility = visibility or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
visibility = if (light) {
visibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
} else {
visibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
}
decorView.systemUiVisibility = visibility

window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
window.statusBarColor = Color.TRANSPARENT
}

/**
* 调整状态栏文字、图标颜色 > 6.0
* true:白底黑字,false:黑底白字
*/
fun lightStatusBar(activity: Activity, light: Boolean = true) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return
var window: Window = activity.window
var visibility = window.decorView.systemUiVisibility
visibility = if (light) {
visibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
} else {
visibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
}
window.decorView.systemUiVisibility = visibility
}


// 获取状态栏高度
fun getStatusBarHeight(activity: Activity): Int {
var result: Int = 0
var resId = activity.resources.getIdentifier("status_bar_height", "dimen", "android")
if (resId > 0) result = activity.resources.getDimensionPixelOffset(resId)
return result
}
}
阅读更多

Gradle常用命令

Gradle 常用命令

0x01 快速构建命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 查看构建版本
./gradlew -v

# 清除build文件夹
./gradlew clean

# 检查依赖并编译打包
./gradlew build

# 编译并安装debug包
./gradlew installDebug

# 编译并打印日志
./gradlew build --info

# 译并输出性能报告,性能报告一般在 构建工程根目录 build/reports/profile
./gradlew build --profile

# 调试模式构建并打印堆栈日志
./gradlew build --info --debug --stacktrace

# 离线模式
./gradlew aDR --offline

# 守护进程
./gradlew build --daemon

# 并行编译模式
./gradlew build --parallel --parallel-threads=N

0x02 构建并安装命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 编译并打Debug包
./gradlew assembleDebug

# 这个是简写 assembleDebug
./gradlew aD

# 编译并打Release的包
./gradlew assembleRelease

# 这个是简写 assembleRelease
./gradlew aR

# Debug模式打包并安装
./gradlew install app:assembleDebug

# Release模式打包并安装
./gradlew installRelease

# 卸载Release模式包
./gradlew uninstallRelease

# Flavor渠道包
./gradlew install app:assemble<Flavor>Debug

0x03 查看包依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
./gradlew dependencies

# 查看app模块依赖
./gradlew app:dependencies

# 检索依赖库
./gradlew app:dependencies | grep CompileClasspath

# windows 没有 grep 命令
./gradlew app:dependencies | findstr "CompileClasspath"

# 将检索到的依赖分组找到 比如flavorDebugCompileClasspath就是flavor渠道分发的开发编译依赖
./gradlew app:dependencies --configuration <flavor>DebugCompileClasspath

# 依赖树过长可以保存到本地文件方便查看
./gradlew app:dependencies --configuration <flavor>DebugCompileClasspath >1.log

0x04 依赖包更新

1
2
3
4
5
# 依赖包更新命令
./gradlew build --refresh-dependencies

# 强制更新最新依赖,清除构建并构建
./gradlew clean build --refresh-dependencies

阿里云 Maven 仓库

0x01 阿里云Maven仓库

仓库地址:https://maven.aliyun.com/mvn/view

1
2
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://maven.aliyun.com/repository/google' }

0x02 Gradle配置指南

在 build.gradle 文件中加入以下代码:

阅读更多

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×