Android从现有的项目创建一个皮应用

Android从现有的项目创建一个皮应用

0x01 Copy一个新项目

  1. clean原项目,然后直接Copy原项目所有文件,等待完成
  2. 根据新项目重命名新文件夹名称
  3. 删除原项目的 .idea .git .gitlab 等文件夹,.gradle 可以不删
  4. 用编辑器打开 settings.gradle,修改项目名称 rootProject.name = "xxx"

0x02 重命名项目包名

这一步是要将包名从 com.sample1.android 重命名为 com.sample2.android

  1. 用AndroidStudio打开新项目,去掉 Compat Middle Packages 前面的勾
  2. 选择项目的 sample1 包名,Shift + F6重命名
  3. 一定选择 Rename Package
  4. 等待完成,项目大时间就比较长
  5. 修改 .aidl 文件的包名路径和import的路径
  6. 修改根目录下的 build.gradleapplicationId "com.sample2.android",并 sync

ps: 不清楚为啥Kotlin的扩展函数还需要重新手动导入

0x03 推到代码库

到上面这一步,不出意外的话已经可以编译成功并且跑起来了。编译成功之后可以先推送本地项目到代码库。

远程代码库新建项目,获取新项目git地址,然后执行下面操作。

1
2
3
4
5
6
$ cd existing_folder
$ git init
$ git remote add origin git://github.com/schacon/grit.git
$ git add .
$ git commit
$ git push -u origin master

0x04 后续

下面就是修改名称,URL,替换功能啥的。

  • Logo、名称、资源文件替换
  • H5协议替换
  • 域名修改
  • 第三方账号切换

0x05 审核相关

现在应用市场对新应用的审核非常严格。为了过审,可能需要做很多相关的工作

  • 混淆,或者改文件名
  • 资源混淆,或者资源改名
  • 增加新功能
  • 修改UI界面样式
  • 做审核版功能

0x06 问题

0x0601 如果Shift + F6重命名时没有Rename Package选项

如果Shift + F6重命名时没有Rename Package选项, 并且出现了以下提示,
Package 'example' contains directories in libraries which cannot be renamed. Do you want to rename current directory or all directories in project?
这是因为依赖包中也存在example这个包名,导致无法直接重命名包名。解决的方案分两种:

方案1. 暂时先移除包含example包名的依赖包,等重命名之后重新添加到项目中
方案2. 只能新建package,然后将要rename的包拖动到新的package中,或者F6移动

PS:因为无法重命名,刚刚经历了手动移动十几个module的package包路径的痛苦历程。

RoundImageView圆角控件

RoundImageView圆角控件

示例代码如下:

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
51
52
53
54
55
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;

import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;

public class RoundImageView extends AppCompatImageView {
private static final String TAG = "RoundImageView";

private int radius = 0;

public RoundImageView(Context context) {
this(context, null);
}

public RoundImageView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}

public RoundImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setup(context, attrs, defStyleAttr);
}

private void setup(Context context, AttributeSet attrs, int defStyleAttr) {
try {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RoundImageView);
radius = a.getDimensionPixelSize(R.styleable.RoundImageView_riv_radius, 0);
Log.d(TAG, "RoundImageView: radius=" + radius);
a.recycle();
} catch (Exception e) {
e.printStackTrace();
}
}

public void setRadius(int radius) {
this.radius = radius;
}

@Override
protected void onDraw(Canvas canvas) {
if (radius > 0) {
Path path = new Path();
path.addRoundRect(new RectF(0, 0, getWidth(), getHeight()), radius, radius, Path.Direction.CW);
canvas.clipPath(path);//设置可显示的区域,canvas四个角会被剪裁掉
}
super.onDraw(canvas);
}
}

attrs.xml 文件中定义控件的圆角dp值属性:

1
2
3
<declare-styleable name="RoundImageView">
<attr name="riv_radius" format="dimension" />
</declare-styleable>

使用示例

1
2
3
4
5
6
<com.xx.ui.widget.RoundImageView
android:id="@+id/image_view"
android:layout_width="120dp"
android:layout_height="60dp"
android:scaleType="centerCrop"
app:riv_radius="8dp" />

Gradle的环境配置

Gradle的环境配置

原文地址:https://www.cnblogs.com/baiqiantao/p/6890674.html

Installing Gradle: https://docs.gradle.org/current/userguide/installation.html

  • gradlew 和 gradlew.bat:封装 gradle 的脚本,目的是为了更方便的使用 gradle
  • 环境变量 GRADLE_HOME:仅仅是为了可以在任意目录中执行 gradle 命令,没有特殊的意义
  • 环境变量 GRADLE_USER_HOME:控制在命令行中执行 gradlew 命令时,gradle 下载的目录
  • IDEA 的 Gradle user home:控制在 IDEA 点击按钮执行各项 Task 等功能时,gradle 下载的目录
  • IDEA 的 User from gradle:控制在 IDEA 点击按钮执行各项 Task 等功能时,使用的 gradle 的版本

环境变量 GRADLE_HOME

设置环境变量 GRADLE_HOME 的目的,仅仅是为了方便在 Path 中指定 gradle 的位置。
GRADLE_HOME:D:_dev\gradle_GRADLE_HOME\gradle-6.7
Path:%GRADLE_HOME%\bin
将 gradle 添加到 Path 的目的是为了,可以在任意目录中执行 gradle 命令。
实际上,完全没必要设置环境变量 GRADLE_HOME,Do we really need GRADLE_HOME?

1
2
3
4
5
6
gradle -v           # 查看版本
gradle --help # 查看命令使用帮助

λ where gradle # 查看 gradle 命令位置
D:\_dev\gradle\GRADLE_HOME\gradle-6.7\bin\gradle
D:\_dev\gradle\GRADLE_HOME\gradle-6.7\bin\gradle.bat

gradlew 是干嘛的

其实 gradlew 只是一个 gradle 的封装(wrapper),gradlew = gradle wrapper,因为在项目根目录有 gradlewgradlew.bat 这两个可执行文件,所以 能且仅能 在项目根目录中执行 gradlew 命令。

1
2
3
4
5
6
D:\_dev\_code\as\Test> gradlew -v       # 查看版本
D:\_dev\_code\as\Test> gradlew --help # 查看命令使用帮助

D:\_dev\_code\as\Test> where gradlew # 查看 gradlew 命令位置
D:\_dev\_code\as\Test\gradlew
D:\_dev\_code\as\Test\gradlew.bat

这两个文件头部的注释也说明了他们的作用:

  • gradlew:Gradle start up script for UN*X
  • gradlew.bat:Gradle startup script for Windows

之所以添加这个 gradlew 脚本,是为了:

统一项目所使用的 gradle 版本,避免不同开发人员使用不同的 gradle 版本导致的兼容性问题
可以把 gradle-wrapper.properties 里面的下载 gradle 的地址切换到公司的公共空间上,以加快下载速度

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

环境变量 GRADLE_USER_HOME

设置环境变量 GRADLE_USER_HOME 的目的,是为了自定义下载 gradle 时的本地存储路径。
在命令行中执行 gradlew 命令(注意不是 gradle 命令)时,会将对应版本的 gradle 下载到此目录中。
下载 gradle 时,下载地址及版本由项目中的 /gradle/wrapper/gradle-wrapper.properties 决定。

1
2
3
4
5
6
#Mon Nov 16 00:55:48 CST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip

IDEA 的 Gradle user home

在 IDEA 的 File | Settings | Build, Execution, Deployment | Build Tools | Gradle 中,有一个 Gradle user home 的配置,其作用和环境变量 GRADLE_USER_HOME 类似,只不过该配置只是给 IDEA 使用的。譬如点击 gradle 窗口的各种 Task 按钮执行各项 Task 功能时。

注意:仅 IDEA 中的各种图形化操作会使用此配置,在 IDEA 的 Terminal 中执行 gradlew 命令时,使用的依旧是环境变量 GRADLE_USER_HOME

IDEA 的 User from gradle

在 IDEA 的 File | Settings | Build, Execution, Deployment | Build Tools | Gradle 中,有一个 User from gradle 的配置,它也是仅提供给 IDEA 使用的(对 gradlew 无效)。

其作用是,指定当前工程中 IDEA 所使用的 gradle 版本:

当勾选 gradle-wrapper.properties 时,使用 gradle-wrapper.properties 中指定的 gradle 版本。
为了防止和在 Terminal 中执行 gradlew 命令时使用的 gradle 版本不同,建议勾选此配置(也是默认配置)。
当勾选 Specified location 时,使用指定目录下的 gradle 版本。
如果 gradle 下载很慢,就可以勾选此配置,以便使用指定本地下载好的 gradle 版本。

不管在 IDEA 中怎么配置,在 Terminal 中执行 gradlew 命令时,所使用的 gradle 版本都是由 gradle-wrapper.properties 决定的,并且下载路径也都是由环境变量 GRADLE_USER_HOME 决定的。

org.gradle.java.home 配置

这里的 JDK 指的是执行 Gradle 命令依赖的 JDK,并非 AndroidStudio 工程依赖的 JDK。

通过 File | Settings | Build, Execution, Deployment | Build Tools | Gradle 设置的 JDK,是在运行 IDEA 图形化按钮时使用的。
通过 gradle.properties 设置的 JDK,是在 Terminal 中执行 gradlew 命令时使用的。

1
2
3
4
5
6
# MacOS的路径写法
org.gradle.java.home=/Applications/Android Studio.app/Contents/jbr/Contents/Home

# Windows系统的路径写法参考如下
# org.gradle.java.home=C:\\Program Files\\Java\\jdk1.8.0_144
# org.gradle.java.home=C\:/_dev/Android/Android Studio/jre

注意:AGP 从 7.0.0-alpha02 版本起,需要使用 Java 11

用Android自带浏览器打开网页

启动android默认浏览器

1
2
3
4
5
val intent = Intent()
intent.data = Uri.parse(url)
intent.action = Intent.ACTION_VIEW
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)

启动指定浏览器打开(不推荐)

警告:爱加密加固之后的包,会把这个异常给吃掉,导致无法跳转,也无反应。

这种方式需要处理手机中不存在指定浏览器的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
try {
val intent = Intent()
intent.data = Uri.parse(targetUrl)
intent.action = Intent.ACTION_VIEW
intent.setClassName(
"com.android.browser",
"com.android.browser.BrowserActivity"
)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
} catch (e: Exception) {
e.printStackTrace()

// android.content.ActivityNotFoundException: Unable to find explicit activity class {com.android.browser/com.android.browser.BrowserActivity}; have you declared this activity in your AndroidManifest.xml?
val intent = Intent()
intent.data = Uri.parse(url)
intent.action = Intent.ACTION_VIEW
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
}

由于华为鸿蒙系统已经没有Android默认的浏览器,所以此处必须要有异常处理,或者提前处理手机中不存在指定浏览器的情况。

市场上常用浏览器的包名和类名@20220630

1
2
3
4
5
6
华为: "com.huawei.browser/com.huawei.browser.BrowserMainActivity"
Vivo: "com.vivo.browser/com.vivo.browser.MainActivity"
小米: "name=com.android.browser/com.android.browser.BrowserActivity"
uc浏览器: "com.uc.browser", "com.uc.browser.ActivityUpdate"
opera: "com.opera.mini.android", "com.opera.mini.android.Browser"
qq浏览器: "com.tencent.mtt", "com.tencent.mtt.MainActivity"

RecyclerView 根据滑动位置动态改变背景透明度

RecyclerView 根据滑动位置动态改变背景透明度

根据滑动位置动态改变背景透明度,直接上代码:

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
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
onRecyclerScrolled(recyclerView, dx, dy)
}

override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
}
})

private val dp180 = dp2px(180)
private var distanceY = 0
private var current = 0
private fun onRecyclerScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
distanceY += dy
when {
distanceY >= dp180 -> {
if (current == 1) return
recyclerView.setBackgroundColor(Color.argb(255, 246, 248, 250))
current = 1
}
distanceY <= 0 -> {
if (current == 0) return
recyclerView.setBackgroundColor(Color.argb(0, 246, 248, 250))
current = 0
}
else -> {
recyclerView.setBackgroundColor(
Color.argb(
distanceY * 255 / dp180,
246, 248, 250
)
)
current = -1
}
}
}

Android 分享功能

Android 分享功能

友盟分享SDK

https://developer.umeng.com/docs

友盟分享,QQ和QQ空间分享成功了,却总是回调分享取消

qqzone_id_value 配置跟当前应用对应不上,PlatformConfig.setQQZone(qqzone_id_value, qqzone_secret_id_value)

Your browser is out-of-date!

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

×