躬行之

躬行之 查看完整档案

成都编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

个人公众号:躬行之(jzman-blog),推送原创Android技术文章,segmentfault不定期更新。

可以添加我的微信:jzmanu
邀你进微信交流群交流。

个人动态

躬行之 发布了文章 · 9月25日

Android动画系列之帧动画和补间动画

原文首发于微信公众号:jzman-blog,欢迎关注交流!

Android 提供三种动画:帧动画、补间动画和属性动画,本篇文章介绍帧动画以及补间动画的使用,属性动画的使用将在后面的文章中分享,那就来复习一下这两种动画的使用吧。

FrameAnimation

FrameAnimation 即逐帧动画,通俗来说就是按照图片动作顺序依次播放来形成动画,创建 FrameAnimation 可用 xml 定义也可直接使用代码创建。

xml创建帧动画

在 res/drawable 文件夹下创建一个 drawable 文件,使用 animation-list 标签,具体内容如下:

<?xml version="1.0" encoding="utf-8"?>
<!--FrameAnimator-->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item
        android:drawable="@drawable/zzlx1"
        android:duration="100" />
    <item
        android:drawable="@drawable/zzlx2"
        android:duration="100" />
    <item
        android:drawable="@drawable/zzlx3"
        android:duration="100" />
    <!--...-->
</animation-list>

属性 oneshot 为 true 表示动画只能播放一次,false 表示动画循环播放,drawable 是当前动作对应的图片,duration 是其持续时间,duration 长度影响动画播放的快慢,然后在 Activity 中使用获取该 drawable 文件对应的 AnimationDrawable,然后使用 AnimationDrawable 对象来控制动画的状态,参考如下:

//获取Frame动画文件对应的AnimationDrawable
mAnimationDrawable = (AnimationDrawable) getResources().getDrawable(R.drawable.frame_animator);
//设置AnimationDrawable为图片的背景
imageView.setBackground(mAnimationDrawable);

//开启动画
mAnimationDrawable.start();
//停止动画
mAnimationDrawable.stop();
代码创建帧动画

使用代码创建帧动画就是创建 AnimationDrawable 对象,然后在 AnimationDrawable 中添加对应的 Frame 即可,代码参考如下:

//代码创建Frame动画
mAnimationDrawable = new AnimationDrawable();
//设置动画循环播放,true为动画只播放一次
mAnimationDrawable.setOneShot(false);
mAnimationDrawable.addFrame(getResources().getDrawable(R.drawable.zzlx1),100);
mAnimationDrawable.addFrame(getResources().getDrawable(R.drawable.zzlx2),100);
mAnimationDrawable.addFrame(getResources().getDrawable(R.drawable.zzlx3),100);
//...
imageView.setBackground(mAnimationDrawable);

//开启动画
mAnimationDrawable.start();
//停止动画
mAnimationDrawable.stop();

FrameAnimation 效果如下:

帧动画

TweenAnimation

TweenAnimation 即常说的补间动画,主要有以下几种:

  1. 位移动画(Translation)
  2. 缩放动画(Scale)
  3. 旋转动画(Rotate)
  4. 透明度动画(Alpha)
  5. 组合动画

上述动画都有自己特有的一下属性,下面来看一看这些动画通用的一些属性,具体如下:

<!--设置动画持续时间-->
android:duration="1200"
<!--动画开始的延时-->
android:startOffset ="1000"
<!--动画播放完是否回到动画开始的位置,默认true,如果fillBefore设置为false,动画不会停留在结束位置,不知道是不是bug-->
android:fillBefore = "true"
<!--动画播放完之后是否回到动画结束的位置,默认false,如果fillAfter设置为true,动画则会停留在结束位置-->
android:fillAfter = "false"
<!--设置fill...属性是否启用,对fillAfter无效-->
android:fillEnabled= "true"
<!--设置动画重复模式,restart为重新播放,reverse为倒序回放,和repeatCount搭配使用-->
android:repeatMode = "restart"
<!--设置动画重复次数-->
android:repeatCount = "0"
<!--设置动画插值器,这里的插值器是动画开始速度较慢,后面加速-->
android:interpolator = "@android:anim/accelerate_interpolator"

如果在代码中进行对应动画实现,这些属性也有对应的属性设置,直接设置即可。

位移动画(Translate)

位移动画对 View 进行水平方向或垂直方向位置的平移,可指定起始位置和结束位置,可使用 xml 定义位移动画也可以使用代码创建位移动画,位移动画对应的 Animation 的子类是 TranslateAnimation。

xml定义位移动画:在 res/anim 下创建一个xml文件 translation_anim.xml,在该文件中定义位移动画如下:

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1200"
    android:startOffset ="0"
    android:fillBefore = "true"
    android:fillAfter = "false"
    android:fillEnabled= "false"
    android:repeatMode = "reverse"
    android:repeatCount = "5"
    android:interpolator = "@android:anim/accelerate_interpolator"

    android:fromXDelta="0"
    android:fromYDelta="0"
    android:toXDelta="100"
    android:toYDelta="100">

上述 xml 文件定义了一个位移动画文件,其中位移动画自有的属性含义如下:

<!--水平方向动画开始的位置-->
android:fromXDelta="0"
<!--垂直方向动画开始的位置-->
android:fromYDelta="0"
<!--水平方向动画结束的位置-->
android:toXDelta="100"
<!--垂直方向动画结束的位置-->
android:toYDelta="100"

然后在 Activity 中获取该 xml 文件对应的 TranslateAnimation,将其设置到想要设置位移动画的 View 上即可,具体如下:

private void translation(){
    //获取在anim下定义的动画文件
    TranslateAnimation translateAnimation = (TranslateAnimation) AnimationUtils.loadAnimation(this, R.anim.translation_anim);、
    //设置并开启动画
    ivImage.startAnimation(translateAnimation);    
}

代码中创建位移动画:代码创建位移动画使用 Animation 的子类 TranslateAnimation,使用时直接创建 TranslateAnimation 对象即可,具体如下:

//代码创建位移动画
private void translation(){
    //表示相对View自身原点(View左上角)像素偏移量
    TranslateAnimation translateAnimation = new TranslateAnimation(0,100,0,100);
    //设置动画持续时间
    translateAnimation.setDuration(1200);
    //设置动画重复模式
    translateAnimation.setRepeatMode(Animation.REVERSE);
    //设置动画重复次数
    translateAnimation.setRepeatCount(3);
    translateAnimation.setFillAfter(true);
    //设置动画插值器
    translateAnimation.setInterpolator(this,android.R.anim.accelerate_interpolator);
//        translateAnimation.setInterpolator(new AccelerateInterpolator());
    //...
    ivImage.startAnimation(translateAnimation);    
}

上面参数中使用的时像素的偏移量,API 还提供了针对 View 自身一个父 View 的百分比的设置方式,下面这种创建 TranslateAnimation 对象的方式和上面实现的效果是一样的。具体如下:

/**
 * ABSOLUTE:表示相对View自身原点(View左上角)像素偏移量
 *          此时和TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)一样
 * RELATIVE_TO_SELF:表示相对View自身的百分比,如0.5f表示View自身大小的50%,1.0f表示View自身大小
 * RELATIVE_TO_PARENT:表示相对父View的百分比,如0.5f表示View自身大小的50%,1.0f表示View自身大小
 */
TranslateAnimation translateAnimation = new TranslateAnimation(
        Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0.46f,
        Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0.46f);

使用时可根据需要选择合适的构造方式创建 TranslateAnimation,测试效果如下:

位移动画

缩放动画(Scale)

缩放动画对 View 就是对视图进行一定程度的放大和缩小,可使用 xml 定义位移动画也可以使用代码创建位移动画,缩放动画对应的 Animation 的子类是 ScaleAnimation。

xml定义缩放动画:在 res/anim 下创建一个 xml 文件 scale_anim.xml,在里面定义缩放动画,具体如下:

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1200"
    android:startOffset ="0"
    android:fillBefore = "true"
    android:fillAfter = "false"
    android:fillEnabled= "false"
    android:repeatMode = "reverse"
    android:repeatCount = "3"
    android:interpolator = "@android:anim/accelerate_interpolator"

    android:fromXScale="1"
    android:fromYScale="1"
    android:toXScale="3"
    android:toYScale="3"
    android:pivotX="50%"
    android:pivotY="50%">
</scale>

上述 xml 文件定义了一个缩放动画文件,其中缩放动画自有的属性含义如下:

<!--设置水平方向上的起始缩放倍数-->
android:fromXScale="1"
<!--设置垂直方向上的起始缩放倍数-->
android:fromYScale="1"
<!--设置水平方向上的结束缩放倍数-->
android:toXScale="3"
<!--设置垂直方向上的结束缩放倍数-->
android:toYScale="3"
<!--设置缩放中心水平方向上的坐标-->
android:pivotX="50%"
<!--设置缩放中心垂直方向上的坐标-->
android:pivotY="50%">

其中 pivotX 和 pivotY 有三种设置方式:

  • 数字:如50表示缩放中心相较 View 原点偏移 50px
  • 百分比:如 50% 表示缩放中心相较 View 原点偏移 View 自身大小的 50%
  • 百分比p:如 50%p 表示缩放中心相较 View 原点偏移父 View 自身大小的 50%

然后在 Activity 中获取该 xml 文件对应的 ScaleAnimation,将其设置到想要设置位移动画的 View 上即可,具体如下:

private void scale(){
    ScaleAnimation scaleAnimation = (ScaleAnimation) AnimationUtils.loadAnimation(this,R.anim.scale_anim);
    ivImage.startAnimation(scaleAnimation);
}

代码创建缩放动画:代码创建缩放动画使用 Animation 的子类 ScaleAnimation,使用时直接创建 ScaleAnimation 对象即可,具体如下:

//代码创建缩放动画
private void scale(){
    ScaleAnimation scaleAnimation = new ScaleAnimation(1,3,1,3,
            Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
    scaleAnimation.setRepeatMode(Animation.REVERSE);
    scaleAnimation.setDuration(500);
    scaleAnimation.setRepeatCount(5);
    scaleAnimation.setInterpolator(this,android.R.anim.accelerate_decelerate_interpolator);
//        translateAnimation.setInterpolator(new AccelerateInterpolator());
    //...
    ivImage.startAnimation(scaleAnimation);
}

至于参数中的 pivotXType 和 pivotYType 和在上文中已经提到过,这里就不在赘述,测试效果如下:

缩放动画

旋转动画(Rotate)

旋转动画对 View 就是对视图角度进行旋转,可使用 xml 定义旋转动画也可以使用代码创建旋转动画,旋转动画对应的 Animation 的子类是 RotateAnimation。

xml定义旋转动画:在 res/anim 下创建一个 xml 文件 rotate_anim.xml,在里面定义缩放动画,具体如下:

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1200"
    android:startOffset ="0"
    android:fillBefore = "true"
    android:fillAfter = "false"
    android:fillEnabled= "false"
    android:repeatMode = "reverse"
    android:repeatCount = "5"
    android:interpolator = "@android:anim/accelerate_interpolator"

    android:fromDegrees="0"
    android:toDegrees="100"
    android:pivotY="50%"
    android:pivotX="50%">
</rotate>

上述 xml 文件定义了一个旋转动画文件,其中缩放动画自有的属性含义如下:

<!--设置动画开始时的角度,正数表示顺时针,负数表示逆时针-->
android:fromDegrees="0"
<!--设置动画结束时的角度,正数表示顺时针,负数表示逆时针-->
android:toDegrees="100"
<!--设置水平方向旋转中心点的坐标-->
android:pivotY="50%"
<!--设置垂直方向旋转中心点的坐标-->
android:pivotX="50%"

其中 pivotX 和 pivotY 有三种设置方式在上文中已经说明。然后在 Activity 中获取该 xml 文件对应的 RotateAnimation,将其设置到想要设置旋转动画的 View 上即可,具体如下:

private void rotate(){
    RotateAnimation rotateAnimation = (RotateAnimation) AnimationUtils.loadAnimation(this,R.anim.rotate_anim);
    ivImage.startAnimation(rotateAnimation);   
}

代码创建旋转动画:代码创建旋转动画使用 Animation 的子类 RotateAnimation,使用时直接创建 RotateAnimation 对象即可,具体如下:

//代码创建旋转动画
private void rotate(){
    RotateAnimation rotateAnimation = new RotateAnimation(0,100,
            Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
    rotateAnimation.setRepeatMode(Animation.REVERSE);
    rotateAnimation.setDuration(1200);
    rotateAnimation.setRepeatCount(3);
    rotateAnimation.setInterpolator(this,android.R.anim.accelerate_decelerate_interpolator);
//        translateAnimation.setInterpolator(new AccelerateInterpolator());
    //...
    ivImage.startAnimation(rotateAnimation);
}

测试效果如下:

旋转动画

透明度动画(Alpha)

透明度动画就是修改 View 的透明度,可使用 xml 定义透明度动画也可以使用代码创建透明度动画,透明度动画对应的 Animation 的子类是 AlphaAnimation。

xml定义透明度动画:在 res/anim 下创建一个 xml 文件 alpha_anim.xml,在里面定义缩放动画,具体如下:

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="3000"
    android:startOffset ="0"
    android:fillBefore = "true"
    android:fillAfter = "true"
    android:fillEnabled= "false"
    android:repeatMode = "restart"
    android:repeatCount = "0"
    android:interpolator = "@android:anim/accelerate_interpolator"

    android:fromAlpha="1"
    android:toAlpha="0">
</alpha>

上述 xml 文件定义了一个透明度动画文件,其中透明度动画自有的属性含义如下:

<!--设置动画的开始透明度,0表示透明,1表示不透明-->
android:fromAlpha="1"
<!--设置动画的结束透明度,0表示透明,1表示不透明-->
android:toAlpha="0"

然后在 Activity 中获取该 xml 文件对应的 AlphaAnimation,将其设置到想要设置旋转动画的 View 上即可,具体如下:

private void alpha(){
    AlphaAnimation alphaAnimation = (AlphaAnimation) AnimationUtils.loadAnimation(this,R.anim.alpha_anim);
    ivImage.startAnimation(alphaAnimation); 
}

代码创建透明度动画:代码创建透明度动画使用 Animation 的子类 AlphaAnimation,使用时直接创建 AlphaAnimation 对象即可,具体如下:

//代码创建透明度动画
private void alpha(){
    AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f,0.0f);
    alphaAnimation.setRepeatMode(Animation.RESTART);
    alphaAnimation.setDuration(1500);
    alphaAnimation.setRepeatCount(3);
//        alphaAnimation.setInterpolator(this,android.R.anim.accelerate_decelerate_interpolator);
//        translateAnimation.setInterpolator(new AccelerateInterpolator());
    //...
    ivImage.startAnimation(alphaAnimation);
}

透明度动画测试效果如下:

透明度动画

到此为止,位移、缩放、旋转、透明度动画的内容介绍完了,除了单独使用这些动画,还可以组合这些动画实现更复杂的动画,

组合动画

组合动画使用 AnimationSet 来实现,可使用 xml 定义组合动画也可以使用代码创建组合动画,透明度动画对应的 Animation 的子类是 AnimationSet。

xml定义组合动画:在 res/anim 下创建一个 xml 文件 combine_anim.xml,在里面定义组合动画,具体如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1200">

    <!--透明度动画-->
    <alpha
        android:repeatMode="reverse"
        android:repeatCount="10"
        android:fromAlpha="1"
        android:toAlpha="0.5" />

    <!--旋转动画-->
    <rotate
        android:repeatMode="reverse"
        android:repeatCount="10"
        android:fromDegrees="0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toDegrees="360" />

    <!--缩放动画-->
    <scale
        android:repeatMode="reverse"
        android:repeatCount="10"
        android:fromXScale="1"
        android:fromYScale="1"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="3"
        android:toYScale="3" />
</set>

然后在 Activity 中获取该 xml 文件对应的 AnimationSet,将其设置到想要设置旋转动画的 View 上即可,具体如下:

private void combine(){
    AnimationSet animationSet = (AnimationSet) AnimationUtils.loadAnimation(this,R.anim.combine_anim);
    ivImage.startAnimation(animationSet);
}

代码创建组合动画:代码创建组合动画使用 Animation 的子类 AnimationSet,使用时直接创建 AnimationSet 对象,将要组合的动画按序添加到 AnimationSet 中,具体如下:

//代码创建组合动画
private void combine(){
    AnimationSet animationSet = new AnimationSet(true);
    AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f,0.3f);
    alphaAnimation.setRepeatMode(Animation.REVERSE);
    alphaAnimation.setRepeatCount(3);
    RotateAnimation rotateAnimation = new RotateAnimation(0,360,
            Animation.RELATIVE_TO_SELF,0.5f,
            Animation.RELATIVE_TO_SELF,0.5f);
    rotateAnimation.setRepeatMode(Animation.REVERSE);
    rotateAnimation.setRepeatCount(3);
    ScaleAnimation scaleAnimation = new ScaleAnimation(1,3,1,3,
            Animation.RELATIVE_TO_SELF,0.5f,
            Animation.RELATIVE_TO_SELF,0.5f);
    scaleAnimation.setRepeatMode(Animation.REVERSE);
    scaleAnimation.setRepeatCount(3);

    animationSet.addAnimation(alphaAnimation);
    animationSet.addAnimation(rotateAnimation);
    animationSet.addAnimation(scaleAnimation);

    animationSet.setDuration(1200);
    //AnimationSet不支持动画重复播放,如果想要组合动画重复播放可设置每个动画重复播放即可
//        animationSet.setRepeatMode(Animation.REVERSE);
//        animationSet.setRepeatCount(10);

    ivImage.startAnimation(animationSet);
}

组合动画测试效果如下:

组合动画

总结

这篇文章总结了 Android 开发中帧动画(FrameAnimation)和补间动画(TweenAnimation)的使用,下一篇将会介绍属性动画(ObjectAnimator )。

可以关注公众号:躬行之(jzman-blog),一起交流学习。

image

查看原文

赞 0 收藏 0 评论 0

躬行之 发布了文章 · 9月24日

Android如何使用注解进行代码检查

原文首发于微信公众号:躬行之(jzman-blog),欢迎关注交流!

Android Studio 内置了代码检查工具 Lint,可在菜单栏选择 Analyze > Inspect Code 执行相应的代码检查,代码检查能够根据推断一些不合法的潜在问题,有助于在开发阶段发现开发者因为主管原因导致的一下代码问题,Android 官方提供了注解库 support-annotations 来帮助开发者及早发现问题,下面是常用的一些注解,主要内容如下:

  1. Nullness注解
  2. 资源注解
  3. 线程注解
  4. 值约束注解
  5. 权限注解
  6. 返回值注解
  7. CallSuper注解
  8. Typedef注解
  9. 可访问性注解

Nullness注解

使用 Nullness 注解可以检查给定变量、参数和返回值是否允许 null 值,具体如下:

  • @Nullable :表示可以为 null 的变量、参数或返回值,
  • @NonNull :表示不可为 null 的变量、参数或返回值。
@NonNull
@Override
public View onCreateView(String name, @NonNull Context context,@NonNull AttributeSet attrs) {
    //...
}

资源注解

资源注解的使用可使得在源码阶段让编辑器检查书写的不规范,也可在一定程度上优化代码结构,下面是常见的资源注解如下:

  • @StringRes: 表示检查是否包含R.string引用
  • @ColorRes: 表示检查是否包含R.color引用
  • @ColorInt: 表示检查是否包含表示颜色的整型
  • @DrawableRes: 表示检查是否包含R.drawable引用
  • @DimenRes: 表示检查是否包含R.dimen引用
  • @InterpolatorRes:表示检查是否包含插值器引用

线程注解

线程注解可以检查某个方法是否从某个特定类型的线程中调用,支持一下线程注解,具体如下:

  • @MainThread:表示主线程
  • @UiThread:表示 UI 线程
  • @WorkerThread:表示工作线程
  • @BinderThread:表示Binder线程
  • @AnyThread:表示任何一个线程

上述注解中 @MainThread@UiThread 在大多时候表示的都是同一线程,如果应用中带有多个试图,UI 线程可与主线程不同,故可使用 @UIThread 标注与应用的视图层次相关联的方法,使用 @MainThread 仅标注与应用生命周期相关联的方法。线程注解最常用的一个用途是 AsyncTask 使用中的方法替换,因为 AsyncTask 会执行后台操作并将结果发布到 UI 线程。

值约束注解

使用值约束注解可验证传递的参数的值的合法性,可以借此指定参数的设置范围,可在一定程度上减少代码在主观程度上出现的错误,常见的值约束注解如下:

  • @IntRange:表示可以验证整型参数是否在指定范围内
  • @FloatRange:表示可以验证浮点型参数是否在指定范围内
  • @Size:表示可以验证集合、数组、字符串参数是否在指定范围内,可指定最大值、最小值以及确切值

上面的注解有一些可使用的参数,如 from、to、min 等,使用时具体在某个注解体重查看定义即可。

权限注解

权限注解 @RequiresPermission 可以验证方法调用方的权限,即当使用了权限注解的方法时会检查有没有指定的权限,如果没有则会提示要在 AndroidManifest.xml 文件中申明权限,如果是危险权限还有进行权限动态申请,使用方式参考如下:

/**
 * 单个权限检查
 * @param message
 */
@RequiresPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
public void setMessage(String message) {
}

/**
 * 全部权限检查
 * @param message
 */
@RequiresPermission(allOf = {
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.READ_EXTERNAL_STORAGE})
public void setMesage(String message) {
}

/**
 * 某个权限检查
 * @param message
 */
@RequiresPermission(anyOf = {
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.READ_EXTERNAL_STORAGE})
public void setMesage(String message) {
}

返回值注解

返回值注解 @CheckResult 会检查某个方法的返回值是否被使用,如果没有被使用,则会根据 suggest 配置建议使用相同公民没有返回值的另一个方法,如果返回值使用了,则和未加该注解的方法一样,使用方式参考如下:

@CheckResult(suggest="#enforcePermission(String,int,int,String)")
public  int checkPermission(@NonNull String permission, int pid, int uid){
    return 0;
}

如果没有使用返回值提示如下:
jzman-blog
当返回值没有被使用,则会建议使用相同功能没有返回值的另一个方法,简而言之,返回值注解 @CheckResult 能够表示某个方法实际使用的时方法本身的处理还是方法最终的处理结果。

CallSuper注解

使用 @CallSuper 注解会验证子类的重写方法是否调用父类的实现,这样约束的好处是可保证父类的实现不会修改,当然,如果不使用该注解,子类重写父类的方法可以不调用弗父类的默认实现,具体参考如下:

/**
 * 父类
 * @CallSuper注解的使用
 */
public class Test {
    //使用@CallSuper注解,子类重写该方法时必须调用该方法
    @CallSuper
    protected void onCreate(){
        
    }
}

下面是 Test 类的实现类:

/**
 * 子类
 * @CallSuper注解的使用
 */
public class TestImpl extends Test{
    @Override
    protected void onCreate() {
        super.onCreate();
        /**
         * 如果不调用父类的方法,则会提示
         * Some methods, such as View#onDetachedFromWindow, require that you also call the super implementation as part of your method.
         */
    }
}

Typedef注解

使用 @IntDef@StringDef 注解 可以创建整型和字符串的枚举注解来验证其他代码中使用的某些整型和字符串,可以保证代码中的某些常量整型或常量字符串是某些具体定义的常量集,这两个注解的位置只能是注解。

开发中总会使用到枚举,枚举在一定程度上可使得代码结构更清晰,但枚举的使用会增加内存的开销,这里可以用 Typedef 注解的方式来代替枚举,下面是 Tyoedef 注解的使用,参考如下:

/**
 * Typedef 注解的定义
 */
public class ActionType {

    public static final int ACTION_TYPE_0 = 0;
    public static final int ACTION_TYPE_1 = 1;
    public static final int ACTION_TYPE_2 = 2;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({ACTION_TYPE_0,ACTION_TYPE_1,ACTION_TYPE_2})
    public @interface ActionTypeDef{

    }
}

下面是上述 Typedef 注解的使用方式,参考如下:

/**
 * Typedef注解的使用
 * @param value
 */
private void setValue(@ActionType.ActionTypeDef int value) {
    switch (value) {
        case ActionType.ACTION_TYPE_0:
            break;
        case ActionType.ACTION_TYPE_1:
            break;
        case ActionType.ACTION_TYPE_2:
            break;

//        case 100://不能使用未定义的整型
//            break;
    }
}

可见 Typedef 注解约束了使用到的某些整型,当然还可以是字符串,这样也能达到枚举的作用。

可访问性注解

可访问性注解是 @VisibleForTesting@Keep 可以表示方法、字段、类的可访问性。具体如下:

  • @VisibleForTesting:表示注解的某个代码块的可见性高于能够测试时需要的水平
  • @Keep:表示被注解的代码块将不会被混淆。

最常用的可能就是资源注解,如 @StringRes、@ColorRes、@ColorInt等,还有Typeof 注解,该注解可以在替换枚举在 Android 开发中带来的性能影响,如果平时留意这些注解在 Android 源码中也经常使用,所以可在开发过程中尝试去使用这些注解以进行必要的代码检查。

可以关注公众号:躬行之(jzman-blog),一起交流学习。

查看原文

赞 0 收藏 0 评论 0

躬行之 发布了文章 · 9月23日

Gradle系列之从零搭建Maven私服库

原文发于微信公众号 jzman-blog,欢迎关注交流。

前面几篇文章学习了 Gradle 相关知识,Gradle 系列文章如下:

今天的内容是使用 Nexus Repository Manager 搭建 Maven 私服库,这样可以将一些通用的库项目提取出来供其他项目使用,如工具库、基本组件库等,下面将从零开始搭建 Maven 私服库,

  1. 安装Nexus服务
  2. 运行Nexus服务
  3. 发布库项目
  4. 使用库项目
  5. 总结

安装Nexus服务

在如下地址下载 Nexus 安装包:

http://www.sonatype.com/download-oss-sonatype

选择适合自己的版本进行下载,如这里选择的是 nexus-3.13.0-01-win64,下载完成之后解压,有两个文件夹如下:

//nexus服务
nexus-3.13.0-01
//nexus相关配置文件,如日志、仓库、索引、缓存等配置文件
sonatype-work

然后打开 cmd 命令提示符以管理员方法运行,进入 Nexus 服务解压目录下的 bin 目录下执行如下命令来安装 Nexus 服务,参考如下:

nexus.exe/install

执行完成后 Nexus 服务就安装完成了,安装 Nexus 服务成功执行结果参考如下:

E:\Gradle\Nexus\nexus-3.13.0-01-win64\nexus-3.13.0-01\bin>nexus.exe/install
Installed service 'nexus'.

运行Nexus服务

安装完成 Nexus 服务之后执行如下命令来运行 Nexus 服务,参考如下:

nexus.exe/run

执行该命令会启动 Nexus 服务,执行成功的关键信息如下:


E:\Gradle\Nexus\nexus-3.13.0-01-win64\nexus-3.13.0-01\bin>nexus.exe/run
Preparing JRE ...
2018-09-10 23:02:21,265+0800 INFO  [FelixStartLevel] *SYSTEM org.sonatype.nexus.pax.logging.NexusLogActivator - start
//...
-------------------------------------------------

Started Sonatype Nexus OSS 3.13.0-01

-------------------------------------------------
//...

当 Nexus 服务正常启动之后,可使用如下地址访问 Nexus 服务,具体如下:

http://localhost:8081/ 

运行结果如下所示:

在这里插入图片描述

创建仓库

使用默认账户名 admin 和默认密码 admin123 登录成功之后,然后选择设置(小齿轮),在左侧选择 Security->Users,然后选择创建用户 Create local user 输入相关信息账号创建完成,然后使用刚才创建的新账号登录,然后就可以在自己的账号下面创建仓库了,可参考下图:

在这里插入图片描述

发布库项目

仓库创建好了之后就可以发布项目到仓库中了,这里首先使用 Android Studio 创建一个库项目 test,然后在其 build.gradle 文件中配置上传该库的 Task,看一下该库使用的插件:

//使用Android Library插件
apply plugin: 'com.android.library'
//使用maven插件
apply plugin: 'maven'

//Upload Task用于发布上传
uploadArchives{
    repositories{
        mavenDeployer{
            //正式发布仓库
//            repository(url:"http://localhost:8081/repository/jzman-releases/"){
//                authentication(userName:"jzman",password:"nexus2410.")
//            }
            //快照版本的仓库
            snapshotRepository(url:"http://localhost:8081/repository/jzman-snapshots/"){
                authentication(userName:"jzman",password:"nexus2410.")
            }

            pom.project {
                //版本号,如果是快照版本,其版本号后面应该添加-SNAPSHOT,否则不能正常识别上传
                version '1.0.0-SNAPSHOT'
                //一般写项目名称即可
                artifactId 'testWidget'
                //组别,类似包名,保证唯一性
                groupId 'com.manu.test'
                //打包格式
                packaging 'aar'
                //描述
                description '测试maven私服搭建'
            }
        }
    }
}
//...

配置完用于上传库项目的 task 之后,就可以根据需要上传到之前创建的对应仓库中了,执行上传 task 可使用 Android Gradle 提供的快捷方式,如下图所示:

在这里插入图片描述

等待 uploadArchives 执行完毕后对应的库项目就发布到对应的仓库了,下面是发布到了正式仓库,如下图所示:

在这里插入图片描述

正式版发布每次必须的更改版本号,否则不能发布成功,下面是发布到了快照仓库,如下图所示:

在这里插入图片描述

如果将库项目发布到快照仓库中,可以多次上传发布,每次不需要修改版本号,它会自己维持在原来版本号的基础上自增,如 1.0.0-时间戳-1、1.0.0-时间戳-2等,实际使用时还是用版本号 1.0.0-SNAPSHOT,使用时 Maven 会自动下载最新的快照版本,也就是序号最大的快照版本,这种方式便于将库项目发现的问题及时发布上去,有助于快速迭代,等到没有问题就可以发布正式版本了。

使用库项目

库项目是发布成功之后,就可以在任意的项目中使用这个库项目了,在项目下的 build.gradle 文件中配置项目使用的 Maven 仓库:

//...

allprojects {
    repositories {
        //...
        //配置maven仓库
        maven {
            url 'http://localhost:8081/repository/jzman-releases/'
        }
    }
}

//...

然后,在具体的项目中就可以配置对应依赖就可以使用这个库项目了,配置依赖如下:

dependencies {
    //...
    //配置依赖库项目
    compile 'com.manu.test:testWidget:1.0.0'
}

总结

本篇文章的主要内容就是在本地搭建一个可用的 Maven 私服库,实际开发中只需根据上述内容将对应的仓库地址更换为自己公司的仓库地址即可,其他如 jcenter 库的搭建也类似,这篇文章的内容就到此为止。

image

查看原文

赞 0 收藏 0 评论 0

躬行之 发布了文章 · 9月21日

Gradle系列之Android Gradle高级配置

本篇文章主要在之前学习的基础上,从实际开发的角度学习如何对 Android Gradle 来进行自定义以满足不同的开发需求,下面是 Gradle 系列的几篇文章:

下面是主要内容:

  1. 修改生成的Apk文件名
  2. 版本信息统一管理
  3. 隐藏签名文件信息
  4. 动态配置AndroidManifest文件
  5. 自定义BuildConfig
  6. 动态添加自定义资源
  7. Java编译选项
  8. adb操作选项配置
  9. DEX选项配置
  10. 自动起立未使用的资源
  11. 突破65535方法限制

修改生成的Apk文件名

修改打包输出的 Apk 的文件名主要用到三个属性:

applicationVariants //Android应用Gradle插件
libraryVariants     //Android库Gradle插件
testVariants        //上述两种插件都适用

下面是修改打包生成的 Apk 文件名的代码,参考如下:

android{
    //...
    
    /**
     * 修改打包生成的apk的文件名
     */
    applicationVariants.all { variant ->
        variant.outputs.all { output ->
            if (output.outputFile != null && output.outputFile.name.endsWith('.apk') &&
                    'release' == variant.buildType.name) {
                //输出文件名
                outputFileName = "AndroidGradleProject_v${variant.versionName}_${buildTime()}.apk"
            }
        }
    }   
}
//当前时间
def static buildTime() {
    def date = new Date()
    return date.format("yyyMMdd")
}

此时,执行 release 模式构建 Apk 的任务,生成的 Apk 的名字就修改了,当然还可以配置在 debug 模式下生成对应的文件名等。

版本信息统一管理

每个应用都有一个版本,版本一般由三部分组成:major.minor.patch,第一个是主版本号,第二个是副版本号,第三个是补丁号,如 1.0.0 这种格式的版本号,在 Android 开发中最原始的版本配置方式就是在 build.gradle 中在 defaultConfig 中配置对应的版本号和版本名称,参考如下:

//最原始的版本配置方式
android{
    defaultConfig {
        versionCode 1
        versionName "1.0"
        //...
    }
}

实际开发中一般将这种版本相关的信息单独定义在一个独立的版本管理文件中进行统一管理,定义 version.gradle 文件如下:

ext{
    //应用版本号、版本名称
    appversionCode = 1
    appVersionName = "1.0"
    //其他版本号...
}

然后在 build.gradle 中使用 version.gradle 文件中定义的版本号、版本名称即可,参考如下:

//引入version.gradle文件
apply from: "version.gradle"
android {
    //...
    defaultConfig {
        //使用version.gradle里定义的版本号
        versionCode appversionCode
        //使用version.gradle里定义的版本名称
        versionName appVersionName
        //...
    }
}

当然不只是应用的版本号,还有使用到的一些第三方的库的版本也可以使用这样的方式来统一管理。

隐藏签名文件信息

签名文件信息是非常重要的信息,如果将签名文件信息直接配置到项目中将是不安全的,那么签名文件如何能够安全呢,签名文件放在本地是不安全的,那么只能放在服务器上才是安全的,打包的时候从服务器上读取签名文件信息即可,当然这个服务器也可以是一台专门用于打包正式 Apk 的电脑,将签名文件和密钥信息配置成环境变量,打包是直接从环境变量中读取签名文件和密钥信息即可。

配置四个环境变量 STORE_FILE、STORE_PASSWORD、KEY_ALIAS、KEY_PASSWORD 分别对应签名文件、签名文件密码、签名文件密钥别名、签名文件密钥密码,环境变量的配置就不具体说了,代码参考如下:

android {
    //签名文件配置
    signingConfigs {
        //读取配置的与签名文件信息对应的环境变量
        def appStoreFile = System.getenv('STORE_FILE')
        def appStorePassword = System.getenv('STORE_PASSWORD')
        def appKeyAlias = System.getenv('KEY_ALIAS')
        def appKeyPassword = System.getenv('KEY_PASSWORD')
        //如果获取不到相关签名文件信息,则使用默认的签名文件
        if(!appStoreFile || !appStorePassword || !keyAlias || !keyPassword){
            appStoreFile = "debug.keystore"
            appStorePassword = "android"
            appKeyAlias = "androiddebugkey"
            appKeyPassword = "android"
        }
        release {
            storeFile file(appStoreFile)
            storePassword appStorePassword
            keyAlias appKeyAlias
            keyPassword appKeyPassword
        }
        debug {
            //默认情况下,debug模式下的签名已配置为Android SDK自动生成的debug签名文件证书
            //.android/debug.keystore
        }
    }
}

注意一点,配置好环境变量后,如果不能读取到新配置的环境变量,重启电脑后就能读取到了,至于如何使用专用的服务器进行打包、读取签名文件信息实践后再来介绍。

动态配置AndroidManifest文件

动态配置 AndroidManifest 配置就是动态的去修改 AndroidManifest 文件中的一些内容,如友盟等第三方统计平台分析统计的时候,一般会要求要在 AndroidManifest 文件中指定渠道名称,如下所示:

<meta-data android:value="CHANNEL_ID" android:name="CHANNEL"/>

这里 CHANNEL_ID 要替换成不同渠道的名称,如 baidu、miui 等各个渠道名称,那么如何动态的修改这些变化的参数呢,这里需要用到 Manifest 占位符和 manifestPlaceholder,manifestPlaceholder 是 ProductFlavor 的一个属性,是一个 Map 类型,可以配置多个占位符,具体代码参考如下:

android{
    //维度
    flavorDimensions "channel"
    productFlavors{
        miui{
            dimension "channel"
            manifestPlaceholders.put("CHANNEL","google")
        }
        baidu{
            dimension "channel"
            manifestPlaceholders.put("CHANNEL","baidu")
        }
    }
}

上述代码中配置了 flavorDimensions 属性,这个属性可以理解为维度,比如 release 和 debug 是一个维度、不同的渠道是一个维度、免费版本还是付费版本又是一个维度,如果这三个维度都要考虑,那么生成 Apk 的格式就是 2 2 2 供 8 个不同的 Apk,从 Gradle 3.0 开始不管是一个维度还是多个维度,都必须使用 flavorDimensions 来约束,上面代码中定义了一个维度 channel,再加上 buildType 中的 debug 和 release ,故此时生成不同 Apk 的个数是 4 个,如下图所示:

channel.jpg

当然,如果没有配置 flavorDimensions 则会出现如下错误,具体如下:

Error:All flavors must now belong to a named flavor dimension.

实际开发中根据实际情况配置对应的 flavorDimensions 即可。

然后,在 AndroidManifest 文件中使用占位符介绍打包时传递过来的参数,在 AndroidManifest 文件中添加 <mata-data> 如下:

<meta-data android:value="${CHANNEL}" android:name="channel"/>

最后,执行对应的渠道包任务,如执行 assembleBaiduRelease 将会将 AndroidManifest 中的渠道替换成 baidu,可使用命令执行也可使用 Android Studio 选择对应的 task 来执行,执行命令如下:

gradle assembleBaiduRelease

如果使用 Android Studio ,打开右侧 Gradle 控制面板,找到对应的 task 来执行相应的任务,如下图所示:

assembleBaiduRelease

选择对应的 task 执行就会生成对应的 Apk,使用 Android Killer 反编译打开生成的 Apk ,查看 AndroidManifest 文件如下:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.manu.androidgradleproject">
    <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme" roundIcon="@mipmap/ic_launcher_round">
        <!--AndroidManifest文件修改成功-->
        <meta-data android:name="channel" android:value="baidu"/>
        <activity android:name="com.manu.androidgradleproject.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <meta-data android:name="android.support.VERSION" android:value="26.1.0"/>
        <meta-data android:name="android.arch.lifecycle.VERSION" android:value="27.0.0-SNAPSHOT"/>
    </application>
</manifest>

上述案列中,渠道的名称是一致的,可以通过遍历很方便的完成渠道名称的替换,参考如下:

productFlavors.all{ flavor ->
    manifestPlaceholders.put("CHANNEL",name)
}

这一小节重要的一点就是关于 manifestPlaceholders 占位符的使用。

自定义BuildConfig

BuildConfig 是一个在 Android Gradle 构建脚本编译后生成的类,默认构建生成的 BuildConfig 内容如下:

/**
 * Automatically generated file. DO NOT MODIFY
 */
package com.manu.androidgradleproject;

public final class BuildConfig {
  public static final boolean DEBUG = false;
  public static final String APPLICATION_ID = "com.manu.androidgradleproject";
  public static final String BUILD_TYPE = "release";
  public static final String FLAVOR = "baidu";
  public static final int VERSION_CODE = 1;
  public static final String VERSION_NAME = "1.0";
}

上面 BuildConfig 中的一些常量都是关于应用的一些关键信息,其中 DEBUG 在 debug 模式下为 true,release 模式下为 false,此外还有应用包名、构建类型、构建渠道、版本号及版本名称,所以如果开发中需要用到这些值可以在 BuildConfig 中直接获取,比如包名的获取一般是 context.getPackageName(),如果直接从 BuildConfig 中获取是不是不仅方便而且有利于应用性能提升,所以,可在构建时在该文件中添加一些额外的有用的信息,可以使用 buildConfigField 方法,具体如下:

/**
 * type:生成字段的类型
 * name:生成字段的常量名称
 * value:生成字段的常量值
 */
public void buildConfigField(String type, String name, String value) {
    //...
}

下面使用 buildConfigField 方法为每个渠道配置一个相关地址,参考如下:

android{
    //维度
    flavorDimensions "channel"
    productFlavors{
        miui{
            dimension "channel"
            manifestPlaceholders.put("CHANNEL","miui")
            buildConfigField 'String' ,'URL','"http://www.miui.com"'
        }
        baidu{
            dimension "channel"
            manifestPlaceholders.put("CHANNEL","baidu")
            //buildConfigField方法参数value中的内容是单引号中的,如果value是String,则String的双引号不能省略
            buildConfigField 'String' ,'URL','"http://www.baidu.com"'
        }
    }
}

再打包时就会自动生成添加的字段,构建完成后查看 BuildConfig 文件,生成了上面添加的字段,参考如下:

/**
 * Automatically generated file. DO NOT MODIFY
 */
package com.manu.androidgradleproject;

public final class BuildConfig {
  public static final boolean DEBUG = false;
  public static final String APPLICATION_ID = "com.manu.androidgradleproject";
  public static final String BUILD_TYPE = "release";
  public static final String FLAVOR = "baidu";
  public static final int VERSION_CODE = -1;
  public static final String VERSION_NAME = "";
  // Fields from product flavor: baidu
  public static final String URL = "http://www.baidu.com";
}

至此,自定义 BuildConfig 的学习就到此为止,当然 buildConfigField 也可以使用到构建类型中,关键就是 buildConfigField 方法的使用。

动态添加自定义资源

Android 开发中资源文件都是放置在 res 目录下,还可以在 Android Gradle 中定义,自定义资源需要使用到 resValue 方法,该方法在 BuildType 和 ProductFlavor 对象中可以使用,使用 resValue 方法会生成相对应的资源,使用方式和在 res/values 文件中定义的一样

android{
    //...
    productFlavors {
        miui {
            //...
           /**
            * resValue(String type,String name,String value)
            * type:生成字段的类型(id、string、bool等)
            * name:生成字段的常量名称
            * value:生成字段的常量值
            */
            resValue 'string', 'welcome','miui'
        }

        baidu {
            //...
            resValue 'string', 'welcome','baidu'
        }
    }

}

当生成不同的渠道包时,通过 R.string.welcome 获取的值是不相同的,如生成的百度的渠道包时 R.string.welcome 的值为 baidu、生成小米渠道包时 R.string.welcome 的值为 miui,构建时生成的资源的位置在 build/generated/res/resValues/baidu/... 下面的 generated.xml 文件中,文件内容参考如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- Automatically generated file. DO NOT MODIFY -->

    <!-- Values from product flavor: baidu -->
    <string name="welcome" translatable="false">baidu</string>

</resources>

Java编译选项

在 Android Gradle 中还可以配置 Java 源代码的编译版本,这里使用到 compileOptions 方法, compileOptions 可配置三个属性:encoding、sourceCompatibility 和 targetCompatibility,通过这些属性来配置 Java 相关的编译选项,具体参考如下:

//配置Java编译选项
android {
    compileSdkVersion 26
    buildToolsVersion '26.0.2'
    compileOptions{
        //设置源文件的编码
        encoding = 'utf-8'
        //设置Java源代码的编译级别()
        sourceCompatibility = JavaVersion.VERSION_1_8
//        sourceCompatibility  "1.8"
//        sourceCompatibility  1.8
//        sourceCompatibility  "Version_1_8"
        //设置Java字节码的版本
        targetCompatibility = JavaVersion.VERSION_1_8
    }
}

adb操作选项设置

adb 的全称是 Android Debug Bridge,adb 主要用来连接手机来进行一些操作,比如调试 Apk、安装 Apk、复制文件等操作,在 Android Gradle 中可借助 adpOptions 来配置,可配置的有两个属性:installOptions 和 timeOutInMs,也可以通过相应的 setter 方法来设置,具体参考如下:

android{
    //adb配置选项
    adbOptions{
        //设置执行adb命令的超时时间
        timeOutInMs = 5 * 1000
        /**
         * 设置adb install安装这个操作的设置项
         * -l:锁定应用程序
         * -r:替换已存在的应用程序
         * -t:允许测试包
         * -s:把应用程序安装到SD卡上
         * -d:允许应用程序降级安装
         * -g:为该应用授予所有运行时的权限
         */
        installOptions '-r', '-s'
    }    
}

installOptions 的配置对应 adb install [-lrtsdg] <file> 命令,如果安装、运行或调试 Apk 的时候,如果出现 CommandRejectException 可以尝试设置 timeOutInMs 来解决,单位是毫秒。

DEX选项配置

Android 中的源代码被编译成 class 字节码,在打包成 Apk 的时候又被 dx 命令优化成 Android 虚拟机可执行的 DEX 文件,DEX 格式的文件是专为 Android 虚拟机设计的,在一定程度上会提高其运行速度,默认情况下给 dx 分配的内存是 1024M,在 Android Gradle 中可以通过 dexOptions 的五个属性:incremental、javaMaxHeapSize、jumboMode、threadCount 和 preDexLibraries 来对 DEX 进行相关配置,具体参考如下:

android{
    //DEX选项配置
    dexOptions{
        //设置是否启用dx增量模式
        incremental true
        //设置执行dx命令为其分配的最大堆内存
        javaMaxHeapSize '4g'
        //设置是否开启jumbo模式,如果项目方法数超过65535,需要开启jumbo模式才能构建成功
        jumboMode true
        //设置Android Gradle运行dx命令时使用的线程数量,可提高dx执行的效率
        threadCount 2
        /**
         * 设置是否执行dex Libraries库工程,开启后会提高增量构建的速度,会影响clean的速度,默认为true
         * 使用dx的--multi-dex选项生成多个dex,为避免和库工程冲突,可设置为false
         */
        preDexLibraries true
    }
}

自动清理未使用资源

Android 开发中打包 Apk 总是希望在相同功能的情况下 Apk 体积尽量小,那就要在打包之前删除没有使用的资源文件或打包时不将无用的资源打包到 Apk 中,可以使用 Android Lint 检查未使用的资源,但是无法清除一些第三方库中的无用资源,还可以使用 Resource Shrinking,可在打包之前检查资源,如果没有使用则不会被打包到 Apk 中,具体参考如下:

//自动清理未使用资源
android{
    buildTypes {
        release {
            //开启混淆,保证某些资源在代码中未被使用,以便于自动清理无用资源,两者配合使用
            minifyEnabled true
            /**
             * 打包时会检查所有资源,如果没有被引用,则不会被打包到Apk中,会处理第三方库不使用的资源
             * 默认不启用
             */
            shrinkResources true
            //开启zipalign优化
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug{
        }
    }
    //...
}

为防止有用资源未被打包到 Apk 中,Android Gradle 提供了 keep 方法来配置那些资源不被清理,在 res/raw/ 下新建一个 xml 文件来使用 keep 方法,参考如下:

<!--keep.xml文件-->
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
    tools:discard="@layout/l_used"
    tools:shrinkMode="safe"/>

可配置的三个属性:keep 表示要保留的资源文件,可使用以(,)分割的资源列表,可使用(*)作为通配符,discard 表示要移除的资源,和 keep 类似,shrinkMode 用于设置自动清理资源的模式,一般设置为 safe 即可,如果设置为 strict 则有可能清除可能会使用的资源。

此外,还可以使用 ProductFlavor 提供的方法 resConfigs 和 resConfig,可配置那些资源打包到 Apk 中,使用方式如下:

android{
    defaultConfig{
       //参数可以是Android开发时的资源限定符
        resConfigs 'zh'
        //...
    }
}

上述自动清理资源的方式只是不打包到 Apk 中,在实际的项目中并没有被清除,可通过日志查看哪些资源被清理了,然后决定要不要在项目中清除。

突破65535方法限制

在 Android 开发中总会遇到方法数大于 65535 时出现异常,那为什么会有这个限制呢,因为 Java 源文件被打包成一个 DEX 文件,这个文件是优化过的、可在 Dalvik 虚拟机上可执行的文件,由于 Dalvik 在执行 DEX 文件的时候,使用了 short 来索引 DEX 文件中的方法,这就意味着单个 DEX 文件可被定义的方法最多只有 65535 个。解决办法自然是当方法数超过 65535 个的时候创建多个 DEX 文件。

从 Android 5.0 开始的 Android 系统使用 ART 的运行方式,原生支持多个 DEX 文件,ART 在安装 App 的时候执行预编译,把多个 DEX 文件合并成一个 oat 文件执行,在 Android 5.0 之前,Dalvik 虚拟机只支持单个 DEX 文件,要想突破单个 DEX 方法数超过 65535 的限制,需使用 Multidex 库,这里就不在赘述了。

总结

​本篇文章的很多内容都可以用到实际开发中,这篇文章也是在边学习边验证的情况下完成的,断断续续花了一周时间,距离上次更文已有一周时间,希望阅读此文能够对你有所帮助。

image

查看原文

赞 0 收藏 0 评论 0

躬行之 发布了文章 · 9月17日

Gradle系列之Android Gradle基础配置

原文发于微信公众号 jzman-blog,欢迎关注交流。

通过前面几篇文章学习了 Gradle 基础知识以及 Gradle 插件相关的知识,关于 Gradle 及其插件相关知识请先阅读下面几篇文章:

上篇文章了解了 Android Gradle 插件的一下配置方法,记得刚开始接触 Android 中的 build.gradle 配置文件也是一脸懵逼,不知道各个配置项的具体含义,这篇文章将对 Android 开发中一些最基本的配置进行介绍,如果你有这方面的疑惑,相信这篇文章对你有一定收获

  1. 默认配置
  2. 配置签名信息
  3. 构建应用类型
  4. 使用混淆
  5. 启用zipalign优化

默认配置

defaultConfig 是 Android Gradle 配置文件中的一个配置块,defaultConfig 的类型是 ProductFlavor,如果没有自定义 ProductFlavor,则使用默认的 ProductFlavor 来配置 Android 工程,下面对 defaultConfig 中的一下配置属性进行介绍:

//默认的ProductFlavor配置块
defaultConfig {
    //配置App的包名
    applicationId "com.manu.base"
    //配合App最低支持的Android系统版本,下面两种minSdkVersion的配置效果是一致的
    minSdkVersion 19
    <!--minSdkVersion 'KitKat'-->
    //配置App基于哪个Android SDK开发
    targetSdkVersion 26
    //配置App的内部版本号,一般用于版本升级
    versionCode 1
    //配置App外部版本号,该版本号供用户查看
    versionName "1.0"
    //配置单元测试时使用的Runner
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    //配置测试App的包名
    testApplicationId "com.manu.androidgradleproject.test"
    //使用指定的签名文件配置
    signingConfig signingConfigs.release
}

配置签名信息

配置 App 签名信息的好处无非是防止 App 被恶意篡改,签名可保证 App 的唯一性且只有使用相同签名的后续升级包才能正常安装,在创建完签名文件之后,如果不做配置打包时每次都必须要指定签名文件的密码、别名等,一般 App 开发时在 denug 和 release 模式下时配置不同的签名文件。

第一步:创建签名证书文件,如下图所示填写相关信息:

第二步:使用 signConfigs 配置块配置已创建签名证书文件的相关信息如下:

//签名文件配置
signingConfigs {
    release{
        //签名证书文件
        storeFile file("project.jks")
        //签名证书文件密码
        storePassword "111111"
        //签名证书密钥别名
        keyAlias "project_jks"
        //签名证书中密钥密码
        keyPassword "111111"
    }
    debug{
        //默认情况下,debug模式下的签名已配置为Android SDK自动生成的debug签名文件证书
        //默认签名文件位置:.android/debug.keystore
    }
}

第三步:使用签名文件配置,在 android{} 下 defaultConfig{} 中使用上述配置,具体如下:

defaultConfig {
    //...
    //使用指定的签名文件配置
    signingConfig signingConfigs.release
}

除了在 defaultConfig{} 中配置,还可以在分别在 denbug 或者是 release 模式下配置不同的签名文件,可在 buildTypes{} 中单独配置配置,具体如下:

buildTypes {
    release {
        signingConfig signingConfigs.release
        //...
    }
    debug{
        signingConfig signingConfigs.debug
        //...
    }
    
    //...
}

构建应用的类型

Android Gradle 内置了两种构建类型 debug 和 release,两者区别是前者一般用在调试状态,后者一般用于正式发布状态,其他方面都一样,那么如何增加新的构建类型呢,可直接在 buildTypes{} 中添加要添加的类型即可,buildTypes 接收的参数是一个域对象,添加的构建类型都是 BuildType,所以可以通过 BuildType 的相关属性来配置构建类型,下面是 BuildType 的常见的一些配置属性:

buildTypes {
    release {
        //...
    }
    debug{
        //配置签名
        signingConfig signingConfigs.debug
        //配置在当前构建类型下applicationId的后缀,构建生成Apk的包名会在applicationId的基础上添加后缀
        applicationIdSuffix '.debug'
        //配置是否生成一个可供调试的Apk
        denbuggable true
        //配置是否生成一个可供调试jni(c/c++)代码的Apk
        jniDebuggable true
        //是否启用proguard混淆
        minifyEnabled true
        //配置当程序中方法数超过65535个时,是否启用自动拆分多个dex的功能,
        multiDexEnabled true
        //配置proguard混淆使用的配置文件,可配置对个混淆文件
        proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
        //配置是否自动清理未使用的资源,默认为false
        shrinkResources true
        //开启zipalign优化(见下文)
        zipAlignEnabled true
    }
}

当在 buildTypes{} 中添加新的构建类型之后,Android Gradle 都会自动生成一个 SourceSet,构建 Apk 会从对应的 SourceSet 中进行构建,切记新构建类型的名称不能和已有的相同。且要在 src 目录下为新创建的构建类型添加源代码目录和资源文件等,在创建好构建类型的同时,Android Gradle 插件也会生成对应的 assemble 任务来用于构建该类型的项目,如 release 对应的是 assembleRelease,执行该任务会生成对应的 Apk.

使用混淆

代码混淆主要了增加反编译的难度,发布正式版 App 时一般都得进行代码混淆,实际上 Android SDK 下面已经提供了默认的混淆文件,具体位置在 Android SDK 下面的 tools/progrard 下面,里面的内容主要是一些最基本的不能混淆的内容,两个默认的混淆文件如下:

//没优化
proguard-android.txt 
//已优化
proguard-android-optimize.txt

那么如何使用混淆呢,在 buildTypes{} 中对应的构建类型下设置 minifyEnabled 为 true 开启混淆,然后配置具体的混淆文件,具体如下:

buildTypes {
    release {
        //开启混淆
        minifyEnabled false
        //配置混淆文件
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
   //...
}

启用 zipalign 优化

zipalign 是 Android 提供的一个整理优化 apk 文件的工具,可在一定程度上上提高系统和应用的运行效率,更快的读取 apk 中的资源,降低内存的使用,开启 zipalign 优化只需要在 buildTypes{} 中对应的构建类型下开启 zipalign 优化即可,具体如下:

buildTypes {
    release {
       //开启zipalign优化
       zipAlignEnabled true
       //''
    }
   //...
}

这篇算介绍了 Android 开发中一些常见配置项的介绍。

可以关注公众号:躬行之(jzman-blog),一起交流学习。

image

查看原文

赞 0 收藏 0 评论 0

躬行之 发布了文章 · 8月23日

Gradle系列之Android Gradle插件

原文发于微信公众号 jzman-blog,欢迎关注交流。

通过前面几篇文章学习了 Gradle 基础知识以及 Gradle 插件相关的知识,关于 Gradle 及其插件相关知识请先阅读下面几篇文章:

学习 Gradle 的目的主要是为了更好的在 Android 开发中使用 Gradle,这篇文章的主要内容是 Android Gradle 插件的相关知识,主要内容如下:

  1. 了解 Android Gradle 插件
  2. Android Gradle 插件分类
  3. 使用 Android Gradle 插件
  4. Android Gradle 工程目录
  5. Android Gradle 基本配置
  6. Android Gradle 任务

了解 Android Gradle 插件

顾名思义,Android Gradle 插件就是一个用于 Android 构建项目的一个 Gradle 插件,该插件有 Google Android 开发团队开发,而且 Android 开发 IDE Android Studio 就采用 Gradle 构建项目,下面是 Android Gradle 插件的优势:

  1. 方便重用代码和资源;
  2. 可更方便的创建应用的衍生版本,如多渠道打包;
  3. 配置方便。可以扩展,还可以自定义构建过程
  4. Android Studio 与 Gradle 深度融合

Android Gradle 插件分类

Android 插件的分类是根据 Android 工程的属性进行分类的,Android 工程分为三类,具体如下:

  1. App 应用工程:可生成可运行的 apk ;
  2. Library 库工程:可生成 aar 共其他 App 应用工程使用,使用方式和 jar 一样,里面有相关的 Android 资源文件
  3. Test 测试工程:用于 App 应用过程或 Library 工程进行测试。

对应的就有三种不同的 Android Gradle 插件,三类插件分别是 App 插件、Library 插件和 Test 插件,其插件 id 如下:

//App插件id
com.android.application
//Library插件id
com.android.library
//Test插件
com.android.test

使用最多的肯定就是 App 插件和 Library 插件,使用这几个插件在 Android Studio 中就可以构建 Android 工程了。

使用 Android Gradle 插件

Gradle 插件使用时以插件的 id 作为唯一标识,如果是第三方的插件,还必须在 buildscript{} 中指定其 classpath,Android Gradle 插件就属于第三方插件,使用时要指定其 classpath,Android Gradle 插件托管到 jcenter 上,所以使用时要指定对应仓库,具体参考如下:

buildscript {
    //配置插件仓库
    repositories {
        google()
        jcenter()
    }
    //配置依赖
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'
    }
}

配置好第三方插件的仓库和依赖,然后使用 apply 使用该插件,具体如下:

//使用App插件
apply plugin: 'com.android.application'

android {
    //编译所依赖的Android SDK的版本
    compileSdkVersion 26
    //构建工具的版本
    buildToolsVersion '26.0.2'
    //...
}

dependencies {
    //...
}

在 Android 开发中一般将插件的仓库和依赖配置到根项目下的 build.gradle 文件中,其子 Module 项目将不需要在配置了,可直接使用,这里仅仅是 App 插件的使用,当然 Libray 插件的使用和 Test 插件的使用也是一样,使用时指定对应的 plugin id 即可。

Android Gradle 工程目录

下面是 Android Studio 创建 Android 工程的 Module 的目录,这也是 Android 工程的基本目录结构,参考如下:

app
├─libs
├─proguard-rules.pro
├─build.gradle
└─src
    ├─androidTest
    │  └─java
    │   
    ├─main
    │  ├─AndroidManifest.xml
    │  ├─java
    │  └─res
    │     
    └─test
        └─java

上述目录中 src 下面的 main、androidTest、test 是三个 SourceSet,main 表示 App 的源代码及资源目录,android 和 test 表示 Android 相关测试代码目录,此外 proguard-rules.pro 是当前项目的混淆文件,libs 用于存放 jar、aar 等库文件,build。gradle 文件用于当前配置文件。

Android Gradle 基本配置

为了介绍 Android Gradle 插件基本配置,使用 Android Studio 创建一个 Android 项目,下面的 app 的一个配置文件,具体如下:

//使用Android Gradle插件
apply plugin: 'com.android.application'
//android{}是Android工程配置的入口
android {
    /**
     * 指定编译依赖的Android SDK的版本,同
     * compileSdkVersion android-26
     * android.compileSdkVersion = 26
     * android.compileSdkVersion = 'android-26'
     */
    compileSdkVersion 26
    //指定构建工具的版本,也可以使用属性值buildToolsVersion设置其版本
    buildToolsVersion '26.0.2'
    /**
     * 默认配置,defaultConfig是一个ProductFlavor,可根据不同需求生成不同的Apk
     * 如果不自定义ProductFlavor进行单独配置,则该productFlavor会采用默认配置生成Apk
     * 这些具体配置,如applicationID都是ProductFlavor的属性
     */
    defaultConfig {
        //配置唯一包名
        applicationId "com.manu.androidgradleplugin"
        //最低支持的Android系统版本
        minSdkVersion 19
        //配置应用的目标Android系统版本
        targetSdkVersion 26
        //用于控制应用升级的版本号
        versionCode 1
        //用户看到的版本名称
        versionName "1.0"
        //测试时用到
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    /**
     * buildTypes是一个NamedDomainObjectContainer类型,是一个域对象,类似SourceSet(源集)
     * buildTypes中可自定义需要构建的类型,Gradle会自动创建一个相应的BuildType,如默认的release、debug
     */
    buildTypes {
        release {
            //设置是否为构建类型启用混淆
            minifyEnabled false
            //如果启用混淆则使用相应的混淆文件
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    productFlavors {
    }
}
//依赖配置
dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation files('libs/plugin.jar')
}

上述配置文件中已经标注了一些 Android Gradle 插件需要配置的地方,也算是对 Android Gradle 插件及项目配置文件 build.gradle 的一次理解与学习,关于 Android Gradle 插件的基本配置就到此为止,以后陆续补充。

Android Gradle 任务

Android Gradle 插件基于 Android Gradle 插件,Android Gradle 插件中包含了 Java Gradle 插件的一些任务,如 assembile、check、build等任务,此外, Android Gradle 插件还添加一些新任务,如 connectedCheck、deviceCheck、lint、install、unInstall 等 Android 开发特有的任务,可在 Android Studio 上查看 Android 开发中一些任务,选中 Android Studio 右侧标签 Gradle 就可查看,参考如下:

image.png

这篇算是初步从学习 Gradle 的角度了解了 Android Gradle 插件相关知识,后面的文章中会继续 Android Gradle 的学习。可以关注公众号:躬行之(jzman-blog),一起交流学习。

image

查看原文

赞 0 收藏 0 评论 0

躬行之 发布了文章 · 8月23日

Gradle系列之Java Gradle插件

原文发于微信公众号 jzman-blog,欢迎关注交流。

上篇文章中了解了 Gradle 插件相关知识以及如何自定义一个 Gradle 插件,为方便理解可以先阅读下面几篇文章:

本篇文章主要来学习 Java Gradle 插件相关的知识,因为 Java Gradle 插件相关的内容也是 Android Gradle 插件的基础。使用 Gradle 来构建项目无非就是帮助开发者做一些重复性的工作,如配置第三方依赖、编译源文件、单元测试、打包发布等,使用相应的 Grade 插件可方便项目的构建以及一定程度上提高开发效率,下面是今天学习的主要内容:

  1. Java Gradle 插件的使用
  2. Java 插件约定的项目结构
  3. 配置第三方依赖
  4. 如何构建 Java 项目
  5. SourceSet 源集概念
  6. Java 插件可添加的任务
  7. Java 插件可添加的属性
  8. 多项目构建
  9. 发布构件

Java Gradle 插件的使用

使用一个 Gradle 插件使用的是 Project 的 apply() 方法:

//java是Java Gradle插件的plugin id
apply plugin:'java'

使用 Java 插件之后会为当前工程添加默认设置和约定,如源代码的位置、单元测试代码的位置、资源文件的位置等,一般使用默认设置即可。

Java 插件约定的项目结构

Java 插件设置一些默认的设置和约定,下面来看一看 Java 项目的默认工程目录,目录结构基本如下:

JavaGradle
└─src
    ├─main
    │  ├─java
    │  └─resources
    └─test
        ├─java
        └─resources

上面目录结构中,src/main/java 默认是源代码存放目录,src/main/resources 是资源文件、配置文件等目录,arc/test 下面的目录当然是与其相对应的单元测试相关文件的存储目录,main 和 test 是 Java Gradle 插件内置的两个源代码集合,当然除此之外可以自己定义其他源代码集合,定义方式如下:

apply plugin : 'java'

sourceSets{
    //指定新的源代码集合
    vip{
        
    }
}

然后在 src 下面创建相应的 java 和 resources 目录,具体目录下存放的文件内容与默认类似,默认目录结构如下:

//源代码目录
src/vip/java
//资源文件目录
src/vip/resource

上述目录结构都是 Java Gradle 插件默认实现的,当然还可以修改具体目录的位置,配置方式如下:

sourceSets{
    //修改默认目录,下面还是和默认位置一样,如需配置其他目录修改即可
    main{
        java{
            srcDir 'src/java'
        }
        
        resources{
            srcDir 'src/resources'
        }
        
    }
}

配置第三方依赖

开发过程中总会使用第三方 jar 包,此时就要对 jar 包进行依赖配置,依赖的仓库类型以及仓库的位置,具体依赖时 Gradle 就会从配置的仓库中去寻找相关依赖,配置仓库和配置具体依赖参考如下:

//配置仓库位置
repositories{
    //仓库类型如jcenter库、ivy库、maven中心库、maven本地库、maven私服库等
    mavenCentral()
    mavenLocal()
    maven {
        uri "http"//xxxx
    }
    jcenter()
    google()
    //...
}

//配置具体的依赖
dependencies{
    //依赖三要素:group(组别)、name(名称)、version(版本)
    //分别对应Maven中的GAV(groupid、artifactid、version)
    
    //完整写法
    compile group: 'com.squareup.okhttp3', name: 'okhttp', version:'3.0.1'
    //简写
    compile 'com.squareup.okhttp3:okhttp:3.0.1'
}

上述代码中 compile 是一个编译时依赖,Gradle 还提供了其他依赖,具体参考如下:

compile:编译时依赖
runtime:运行时依赖
testCompile:测试时编译时依赖
testRuntime:仅在测试用例运行时依赖
archives:发布构件时依赖,如jar包等
default:默认依赖配置

在 Gradle 3.0 之后还会有 implementation、api 来替代 compile,这里就不多说这两个依赖方式了,关于新 API 的学习不是本文的重点,Java Gradle 插件还支持为不同的源代码集合指定不同的依赖,具体参考如下:

//为不同的源代码集合配置不同的依赖
dependencies{
    //配置格式:sourceSetCompile、sourceSetRuntime
    mainCompile 'com.squareup.okhttp3:okhttp:3.0.1'
    vipCompile 'com.squareup.okhttp3:okhttp:2.0.1'
}

上面介绍的是某个外部库的依赖,除此之外在开发中还会遇到 Module 的依赖、文件的依赖,实际上 Module 的依赖实际上就是某个子项目的依赖,文件以来一般就是 jar 包的依赖。

在Gradle系列之构建脚本基础这篇文章中已经知道构建某个子项目必须在 setting.gradle 文件中使用 include 关键字将子项目引入进来,这里也是一样,现在 setting.gradle 文件中要依赖的项目引入进来,然后按照如下方式依赖某个子项目,具体如下:

//依赖一个子项目
dependencies{
    //setting.gradle文件中 include ':childProject'
    
    //依赖子项目
    compile project('childProject')
}

文件依赖主要就是 jar 包的依赖,一般都是将 jar 包放在项目的 libs 目录下,然后在起来 jar 包,具体参考如下:

//依赖一个jar包
dependencies{
    //配置单个jar
    compile file('libs/java1.jar')
    //配置多个jar
    compile files('libs/java1.jar','libs/java2.jar')
    //批量配置jar,配置好jar所在路径,会将后缀为jar的所有文件依赖到项目中
    compile fileTree(dir:'libs',include:'*.jar')
}

如何构建Java项目

Gradle 中所有的操作都是基于任务的,Java Gradle 插件同样内置了一系列的任务帮助我们构建项目,执行 build 任务 Gradle 就开始构建当前项目了,可以使用 gradlew build 开始执行构建任务,Gradle 会编译源代码文件、处理资源文件、生成 jar 包、编译测试代码、运行单元测试等。

如在 Android 开发中有个 clean 任务,执行 clean 操作就会删除 build 文件夹以及其他构建项目生成的文件,如果编译出错可以尝试向 clean 然后 build。此外还有 check 任务,该任务会在单元测试的时候使用到,javadoc 任务可以方便生成 Java 格式的 doc api 文档,学习 Java 项目的构建目的还是为了学习 Android 项目构建做准备,所以如何使用 Gradle 构建一个 Java 项目就到此为止。

SourceSet 源集概念

这一小节来认识一下 SourceSet ,这也就是前问中提到的源代码集合,它是 Java Gradle 插件用来描述和管理源代码及其资源的一个抽象概念,是一个 Java 源代码文件和资源文件的集合,故可以通过源代码集合配置源代码文件的位置、设置源代码集合的属性等,源集可以针对不同的业务将源代码分组管理,如 Java Gradle 插件默认提供的 main 和 test 源代码目录,一个用于业务代码,另一个用于单元测试,非常方便。

Java Gradle 插件在 Project 下提供一个 sourceSet 属性以及 sourceSet{} 闭包来访问和配置源集,sourceSet 是一个 SourceSetContainer, 源集的常用属性如下:

//比如main、test等表示源集的名称
name(String)
//表示源集编译后的class文件目录
output.classDir(File)
//表示编译后生成的资源文件目录
output.resourcesDir(File)
//表示编译后源集所需的classpath
compileClasspath(FileCollection)
//表示该源集的Java源文件
java(SourceDirectorySet)
//表示该源集的Java源文件所在目录
java.srcDirs(Set)
//表示源集的资源文件
resources(SourceDirectorySet)
//表示该源集的资源文件所在目录
resources.srcDirs(Set)

下面是设置 main 这个源集的输出目录,参考如下:

//设置某个源集的属性
sourceSets{
    main{
        //源集名称只读
        println name
        //其他属性设置
        //从4.0开始已经被过时。替代的是dir
        output.classesDir = file("a/b")
//        output.dir("a/b")
        output.resourcesDir = file("a/b")
        //....
    }
}

Java 插件可添加的任务

项目的构建还是通过一系列 Gradle 插件提供的任务,下面是 Java 项目中常用的任务,具体如下:

任务名称类型描述
默认源集通用任务
compileJavaJavaCompile表示使用javac编译java源文件
processResourcesCopy表示把资源文件复制到生成的资源文件目录中
classesTask表示组装产生的类和资源文件目录
compileTestJavaJavaCompile表示使用javac编译测试java源文件
processTestResourcesCopy表示把资源文件复制到生成的资源文件目录中
testClassesTask表示组装产生的测试类和相关资源文件
jarJar表示组装jar文件
javadocJavadoc表示使用javadoc生成Java API文档
uploadArchivesUpload表示上传包含Jar的构建,使用archives{}闭包进行配置
cleanDelete表示清理构建生成的目录文件
cleanTaskNameDelete表示删除指定任务生成的文件,如cleanJar是删除jar任务生成的文件
自定义源集任务(SourceSet是具体的源集名称)
compileSourceSetJavaJavaCompile表示使用javac编译指定源集的源代码
processSouceSetResourcesCopy表示把指定源集的资源文件复制到生成文件中的资源目录中
sourcesSetClassesTask表示组装给定源集的类和资源文件目录

Java插件可以添加的属性

Java Gradle 插件中的常用属性都被添加到 Project 中,这些属性可以直接使用,具体如下:

属性名称类型描述
sourceSetsSourceSetContaunerJava项目的源集,可在闭包内进行相关配置
sourceCompatibilityJavaVersion编译Java源文件使用的Java版本
targetCompatinilityJavaVersion编译生成类的Java版本
archivesBaseNameString打包成jar或zip文件的名称
manifestManifest用来访问和配置manifest清单文件
libsDirFile存放生成的类库目录
distsDirFile存放生成的发布的文件目录

多项目构建

使用 Gradle 进行多个项目的构建,一般都是一个主项目依赖其他的子模块项目,是否构建这些子项目主要在 Setting.gradle 文件中配置,是否使用这些子项目则必须要在主项目中配置项目依赖,上文中接受过依赖的三种方式:库依赖、项目依赖和文件依赖,这里使用到的就是项目依赖,多项目配置中经常使用到 subprojects 和 allprojects ,具体如下:

//子项目统一配置
subprojects{
    //配置子项目都使用Java Gradle插件
    apply plugin: 'java'
    //配置子项目都是用Maven中心库
    repositories{
        mavenCentral()
    }
    //其他通用配置
    //...
}
//全部项目统一配置
allprojects{
    //配置所有项目都使用Java Gradle插件
    apply plugin: 'java'
    //配置所有项目都是用Maven中心库
    repositories{
        mavenCentral()
    }
    //其他通用配置
    //...
}

发布构件

Gradle 构建的产物,一般称之为构件,一个构建可以是一个 jar 包、zip 包等,那么如何发布构件呢,下面介绍如何发布一个 jar 构件到项目本地文件夹或 mavenLocal() 中,具体如下:

/**
 * 发布构件到项目文件夹/或mavenLocal()
 */
apply plugin : 'java'
//生成jar的任务,类似于自定义wrapper
task publishJar(type:Jar)
//构件版本
version '1.0.0'
//构件通过artifacts{}闭包配置
artifacts{
    archives publishJar
}
//构件发布上传
uploadArchives{
    repositories{
        flatDir{
            name 'libs'
            dirs "$projectDir/libs"
        }
        //将构建发布到mavenLocal()
        mavenLocal()
    }
}

执行 uploadArchives 任务就会在相应的位置生成相应的 jar 文件,执行命令如下:

//执行uploadArchives
gradle uploadArchives

执行成功之后,就会在项目的 libs 目录下看到生成的 jar 文件,如下图所示:

image.png

执行成功之后,就会在用户的 .m2/repository 目录下看到生成的 jar 文件,如下图所示:

image.png

下面是如何将 jar 发布到搭建的 maven 私服上,代码参考如下:

/**
 * 发布构件到maven私服
 */
apply plugin : 'java'
apply plugin : 'maven'
//生成jar的任务,类似于自定义wrapper
task publishJar(type:Jar)
//构件版本
version '1.0.0'
//构件通过artifacts{}闭包配置
artifacts{
    archives publishJar
}
//构件发布上传
uploadArchives{
    repositories{
        mavenDeployer{
            repository(url:'http://xxx'){
                //仓库用户名和密码
                authentication(userName:'username',password:'pass')
            }
            snapshotRepository(url:'http://xxx'){
                authentication(userName:'username',password:'pass')
            }
        }
    }
}

上传过程也是执行 uploadArchives 任务,有 maven 私服的可以尝试一下,关于 Java Gradle 插件的学习就到此为止,下一篇正式进入 Android Gradle 插件的学习,可以关注公众号:躬行之(jzman-blog),一起交流学习。

image

查看原文

赞 0 收藏 0 评论 0

躬行之 发布了文章 · 8月21日

Gradle系列之Gradle插件

原文发于微信公众号 jzman-blog,欢迎关注交流。

前面几篇文章学习了 Gradle 基础知识、Groovy 以及 Gradle 任务,可以先阅读前面几篇文章了解 Gradle 相关知识:

本篇文章主要介绍 Gradle 插件,Gradle 内置了很多常用的插件,Gradle 中的插件可在一定的场景中可以帮助我们提高开发效率,可以通过扩展现有的插件来实现更多功能,比如 Android Gradle 插件就是基于内置的 Java 插件来实现的。

  1. 插件的作用
  2. 如何应用一个插件
  3. 自定义插件

插件的作用

先来一下 Gradle 插件的作用,主要有以下几个方面:

  1. 添加任务到项目中,可对项目进行测试、编译、打包;
  2. 添加依赖到项目中,可用来配置项目构建过程中需要的依赖;
  3. 可以向项目中现有的对象类型添加新的扩展属性、方法等,可方便项目的配置和构建优化,比如 Android 项目构建中的 android{} 就是 Android Gradle 插件为 Project 对象添加的一个扩展;
  4. 可以对项目进行一些约定,如使用 Java Gradle 插件可以约定 src/main/java 目录下源代码的存放位置,在编译的时候就可以编译指定目录下的 Java 源代码文件。

如何应用一个插件

在使用一个插件之前要先使用 Project 的 apply 方法来应用该插件,插件分为二进制插件和脚本插件。

二进制插件的使用

二进制插件就是实现了 org.gradle.api.Plugin 接口的插件,每个 Java Gradle 插件都有一个 plugin id,可以通过如下方式使用一个 Java 插件:

apply plugin : 'java'

通过上述代码就将 Java 插件应用到我们的项目中了,其中 java 是 Java 插件的 plugin id,对于 Gradle 自带的核心插件都有唯一的 plugin id,这里的 java 对应的具体类型是 org.gradle.api.plugins.JavaPlugin,所以可以使用如下方式使用 Java 插件:

apply.plugin:org.gradle.api.plugins.JavaPlugin
//org.gradle.api.plugins默认导入
apply.plugin:JavaPlugin

二进制插件一般是打包在一个 Jar 中发布的,如自定义插件的时候在发布的时候要指定插件的 Plugin id,这个 plugin id 必须是唯一的,可使用应用包名来保证 plugin id 的唯一性。

脚本插件的使用

脚本插件的使用实际上就是某个脚本文件的使用,使用脚本插件时将脚本加载进来就可以了,使用脚本插件要使用到关键字 from,后面的脚本文件可以是本地的也可以是网络上的脚本文件,下面定义一段脚本,我们在 build.gradle 文件中使用它,具体如下:

//version.gradle文件
ext{
    versionName = "1.0"
    versionCode = 1
}

下面将将在构建文件中使用这个脚本文件,具体如下:

//build.gradle文件
apply from: 'version.gradle'

task taskVersion{
    doLast{
        println "版本是${versionName},版本号是${versionCode}"
    }
}

上述代码的执行结果如下:

PS E:\Gradle\study\GradlePlugin> gradle taskVersion

> Task :taskVersion
版本是1.0,版本号是1


BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed

显然,通过 apply from 引用到插件脚本了,脚本插件的使用可将构建使用的脚本文件分段整理,可将脚本文件分离成职责分明的单个脚本文件,然后直接使用 apply from 关键字来使用这些脚本文件,如可将某些工具性质的方法以及各个依赖使用的版本号分别定义到单个 gradle 文件中,方便统一调用和管理各个依赖的版本号等。

apply方法的用法

Project.apply() 可以接收三种不同的参数,具体如下:

//闭包作为参数
void apply(Closure closure);
//配置一个ObjectConfigurationAction
void apply(Action<? super ObjectConfigurationAction> action);
//Map作为参数
void apply(Map<String, ?> options);

使用上面三种方式可以配置一个插件,三种方式的写法如下:

//Map作为参数
apply plugin:'java'
//闭包作为一个参数
apply{
    plugin 'java'
}
//配置一个ObjectConfigurationAction
apply(new Action<ObjectConfigurationAction>() {

    @Override
    void execute(ObjectConfigurationAction objectConfigurationAction) {
        objectConfigurationAction.plugin('java')
    }
})
使用第三方发布的插件

大多时候需要第三方的插件来构建项目,使用的时候必须要在 buildscript{} 里配置 classpath 才能使用,如 Android Gradle 插件使用的时候就需要在 buildgradle{} 里面配置对应的 classpath,代码参考如下:

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

buildscript{} 块主要是在项目构建之前配置好项目构建的相关依赖,配置好这些依赖就可以通过如下方式使用插件了:

//使用时必须先在buildscript{}中配置相应的classpath
apply plugin: 'com.android.application'
使用plugins DSL应用插件

plugins DSL 是一种新的插件的应用方式,这种方式只能在 Gradle 2.1 以上才可以使用,使用方式参考如下:

//plugins DSL 方式
plugins{
    id 'java'
}
//如果第三方插件托管到https://plugins.gradle.org/,就不用在
//buildscript中配置classpath了
plugins{
    id "插件id" version '插件版本号'
}

关于插件的使用就到此为止。

自定义插件

大多时候需要自定义插件来完成一些项目的构建操作,自定义插件必须实现 Plugin 接口,接口中的 apply 方法会在插件被应用的时候执行,可实现该方法在里面进行相关操作,下面使用 Android Studio 来进行一个简单插件的开发,插件的作用创建一个任务,这里主要借助 Android Studio 来创建一个 Groovy 工程,然后进行相关代码的开发。

在 Android Studio 中创建一个 Module , 删除出 src/main、build.gradle 以外的其他文件,然后创建一个 .groovy 文件实现 Plugin 接口,文件内容如下:

package com.manu.plugin

import org.gradle.api.Plugin
import org.gradle.api.Project

/**
 * 自定义插件
 */
class MPlugin implements Plugin<Project>{

    @Override
    void apply(Project target) {
        target.task('taskPlugin')  {
            doLast {
                println "自定义插件创建任务"
            }
        }
    }
}

然后,指定该插件的 plugin id,在 src/main 下面创建 resources/META-INF/gradle-plugins 目录,然后创建一个带有 plugin id 的 properties 文件,文件内容如下:

//指定插件具体实现类
implementation-class=com.manu.plugin.MPlugin

下面是 build.gradle 文件对应的配置参考如下:

apply plugin: 'groovy'

dependencies {
    //gradle sdk
    compile gradleApi()
    //groovy sdk
    compile localGroovy()
}

可以说一个简单的插件就定义好了,为了方便实用将该插件项目生成 jar 包,然后就可以在其他项目中使用了,下面来一张自定义插件项目目录截图:

image.png

最后,在构建项目中使用该插件,复制插件到某个项目中,如复制到项目中的 libs 文件夹中,指定插件的 classpath ,使用 apply 方法使用插件即可:

apply plugin: 'com.manu.plugin'

buildscript{
    dependencies{
        classpath files('libs/plugin.jar')
    }
}

上述代码的执行结果如下:

PS E:\Gradle\study\GradlePlugin> gradle taskPlugin

> Task :taskPlugin
自定义插件创建任务


BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed

因为插件创建了任务 taskPlugin,所以可以使用该任务,插件的基本使用如上所述,接下来将会继续学习 Java Gradle 插件的使用。可以关注公众号:jzman-blog,一起交流学习。

image

查看原文

赞 0 收藏 0 评论 0

躬行之 发布了文章 · 8月15日

Gradle系列之认识Gradle任务

原文发于微信公众号 jzman-blog,欢迎关注交流。

前面几篇学习了 Gradle 构建任务的基础知识,了解了 Project 和 Task 这两个概念,建议先阅读前面几篇文章:

Gradle 的构建工作是有一系列的 Task 来完成的,本文将针对 Task 进行详细介绍,本文主要内容如下:

  1. 多种方式创建任务
  2. 多种方式访问任务
  3. 任务分组和描述
  4. 操作符
  5. 任务的执行分析
  6. 任务排序
  7. 任务的启用和禁用
  8. 任务的onlyIf断言
  9. 任务规则

多种方式创建任务

Gradle 中可以使用多种方式来创建任务,多种创建任务的方式最终反映在 Project 提供的快捷方法以及内置的 TaskContainer 提供的 create 方法,下面是几种常见的创建任务的方式:

/**
 * 第一种创建任务方式:
 * 方法原型:Task task(String name) throws InvalidUserDataException;
 */
//定义Task变量接收task()方法创建的Task,方法配置创建的Task
def Task taskA = task(taskA)
//配置创建的Task
taskA.doFirst {
    println "第一种创建任务的方式"
}

/**task
 * 第二种创建任务方式:可在Map参数中进行相关配置,如依赖、任务描述、组别等
 * 方法原型:Task task(Map<String, ?> args, String name) throws InvalidUserDataException;
 */
def Task taskB = task(group: BasePlugin.BUILD_GROUP,taskB,description: "描述")
//配置创建的Task
taskB.doLast {
    println "第二种创建任务的方式"
    println "任务taskB分组:${taskB.group}"
    println "任务taskB描述:${taskB.description}"
}

/**
 * 第三种创建任务方式:通过闭包的方式创建Task,闭包里的委托对象就是Task,即可在闭包内调用Task
 * 的一切属性和方法来进行Task的配置
 * 方法原型:Task task(String name, Closure configureClosure);
 */
task taskC{
    description 'taskC的描述'
    group BasePlugin.BUILD_GROUP
    doFirst{
        println "第三种创建任务的方式"
        println "任务taskC分组:${group}"
        println "任务taskC描述:${description}"
    }
}

/**
 * 第四种创建任务的方式:可在闭包中灵活配置,也可在Map参数中配置,闭包中中的配置父覆盖Map中相同的配置
 * 方法原型:Task task(Map<String, ?> args, String name, Closure configureClosure);
 */
def Task taskD = task(group: BasePlugin.BUILD_GROUP,taskD,description: "描述"){
    description 'taskD的描述'
    group BasePlugin.UPLOAD_GROUP
    doFirst{
        println "第四种创建任务的方式"
        println "任务taskD分组:${group}"
        println "任务taskD描述:${description}"
    }
}

上面是创建任务的四种方式,使用时选择合适的创建方式即可,上面提到 Map 中可以配置 Task 的相关参数,下面是是 Map 中可使用的配置:

type:基于一个已存在的Task来创建,类似于类的继承,默认值DefaultTask
overwrite:是否替换存在的Task,一般和type配合使用,默认值false
dependsOn:配置当前任务的依赖,默认值[]
action:添加到任务中的一个Action或者是一个闭包,默认值为null
description:任务描述,默认值null
group:任务分组,默认值null

通过闭包的方式创建 Task,闭包里的委托对象就是 Task,即可在闭包内调用 Task
的一切属性和方法来进行 Task 的配置,可以说使用闭包的这种任务创建方式更灵活,此外还可以使用 TaskContainer 创建任务,参考如下:

//使用TaskContainer创建任务的方式
tasks.create("taskE"){
    description 'taskE的描述'
    group BasePlugin.BUILD_GROUP
    doFirst{
        println "第三种创建任务的方式"
        println "任务taskE分组:${group}"
        println "任务taskE描述:${description}"
    }
}

tasks 是 Project 的属性,其类型是 TaskContainer,所以可以通过 tasks 来创建任务,当然 TaskContainer 创建任务也有创建任务的其他构造方法,到此关于任务的创建就基本介绍完了。

多种方式访问任务

创建的任务(Task)属于项目(Project)的一个属性,其属性名就是任务名,该属性的类型是 Task,如果已知任务名称,那么可以通过任务名直接访问和操纵该任务了,也可以理解访问和操纵该任务所对应的 Task 对象,参考
如下:

/**
 * 访问任务的第一种方式:Task名称.doLast{}
 */
task taskF{

}
taskF.doLast{
    println "第一种访问任务的方式"
}

任务都是通过 TaskContainer 的 create 方法创建的,而 TaskContainer 是创建任务的集合,在 Project 中可通过 tasks 属性访问 TaskContainer ,tasks 的类型就是 TaskContainer,所以对于已经创建的任务可通过访问几何元素的方式访问已创建任务的属性和方法,参考代码如下:

/**
 * 访问任务的第二种方式:使用TaskContainer访问任务
 */
task taskG{

}
tasks['taskG'].doLast {
    println "第二种访问任务的方式"
}

在 Groovy 中 [] 也是一个操作符,上面 tasks['taskG'] 的真正含义是 tasks.getAt('taskG') , getAt() 方法在 TaskCollection 中的方法,这样可以通过任务名称对相关任务进行访问和操作。

还可以通过路径访问的方式访问任务,通过路径访问任务有两个关键方法:findByPath 和 getByPath,区别在于前者找不到指定任务的时候会返回 null,后者找不到任务的时候会抛出 UnknowTaskException 异常,代码参考如下:

/**
 * 访问任务的第三种方式:使用路径访问任务
 */
task taskH{
    println 'taskH'
    //通过路径访问任务,参数可以是路径(没有访问成功,写法如下)
    println tasks.findByPath(':GradleTask:taskG')
    //通过路径访问任务,参数可以是任务名称
    println tasks.findByPath('taskG')
    println tasks.getByPath('taskG')
}

上述代码执行结果参考如下:

PS E:\Gradle\study\GradleTask> gradle taskH

> Configure project :
taskH
null
task ':taskG'
task ':taskG'


FAILURE: Build failed with an exception.

* Where:
Build file 'E:\Gradle\study\GradleTask\build.gradle' line: 98

* What went wrong:
A problem occurred evaluating root project 'GradleTask'.
> Task with path 'test' not found in root project 'GradleTask'.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

* Get more help at https://help.gradle.org

BUILD FAILED in 2s

使用路径访问任务的过程,参数写成路径访问不到具体的任务,可能是写法问题,希望在后面的学习中能够解决。

此外,还可以通过任务名称访问,方法主要是 findByName 和 getByName,区别和第三种访问方式一样, 代码参考如下:

/**
 * 访问任务的第四种方式:使用任务名称访问
 */
task taskJ
tasks['taskJ'].doLast{
    println 'taskJ'
    println tasks.findByName('taskJ')
    println tasks.getByName('taskJ')
}

上面学习了访问任务的四种方式,通过对 Gradle 访问任务的了解,在具体的项目构建上在结合上面访问任务的方式灵活使用。

任务分组和描述

对于任务分组及描述实际上在之前的文章已经提到过且配置过,这里再简单说明一下,任务分组和描述实际上就是对已经创建的任务配置分组和任务描述,如下面这样配置:

//任务分组与描述
def Task task1 = task taskK
task1.group = BasePlugin.BUILD_GROUP
task1.description = '测试任务分组与描述'
task1.doLast {
    println "taskK is group = ${group}, description = ${description}"
}

下面是上述代码执行结果,参考如下:

PS E:\Gradle\study\GradleTask> gradle taskK

> Task :taskK
taskK is group = build, description = 测试任务分组与描述


BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed

从执行结果可以看出,如果配置了任务的相关属性,那么就可以访问到任务的所有信息了。

操作符

学习一个操作符 << ,之前的测试代码中为了测试都会调用 task.doLast() 方法,我们可以使用 << 操作符来代替 doLast 方法,也就是说 daLast() 方法可以这样写:

//<< 任务操作符
//简写方式,Gradle 5.0 开始以不推荐这种写法
task taskL <<{
    println "doLast"
}
//推荐写法
task taskL{
    doLast{
        println "doLast"
    }
}

上述两种写法的执行结果参考如下:

PS E:\Gradle\study\GradleTask> gradle taskL

> Configure project :
The Task.leftShift(Closure) method has been deprecated and is scheduled to be removed in Gradle 5.0. Please use Task.doLast(Action) instead.
        at build_6syzx8ks0l09hby4j6yn247u9.run(E:\Gradle\study\GradleTask\build.gradle:123)

> Task :taskL
doLast


BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed
PS E:\Gradle\study\GradleTask> gradle taskL

> Task :taskL
doLast


BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed
PS E:\Gradle\study\GradleTask>

从输出结果可以看到两种写法都输出了想要的结果,同时观察日志发现这种简写方式已经在 Gradle 5.0 开始已经被放弃,所以推荐搭建使用 doLast 的方式配置任务。

任务的执行分析

在 Gradle 任务执行过程中,我们可以通过 doFirst 和 doLast 在任务执行之前或执行之后进行任务相关配置,当我们执行一个任务的时候,实际上是在执行该 Task 所拥有的 action,可以通过 getActions() 方法获取所有可以执行的 action,下面自定义一个 Task 类型 CustomTask ,并使用注解 @TaskAction 标注方法 doSelf 表示 Task 本身要执行的方法,代码如下:

//任务执行流程分析
def Task taskA = task taskB(type: CustomTask)
taskA.doFirst {
    println "Task执行之前调用:doFirst"
}

taskA.doLast {
    println "Task执行之后调用:doLast"
}

class CustomTask extends DefaultTask{
    @TaskAction
    def doSelf(){
        println "Task执行本身调用:doSelf"
    }
}

上述代码的执行结果如下:

PS E:\Gradle\study\GradleTask2> gradle taskB

> Task :taskB
Task执行之前调用:doFirst
Task执行本身调用:doSelf
Task执行之后调用:doLast


BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed

由于 Task 的执行是在遍历需要执行的 action 列表,为了保证执行的顺序,则必须将 doFirst 对应的 action 放在 action 列表的最前面,doLast 对应的 action 放在 action 列表的最后面,doSelf 对应的 action 放置在列表的中间位置,这样就能保证对应的执行顺序了,下面是伪代码:

//创建任务的时候将使用@TaskAction标注的方法作为Task本身执行的Task
//此时,任务正在创建,actionList里面只有Task本身执行的Action
actionList.add(0,doSelfAction)
//任务创建完成之后,如果设置了doFirst则会在actionList最前面添加doFist对应的action
//此时,doFirst对应的action添加actionList的最前面,保证了doFirst方法在任务开始执行之前执行
actionList.add(0,doFirstAction)
//任务创建完成之后,如果设置了doLast则会在actionList最后面添加doLast对应的action,保证了doLast方法在任务开始执行之后执行
actionList.add(doLastAction)

任务执行的流程基本如上,尽量在具体实践中多体会。

任务排序

Gradle 中任务排序使用到的是 Task 的两个方法 shoundRunAfter 和 mustRunAfter,可以方便的控制两个任务谁先执行:

/**
 * 任务顺序
 * taskC.shouldRunAfter(taskD):表示taskC要在taskD的后面执行
 * taskC.mustRunAfter(taskD):表示taskC必须要在taskD的后面执行
 */
task taskC  {
    doFirst{
        println "taskC"
    }
}
task taskD  {
    doFirst{
        println "taskD"
    }
}
taskC.shouldRunAfter(taskD)

上述代码的执行结果,参考如下:

PS E:\Gradle\study\GradleTask2> gradle taskC taskD

> Task :taskD
taskD

> Task :taskC
taskC


BUILD SUCCESSFUL in 2s
2 actionable tasks: 2 executed

任务的启用和禁用

Task 中有个 enabled 属性,可以使用该属性启用和禁用某个任务,设置为 true 则启用该任务,反之则禁用该任务,该属性默认为 true,使用如下所示:

taskA.enabled = true

任务的onlyIf断言

断言是一个条件表达式, Task 对象有一个 onlyIf 方法,该方法可以接收一个闭包作为参数,如果该闭包内参数返回 true,则该任务执行,反之则不执行该任务,这样可以通过任务的断言来控制那些任务需要执行,下面通过一个打包的案列来学习任务的断言,代码参考如下:

//任务的onlyIf断言
final String BUILD_ALL = 'all'
final String BUILD_FIRST = 'first'
final String BUILD_OTHERS = 'others'

task taskTencentRelease{
    doLast{
        println "打应用宝渠道包"
    }
}

task taskBaiduRelease{
    doLast{
        println "打百度手机助手渠道包"
    }
}

task taskMiuiRelease{
    doLast{
        println "打小米应用商店渠道包"
    }
}

task buildTask{
    group BasePlugin.BUILD_GROUP
    description "打渠道包"
}

//为buildTask添加依赖的具体任务
buildTask.dependsOn taskTencentRelease, taskBaiduRelease, taskMiuiRelease

taskTencentRelease.onlyIf{
    if (project.hasProperty("buildApp")){
        Object buildApp = project.property("buildApp")
        return BUILD_ALL == buildApp || BUILD_FIRST == buildApp
    }else{
        return true
    }
}

taskBaiduRelease.onlyIf{
    if (project.hasProperty("buildApp")){
        Object buildApp = project.property("buildApp")
        return BUILD_ALL == buildApp || BUILD_FIRST == buildApp
    }else{
        return true
    }
}

taskMiuiRelease.onlyIf{
    if (project.hasProperty("buildApp")){
        Object buildApp = project.property("buildApp")
        return BUILD_OTHERS == buildApp || BUILD_ALL == buildApp
    }else{
        return true
    }
}

下面是上述代码的执行结果:

PS E:\Gradle\study\GradleTask2> gradle -PbuildApp=first buildTask

> Task :taskBaiduRelease
打百度手机助手渠道包

> Task :taskTencentRelease
打应用宝渠道包


BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed
PS E:\Gradle\study\GradleTask2> gradle -PbuildApp=others buildTask

> Task :taskMiuiRelease
打小米应用商店渠道包


BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed
PS E:\Gradle\study\GradleTask2> gradle -PbuildApp=all buildTask

> Task :taskBaiduRelease
打百度手机助手渠道包

> Task :taskMiuiRelease
打小米应用商店渠道包

> Task :taskTencentRelease
打应用宝渠道包


BUILD SUCCESSFUL in 1s
3 actionable tasks: 3 executed

可以看出,当我们执行 buildTask 时为 Project 配置了属性 buildApp,通过 buildApp 不同的值,借助 onlyIf 实现了不同渠道包的定制打包策略,可在实际开发中借鉴加以使用。

此外,注意上述代码执行命令的写法,具体如下:

//其中buildApp和=后面的值others是键值对的关系,使用命令执行任务时可使用-P命令简写
//-P要为当前Project指定K-V的属性键值对,即-PK=V
gradle -PbuildApp=others buildTask

任务规则

创建的任务都是在 TaskContain 里面,我么可以通过从 Project 的属性 tasks 中根据任务的名称来获取想要获取的任务,可以通过 TaskContain 的 addRule 方法添加相应的任务规则,参考代码如下:

//任务规则
tasks.addRule("对该规则的一个描述"){
    //在闭包中常常将->作为参数与代码块之间的分隔符
    String taskName ->
        task(taskName) {
            doLast{
                println "${taskName} 不存在"
            }
        }
}

task taskTest{
    dependsOn taskX
}

上述代码的执行结果:

PS E:\Gradle\study\GradleTask2> gradle taskTest

> Task :taskX
taskX 不存在


BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed

如果不指定对某些特殊情况的任务处理,则会报错,如果处理了则会输出相关的提示信息,Gradle 任务的了解和学习就到此为止。可以关注公众号:躬行之(jzman-blog),一起交流学习。

查看原文

赞 0 收藏 0 评论 0

躬行之 收藏了文章 · 4月19日

小姐姐用动画图解Git命令,一看就懂!

无论是开发、运维,还是测试,大家都知道Git在日常工作中的地位。所以,也是大家的必学、必备技能之一。之前公众号也发过很多git相关的文章。

但是呢,民工哥,也经常在后台看到读者说,命令太多了不好记啊,时间长了不用又忘记了等等的吐槽。是啊,要学一门技术真难,何况现在技术更新、迭代这么快.....

所以,对于学习Git这门技术,要是有一个一看就懂,一学就会的入门资料就好了。前不久,国外的一位小姐姐写了一篇这样的文章《CS Visualized: Useful Git Commands》。作者是来自英属哥伦比亚的小姐姐 Lydia Hallie,在这篇文章里面,她通过生动形象的动画,以更加直观的方式,向开发者展示 Git 命令中的 merge、rebase、reset、revert、cherry-pick 等常用骚操作的具体原理。

下面就给大家带来一些实例分享:

1、git merge

fast-forward模式

640.gif

no-fast-forward模式

640 (1).gif

合并冲突修复的过程 ,动画演示如下:

640 (2).gif

2、git rebase

git rebase 指令会复制当前分支的所有最新提交,然后将这些提交添加到指定分支提交记录之上。

640 (4).gif

git rebase还提供了 6 种操作模式:

  • reword:修改提交信息
  • edit:修改此提交
  • squash:将当前提交合并到之前的提交中
  • fixup:将当前提交合并到之前的提交中,不保留提交日志消息
  • exec:在每一个需要变基的提交上执行一条命令
  • drop:删除提交

以 drop 为例:
msofpv7k6rcmpaaefscm.gif

以 squash 为例:

640 (7).gif

3、git reset

以下图为例:9e78i 提交添加了 style.css 文件,035cc 提交添加了 index.js 文件。使用软重置,我们可以撤销提交记录,但是保留新建的 style.css 和 index.js 文件。

640 (6).gif

Hard reset硬重置

硬重置时:无需保留提交已有的修改,直接将当前分支的状态恢复到某个特定提交下。需要注意的是,硬重置还会将当前工作目录(working directory)中的文件、已暂存文件(staged files)全部移除!如下图所示:

640 (8).gif

4、git revert

举个例子,我们在 ec5be 上添加了 index.js 文件。之后发现并不需要这个文件。那么就可以使用 git revert ec5be 指令还原之前的更改。如下图所示:
640 (9).gif

5、git cherry-pick

举个例子:dev 分支上的 76d12 提交添加了 index.js 文件,我们需要将本次提交更改加入到 master 分支,那么就可以使用 git cherry-pick 76d12 单独检出这条记录修改。如下图所示:

640 (10).gif

6、git fetch

使用 git fetch 指令将远程分支上的最新的修改下载下来。

640 (11).gif
7、git pull

git pull 指令实际做了两件事:git fetch 和 git merge。

如下图所示:

640 (12).gif
8、git reflog

git reflog 用于显示所有已执行操作的日志!包括合并、重置、还原,也就是记录了对分支的一切更改行为。

640 (13).gif

如果,你不想合并 origin/master 分支了。就需要执行 git reflog 命令,合并之前的仓库状态位于 HEAD@{1} 这个地方,所以我们使用 git reset 指令将 HEAD 头指向 HEAD@{1}就可以了。
640 (14).gif

以上就是民工哥今天给大家带来的分享,如果本文对你有所帮助,请点个在看与转发分享支持一下,感谢大家。我们一起学习,共同进步!!!

原作者:莉迪亚·哈莉(Lydia Hallie)
原文:https://dev.to/lydiahallie/cs...
民工哥通过翻译作者原文再加上一些个人理解总结而成,版权归原作者所有,纯属技术分享,不作为商业目的。
image
查看原文

认证与成就

  • 获得 7 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-03-19
个人主页被 1k 人浏览