Edvard's blog

技术&生活


  • 首页

  • 关于

  • 归档

  • 标签

[译] 使用强大的 Mockito 来测试你的代码

发表于 2016-07-22   |  
  • 原文链接 : Unit tests with Mockito - Tutorial
  • 原文作者 : vogella
  • 译者 : edvardhua
  • 校对者: hackerkevin, futureshine

这篇教程介绍了如何使用 Mockito 框架来给软件写测试用例

1. 预备知识

如果需要往下学习,你需要先理解 Junit 框架中的单元测试。

如果你不熟悉 JUnit,请查看下面的教程:
http://www.vogella.com/tutorials/JUnit/article.html

2. 使用mock对象来进行测试

2.1. 单元测试的目标和挑战

单元测试的思路是在不涉及依赖关系的情况下测试代码(隔离性),所以测试代码与其他类或者系统的关系应该尽量被消除。一个可行的消除方法是替换掉依赖类(测试替换),也就是说我们可以使用替身来替换掉真正的依赖对象。

2.2. 测试类的分类

dummy object 做为参数传递给方法但是绝对不会被使用。譬如说,这种测试类内部的方法不会被调用,或者是用来填充某个方法的参数。

Fake 是真正接口或抽象类的实现体,但给对象内部实现很简单。譬如说,它存在内存中而不是真正的数据库中。(译者注:Fake 实现了真正的逻辑,但它的存在只是为了测试,而不适合于用在产品中。)

stub 类是依赖类的部分方法实现,而这些方法在你测试类和接口的时候会被用到,也就是说 stub 类在测试中会被实例化。stub 类会回应任何外部测试的调用。stub 类有时候还会记录调用的一些信息。

mock object 是指类或者接口的模拟实现,你可以自定义这个对象中某个方法的输出结果。

测试替代技术能够在测试中模拟测试类以外对象。因此你可以验证测试类是否响应正常。譬如说,你可以验证在 Mock 对象的某一个方法是否被调用。这可以确保隔离了外部依赖的干扰只测试测试类。

我们选择 Mock 对象的原因是因为 Mock 对象只需要少量代码的配置。

2.3. Mock 对象的产生

你可以手动创建一个 Mock 对象或者使用 Mock 框架来模拟这些类,Mock 框架允许你在运行时创建 Mock 对象并且定义它的行为。

一个典型的例子是把 Mock 对象模拟成数据的提供者。在正式的生产环境中它会被实现用来连接数据源。但是我们在测试的时候 Mock 对象将会模拟成数据提供者来确保我们的测试环境始终是相同的。

Mock 对象可以被提供来进行测试。因此,我们测试的类应该避免任何外部数据的强依赖。

通过 Mock 对象或者 Mock 框架,我们可以测试代码中期望的行为。譬如说,验证只有某个存在 Mock 对象的方法是否被调用了。

2.4. 使用 Mockito 生成 Mock 对象

Mockito 是一个流行 mock 框架,可以和JUnit结合起来使用。Mockito 允许你创建和配置 mock 对象。使用Mockito可以明显的简化对外部依赖的测试类的开发。

一般使用 Mockito 需要执行下面三步

  • 模拟并替换测试代码中外部依赖。

  • 执行测试代码

  • 验证测试代码是否被正确的执行

mockitousagevisualization

3. 为自己的项目添加 Mockito 依赖

3.1. 在 Gradle 添加 Mockito 依赖

如果你的项目使用 Gradle 构建,将下面代码加入 Gradle 的构建文件中为自己项目添加 Mockito 依赖

repositories { jcenter() }
dependencies { testCompile "org.mockito:mockito-core:2.0.57-beta" }

3.2. 在 Maven 添加 Mockito 依赖

需要在 Maven 声明依赖,您可以在 http://search.maven.org 网站中搜索 g:”org.mockito”, a:”mockito-core” 来得到具体的声明方式。

3.3. 在 Eclipse IDE 使用 Mockito

Eclipse IDE 支持 Gradle 和 Maven 两种构建工具,所以在 Eclipse IDE 添加依赖取决你使用的是哪一个构建工具。

3.4. 以 OSGi 或者 Eclipse 插件形式添加 Mockito 依赖

在 Eclipse RCP 应用依赖通常可以在 p2 update 上得到。Orbit 是一个很好的第三方仓库,我们可以在里面寻找能在 Eclipse 上使用的应用和插件。

Orbit 仓库地址 http://download.eclipse.org/tools/orbit/downloads

orbit p2 mockito

4. 使用Mockito API

4.1. 静态引用

如果在代码中静态引用了org.mockito.Mockito.*;,那你你就可以直接调用静态方法和静态变量而不用创建对象,譬如直接调用 mock() 方法。

4.2. 使用 Mockito 创建和配置 mock 对象

除了上面所说的使用 mock() 静态方法外,Mockito 还支持通过 @Mock 注解的方式来创建 mock 对象。

如果你使用注解,那么必须要实例化 mock 对象。Mockito 在遇到使用注解的字段的时候,会调用MockitoAnnotations.initMocks(this) 来初始化该 mock 对象。另外也可以通过使用@RunWith(MockitoJUnitRunner.class)来达到相同的效果。

通过下面的例子我们可以了解到使用@Mock 的方法和MockitoRule规则。

import static org.mockito.Mockito.*;

public class MockitoTest  {

        @Mock
        MyDatabase databaseMock; (1)

        @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); (2)

        @Test
        public void testQuery()  {
                ClassToTest t  = new ClassToTest(databaseMock); (3)
                boolean check = t.query("* from t"); (4)
                assertTrue(check); (5)
                verify(databaseMock).query("* from t"); (6)
        }
}
  1. 告诉 Mockito 模拟 databaseMock 实例

  2. Mockito 通过 @mock 注解创建 mock 对象

  3. 使用已经创建的mock初始化这个类

  4. 在测试环境下,执行测试类中的代码

  5. 使用断言确保调用的方法返回值为 true

  6. 验证 query 方法是否被 MyDatabase 的 mock 对象调用

4.3. 配置 mock

当我们需要配置某个方法的返回值的时候,Mockito 提供了链式的 API 供我们方便的调用

when(…​.).thenReturn(…​.)可以被用来定义当条件满足时函数的返回值,如果你需要定义多个返回值,可以多次定义。当你多次调用函数的时候,Mockito 会根据你定义的先后顺序来返回返回值。Mocks 还可以根据传入参数的不同来定义不同的返回值。譬如说你的函数可以将anyString 或者 anyInt作为输入参数,然后定义其特定的放回值。

import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

@Test
public void test1()  {
        //  创建 mock
        MyClass test = Mockito.mock(MyClass.class);

        // 自定义 getUniqueId() 的返回值
        when(test.getUniqueId()).thenReturn(43);

        // 在测试中使用mock对象
        assertEquals(test.getUniqueId(), 43);
}

// 返回多个值
@Test
public void testMoreThanOneReturnValue()  {
        Iterator i= mock(Iterator.class);
        when(i.next()).thenReturn("Mockito").thenReturn("rocks");
        String result=i.next()+" "+i.next();
        // 断言
        assertEquals("Mockito rocks", result);
}

// 如何根据输入来返回值
@Test
public void testReturnValueDependentOnMethodParameter()  {
        Comparable c= mock(Comparable.class);
        when(c.compareTo("Mockito")).thenReturn(1);
        when(c.compareTo("Eclipse")).thenReturn(2);
        // 断言
        assertEquals(1,c.compareTo("Mockito"));
}

// 如何让返回值不依赖于输入
@Test
public void testReturnValueInDependentOnMethodParameter()  {
        Comparable c= mock(Comparable.class);
        when(c.compareTo(anyInt())).thenReturn(-1);
        // 断言
        assertEquals(-1 ,c.compareTo(9));
}

// 根据参数类型来返回值
@Test
public void testReturnValueInDependentOnMethodParameter()  {
        Comparable c= mock(Comparable.class);
        when(c.compareTo(isA(Todo.class))).thenReturn(0);
        // 断言
        Todo todo = new Todo(5);
        assertEquals(todo ,c.compareTo(new Todo(1)));
}

对于无返回值的函数,我们可以使用doReturn(…​).when(…​).methodCall来获得类似的效果。例如我们想在调用某些无返回值函数的时候抛出异常,那么可以使用doThrow 方法。如下面代码片段所示

import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

// 下面测试用例描述了如何使用doThrow()方法

@Test(expected=IOException.class)
public void testForIOException() {
        // 创建并配置 mock 对象
        OutputStream mockStream = mock(OutputStream.class);
        doThrow(new IOException()).when(mockStream).close();

        // 使用 mock
        OutputStreamWriter streamWriter= new OutputStreamWriter(mockStream);
        streamWriter.close();
}

4.4. 验证 mock 对象方法是否被调用

Mockito 会跟踪 mock 对象里面所有的方法和变量。所以我们可以用来验证函数在传入特定参数的时候是否被调用。这种方式的测试称行为测试,行为测试并不会检查函数的返回值,而是检查在传入正确参数时候函数是否被调用。

import static org.mockito.Mockito.*;

@Test
public void testVerify()  {
        // 创建并配置 mock 对象
        MyClass test = Mockito.mock(MyClass.class);
        when(test.getUniqueId()).thenReturn(43);

        // 调用mock对象里面的方法并传入参数为12
        test.testing(12);
        test.getUniqueId();
        test.getUniqueId();

        // 查看在传入参数为12的时候方法是否被调用
        verify(test).testing(Matchers.eq(12));

        // 方法是否被调用两次
        verify(test, times(2)).getUniqueId();

        // 其他用来验证函数是否被调用的方法
        verify(mock, never()).someMethod("never called");
        verify(mock, atLeastOnce()).someMethod("called at least once");
        verify(mock, atLeast(2)).someMethod("called at least twice");
        verify(mock, times(5)).someMethod("called five times");
        verify(mock, atMost(3)).someMethod("called at most 3 times");
}

4.5. 使用 Spy 封装 java 对象

@Spy或者spy()方法可以被用来封装 java 对象。被封装后,除非特殊声明(打桩 stub),否则都会真正的调用对象里面的每一个方法

import static org.mockito.Mockito.*;

// Lets mock a LinkedList
List list = new LinkedList();
List spy = spy(list);

// 可用 doReturn() 来打桩
doReturn("foo").when(spy).get(0);

// 下面代码不生效
// 真正的方法会被调用
// 将会抛出 IndexOutOfBoundsException 的异常,因为 List 为空
when(spy.get(0)).thenReturn("foo");

方法verifyNoMoreInteractions()允许你检查没有其他的方法被调用了。

4.6. 使用 @InjectMocks 在 Mockito 中进行依赖注入

我们也可以使用@InjectMocks 注解来创建对象,它会根据类型来注入对象里面的成员方法和变量。假定我们有 ArticleManager 类

public class ArticleManager {
    private User user;
    private ArticleDatabase database;

    ArticleManager(User user) {
     this.user = user;
    }

    void setDatabase(ArticleDatabase database) { }
}

这个类会被 Mockito 构造,而类的成员方法和变量都会被 mock 对象所代替,正如下面的代码片段所示:

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest  {

       @Mock ArticleCalculator calculator;
       @Mock ArticleDatabase database;
       @Most User user;

       @Spy private UserProvider userProvider = new ConsumerUserProvider();

       @InjectMocks private ArticleManager manager; (1)

       @Test public void shouldDoSomething() {
               // 假定 ArticleManager 有一个叫 initialize() 的方法被调用了
               // 使用 ArticleListener 来调用 addListener 方法
               manager.initialize();

               // 验证 addListener 方法被调用
               verify(database).addListener(any(ArticleListener.class));
       }
}
  1. 创建ArticleManager实例并注入Mock对象

更多的详情可以查看
http://docs.mockito.googlecode.com/hg/1.9.5/org/mockito/InjectMocks.html.

4.7. 捕捉参数

ArgumentCaptor类允许我们在verification期间访问方法的参数。得到方法的参数后我们可以使用它进行测试。

import static org.hamcrest.Matchers.hasItem;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

import java.util.Arrays;
import java.util.List;

import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

public class MockitoTests {

        @Rule public MockitoRule rule = MockitoJUnit.rule();

        @Captor
    private ArgumentCaptor> captor;

        @Test
    public final void shouldContainCertainListItem() {
        List asList = Arrays.asList("someElement_test", "someElement");
        final List mockedList = mock(List.class);
        mockedList.addAll(asList);

        verify(mockedList).addAll(captor.capture());
        final List capturedArgument = captor.>getValue();
        assertThat(capturedArgument, hasItem("someElement"));
    }
}

4.8. Mockito的限制

Mockito当然也有一定的限制。而下面三种数据类型则不能够被测试

  • final classes

  • anonymous classes

  • primitive types

5. 在Android中使用Mockito

在 Android 中的 Gradle 构建文件中加入 Mockito 依赖后就可以直接使用 Mockito 了。若想使用 Android Instrumented tests 的话,还需要添加 dexmaker 和 dexmaker-mockito 依赖到 Gradle 的构建文件中。(需要 Mockito 1.9.5版本以上)

dependencies {
    testCompile 'junit:junit:4.12'
    // Mockito unit test 的依赖
    testCompile 'org.mockito:mockito-core:1.+'
    // Mockito Android instrumentation tests 的依赖
    androidTestCompile 'org.mockito:mockito-core:1.+'
    androidTestCompile "com.google.dexmaker:dexmaker:1.2"
    androidTestCompile "com.google.dexmaker:dexmaker-mockito:1.2"
}

6. 实例:使用Mockito写一个Instrumented Unit Test

6.1. 创建一个测试的Android 应用

创建一个包名为com.vogella.android.testing.mockito.contextmock的Android应用,添加一个静态方法
,方法里面创建一个包含参数的Intent,如下代码所示:

public static Intent createQuery(Context context, String query, String value) {
    // 简单起见,重用MainActivity
    Intent i = new Intent(context, MainActivity.class);
    i.putExtra("QUERY", query);
    i.putExtra("VALUE", value);
    return i;
}

6.2. 在app/build.gradle文件中添加Mockito依赖

dependencies {
    // Mockito 和 JUnit 的依赖
    // instrumentation unit tests on the JVM
    androidTestCompile 'junit:junit:4.12'
    androidTestCompile 'org.mockito:mockito-core:2.0.57-beta'
    androidTestCompile 'com.android.support.test:runner:0.3'
    androidTestCompile "com.google.dexmaker:dexmaker:1.2"
    androidTestCompile "com.google.dexmaker:dexmaker-mockito:1.2"

    // Mockito 和 JUnit 的依赖
    // tests on the JVM
    testCompile 'junit:junit:4.12'
    testCompile 'org.mockito:mockito-core:1.+'

}

6.3. 创建测试

使用 Mockito 创建一个单元测试来验证在传递正确 extra data 的情况下,intent 是否被触发。

因此我们需要使用 Mockito 来 mock 一个Context对象,如下代码所示:

package com.vogella.android.testing.mockitocontextmock;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class TextIntentCreation {

    @Test
    public void testIntentShouldBeCreated() {
        Context context = Mockito.mock(Context.class);
        Intent intent = MainActivity.createQuery(context, "query", "value");
        assertNotNull(intent);
        Bundle extras = intent.getExtras();
        assertNotNull(extras);
        assertEquals("query", extras.getString("QUERY"));
        assertEquals("value", extras.getString("VALUE"));
    }
}

7. 实例:使用 Mockito 创建一个 mock 对象

7.1. 目标

创建一个 Api,它可以被 Mockito 来模拟并做一些工作

7.2. 创建一个Twitter API 的例子

实现 TwitterClient类,它内部使用到了 ITweet 的实现。但是ITweet实例很难得到,譬如说他需要启动一个很复杂的服务来得到。

public interface ITweet {

        String getMessage();
}


public class TwitterClient {

        public void sendTweet(ITweet tweet) {
                String message = tweet.getMessage();

                // send the message to Twitter
        }
}

7.3. 模拟 ITweet 的实例

为了能够不启动复杂的服务来得到 ITweet,我们可以使用 Mockito 来模拟得到该实例。

@Test
public void testSendingTweet() {
        TwitterClient twitterClient = new TwitterClient();

        ITweet iTweet = mock(ITweet.class);

        when(iTweet.getMessage()).thenReturn("Using mockito is great");

        twitterClient.sendTweet(iTweet);
}

现在 TwitterClient 可以使用 ITweet 接口的实现,当调用 getMessage() 方法的时候将会打印 “Using Mockito is great” 信息。

7.4. 验证方法调用

确保 getMessage() 方法至少调用一次。

@Test
public void testSendingTweet() {
        TwitterClient twitterClient = new TwitterClient();

        ITweet iTweet = mock(ITweet.class);

        when(iTweet.getMessage()).thenReturn("Using mockito is great");

        twitterClient.sendTweet(iTweet);

        verify(iTweet, atLeastOnce()).getMessage();
}

7.5. 验证

运行测试,查看代码是否测试通过。

8. 模拟静态方法

8.1. 使用 Powermock 来模拟静态方法

因为 Mockito 不能够 mock 静态方法,因此我们可以使用 Powermock。

import java.net.InetAddress;
import java.net.UnknownHostException;

public final class NetworkReader {
    public static String getLocalHostname() {
        String hostname = "";
        try {
            InetAddress addr = InetAddress.getLocalHost();
            // Get hostname
            hostname = addr.getHostName();
        } catch ( UnknownHostException e ) {
        }
        return hostname;
    }
}

我们模拟了 NetworkReader 的依赖,如下代码所示:

import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;

@RunWith( PowerMockRunner.class )
@PrepareForTest( NetworkReader.class )
public class MyTest {

// 测试代码

 @Test
public void testSomething() {
    mockStatic( NetworkUtil.class );
    when( NetworkReader.getLocalHostname() ).andReturn( "localhost" );

    // 与 NetworkReader 协作的测试
}

8.2.用封装的方法代替Powermock

有时候我们可以在静态方法周围包含非静态的方法来达到和 Powermock 同样的效果。

class FooWraper { 
      void someMethod() { 
           Foo.someStaticMethod() 
       } 
}

9. Mockito 参考资料

http://site.mockito.org - Mockito 官网

https://github.com/mockito/mockito- Mockito Github

https://github.com/mockito/mockito/blob/master/doc/release-notes/official.md - Mockito 发行说明

http://martinfowler.com/articles/mocksArentStubs.html 与Mocks,Stub有关的文章

http://chiuki.github.io/advanced-android-espresso/ 高级android教程(竟然是个妹子)

[译] 在 Android 中使用 VectorDrawable

发表于 2016-06-22   |  
  • 原文链接 : Vectors For All (almost)
  • 原文作者 : stylingandroid
  • 译者 : edvardhua
  • 校对者: SatanWoo, zhangzhaoqi

经常阅读 Styling Android 的读者会知道我有多么喜欢用 VectorDrawable 和 AnimatedVectorDrawable 。直到我在写这篇文章之前我还在等待 VectorDrawableCompat (译者注:之前大家以为官方会出兼容 Support ,后来官方使用了另外一种方案,详细内容可戳该链接),所以目前矢量图只能够在 API 21+ (Lollipop) 上面使用。然而,Android Studio 1.4 增加了对旧 android 的兼容,所以,实际上可以在低于 Lollipop 版本的机器上面使用 VectorDrawable 。

在使用之前先来快速回顾一下什么是 VectorDrawable 。 本质上来说它其实就是安卓对 SVG path data 的一层封装。而 SVG paths 是一种以 xml 方式描述复杂图形元素的东西。(译者注:感兴趣的可以阅读 W3C 的官方文档) SVG 很适合用来储存线条和矢量图像,但不适合用来储存摄影图像。通常在Android中 ShapeDrawable 可以实现一些线条和形状的绘画。 但大多数情况我们会将这些矢量图转换成不同像素密度位图来使用。在这篇文章中,我们将会一起来探索如何使用它。

Android Studio 1.4 介绍了如何将SVG图像导入到 Android Studio 然后再自动转换为 VectorDrawable 。这些图标可以来自 material icons pack 或者是单独的 SVG 文件。导入 material icons 的确可以和 VectorDrawable 配合的天衣无缝,同时 google 也提供了大量的 icon 供我们选择。然而,导入单独的 SVG 文件会产生诸多的问题。产生这些问题的主要原因是 VectorDrawable 只支持一部分的 SVG 特性,而像图像渐变,填充和本地 IRI 引用(能够给元素一个唯一的索引,然后在 SVG 内通过索引重用)以及图像的变换等一些我们经常使用的特性都不支持。

举个例子,即使是如官网 LOGO 这样简单的一个SVG图像(如下图所示)都不能导入到 Android 中,因为他使用了本地的IRI引用。

现在还不太清楚 Android 去除这些特性是否是出于性能方面原因,(譬如,渐变效果的渲染会比较复杂一点)或者说是为了以后开发的考虑。

如果你足夠了解 SVG 的格式(这个已经不属于本文的讨论范畴了)我们可以手动的修改图标,然后移除本地 IRI 引用,我们可以对刚才提到的图标可以使用这个方法。

然而仍然不能够导入到 Android ,因为会抛出 “premature end of file” 的错误信息,并且会指出出现问题的那行。感谢来自Wojtek Kaliciński 的建议,我将 SVG 中 width 和 height 的值从百分比改成绝对值之后就可以导入到 Android 中去了。但是因为水平和竖直移动(Translations)特性不支持,导致所有的元素摆放位置不好。

通过手动将所有的平移(translation)和旋转(rotation)变换从原来的 SVG 格式转换到 Android 中所支持的格式后(在<group></group>包含<path></path>来支持变换),我终于能够将 SVG 图标导入,并使用 VectorDrawable 将其在 Marshmallow 上正确渲染。

使用 Juraj Novák 所开发的 svg2android 工具可以方便的将 SVG 转换成 VectorDrawable 。该工具也有限制,那就是不能处理渐变和本地 IRI 引用的问题。但是可以省去我们手动微调的工作还是很不错的,像我刚才提到的宽高的问题,使用该工具转换则没有抛出错误。该工具开启实验性模式后还支持图像的变换(Transformation)并且支持的很好。但是对于本地的IRI引用还是需要我们手动的去修改原生的 SVG 文件。

将转换后的文件放到 res/drawable 文件夹后,我们可以直接当成 drawable 来引用,如下代码所示:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:paddingBottom="@dimen/activity_vertical_margin"
  android:paddingLeft="@dimen/activity_horizontal_margin"
  android:paddingRight="@dimen/activity_horizontal_margin"
  android:paddingTop="@dimen/activity_vertical_margin"
  tools:context=".MainActivity">

  <ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:contentDescription="@null"
    android:src="@drawable/svg_logo2" />
</RelativeLayout>

假如我们使用的 gradle plugin 版本是 1.4.0 或者更高的话(截至到我写这篇文章之前,还未正式发布1.4.0,但是 1.4.0-beta6 已经可以实现这个效果了),那么他将适配到 Android API 1。

那么,适配低版本的原因是什么呢?让我们来看一下Build文件夹里面所生成的代码,答案就已经很明显了。

Screen Shot 2015-10-03 at 15.20.33

针对于 API 21 或者更高版本的设备,我们导入的矢量 XML drawable 文件将会被使用,但是对于早起的版本,我们则使用 PNG 代替矢量的 drawable 。

但是如果我们出于 apk 大小的考虑,并不想生成多个 PNG 文件来适配多个分辨率呢?我们可以通过设置 generatedDensities 的值来决定需要生成 PNG 的分辨率和数量。

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.stylingandroid.vectors4all"
        minSdkVersion 7
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
        generatedDensities = ['mdpi', 'hdpi']
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.0.1'
}

如果我们现在进行 build ,我们会发现它(记住,在 build 之前先清除上一次 build 生成的 PNG 文件)只生成了我们指定的分辨率大小的 PNG 文件。

Screen Shot 2015-10-03 at 15.27.08

所以,现在来看一下这些 PNG 图片里面的内容是什么:

这些图片本质上跟我之前未修改的 SVG 图像的内容是相同的。我需要提醒大家,这里会抛出警告信息,告诉我们 <group></group> 元素不支持生成栅格化的图片格式。但是这并不能消除 VectorDrawable 是 Android 中特有的格式,而且该格式不支持上述特性,这让我们感到很困惑的事实。

我们现在开始了解为什么图像的变换特性在导入工具中不被支持了,因为 VectorDrawable 中的 <group></group> 元素不支持导出栅格化的图片格式,从而导致不能够向前兼容的问题。这个看上去是一个重大的疏忽:因为在 Lollipop 上面渲染正常的 VectorDrawable 资源,在将他们转换成 PNG 后则不能够正确的渲染。

总结:如果使用这些新的工具从 material icons 库导入资源,那么它们则能够完美无瑕的渲染。但是,我们需要注意的是它们只具备导入 SVG 的能力以及只支持 SVG 一部分特性的转换,所以这导致了它们不能够导入现实世界中大部分的 SVG 文件。此外,对于新工具中将 VectorDrawable 转换成 PNG 来适配低版本的机器的时候是不支持像素图转换的,这让我们觉得新工具的功能还没有完成还不能够拿来使用。

其实这种层次的手动微调花不了太多的时间(譬如修改官方 logo ),尤其我们是先用转换工具将它们转换成 VectorDrawable 后。尽管我仍然需要手动的修改图像变换所涉及到的全部坐标,也就是 SVG pathData 的元素内容。

让我们期望这些问题在新的工具上会得到解决。这样这些有潜力的新工具才能够开始达到它们原来的目标。

这篇文章的源代码可以在这里找到。

[译] 怎样用 JRebel for Android

发表于 2016-06-02   |  
  • 原文链接 : Getting started with JRebel for Android
  • 原文作者 : Oleg Šelajev
  • 译者 : edvardhua
  • 校对者: DeadLion, circlelove

只要你的项目相对较小,开发Android应用的用户体验还是很棒的。然而随着项目功能的增加,你会发现构建项目的时间也会随着增长。这种情况会导致你的大部分时间都花在如何更快的构建项目,而不是为应用增加更多的价值。

网上有很多教你如何加快Gradle构建速度的教程。有一些很好的文章,譬如“Making Gradle builds faster”。 通过这些方法我们可以节省几秒甚至几分钟的构建时间,但是仍然存在一些构建上的瓶颈。举个例子,基于注释的依赖注入使得项目架构清晰,但是这对项目构建时间是有很大影响的。

但是你可以尝试一下使用JRebel for Android。每次改动代码后不需要重新安装新的 apk。而是在安装完一次应用后,通过增量包传递到设备或者模拟器上,并且能够在应用运行时进行更新。这个想法(热部署)已经在JRebel的java开发工具上面使用超过8年的时间。

拿Google IO 2015 app来看看如何使用JRebel for Android,以及它能为我们节省多少宝贵的时间。

安装 JRebel for Android

JRebel for Android 是一个Android Studio的插件,你可以直接点击IDE的 Plugins > Browse Repositories 键入“JRebel for Android”来搜索和安装插件。

如果因为某些原因你无法访问 maven 的公有仓库,你可以直接在 JetBrians 官网下载,然后通过 Plugins > Install plugin from disk… 来安装插件。

当你安装完插件后,你需要重启Android Studio,在重启之后,你需要提供你的姓名和邮箱来得到JRebel for Android的21天免费使用。

用 JRebel for Android 来运行你的应用程序

安装完插件后,只需要点击 Run with JRebel for Android 按钮,它会检测这次代码与上次是否有改动,然后决定是否构建一个新的apk。Run with JRebel for Android 其实和Android Studio中的 Run 操作是一样的。所以有同样的运行流程,首先需要你选择一个设备,然后再构建apk安装到那台设备上去。

为了更新代码和资源,JRebel for Android 需要处理项目 classes,并嵌入一个代理应用。JRebel for Android只会运行在调试模式下,所以对于正式发布的版本来说是没有影响的。另外,使用该插件也不需要你在项目中做任何改动。想要知道更多JRebel for Android的细节,请看under the hood post。(译者注:InfoQ的一篇介绍JRebel for Android的文章写的不错。)

所以在Google IO 2015应用上点击 Run with JRebel for Android 将会得到如下的结果:

在JRebel for Android应用代码修改

Apply changes 按钮是使用 JRebel for Android的关键,它将会做最少的工作来将你代码的改动更新到你的设备上去。如果你没有使用 Run with JRebel for Android 来部署应用的话,Apply changes 将会帮你做这部分的工作。

现在让我们在应用上做一个简单的功能改动。针对于GoogleIO中每一个举行的子会场你都可以发送反馈问卷,我们给这个问卷添加多一个输入框输入你的姓名,当你完成反馈的时候会弹出Toast来感谢你的反馈。

步骤一: 在 _session_feedbackfragment.xml 中添加一个EditTex组件。

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <EditText
        android:id="@+id/name_input"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</FrameLayout>

步骤2: 调整间距

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingLeft="@dimen/padding_normal"
    android:paddingStart="@dimen/padding_normal"
    android:paddingRight="@dimen/padding_normal"
    android:paddingEnd="@dimen/padding_normal"
    android:paddingTop="@dimen/spacing_micro"
    android:paddingBottom="@dimen/padding_normal">

步骤3: 添加提示

<EditText
    android:id="@+id/name_input"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="@string/name_hint"/>

这些改动现在都是在同一个页面上,每一次按下 Apply change 按钮后,JRebel for Android都会调用Activity.recreate()。在最顶部的activity将会同样的回调方法,就像设备从纵向切换到横向那样。

到目前为止我们都还只是改动resource文件,下面我们来改动Java代码。

步骤4: 在 SessionFeedbackFragment.sumbitFeedback() 方法中弹出Toast

EditText nameInput = (EditText) 

getView().findViewById(R.id.name_input);

Toast.makeText(getActivity(), "Thanks for the feedback " + 

nameInput.getEditableText().toString(), Toast.LENGTH_SHORT).show();

应用重启动 vs Activity重启动

并不是所有的改动都会触发调用Activity.recreate()的。如果你在AndroidManifest改动了一些内容,一个新的 apk 将会被构建并增加安装。在这种情况下,应用将会重新启动。或者你替换或改动了已经被实现的superclass或者interfaces的时候也会导致应用重启动。下面有一份完整的对照表:

为什么我要尝试使用 JRebel for Android

下面我列出了最有说服力的理由,来让你使用它。

  • 可以快速看到自己代码改动的效果。
  • 可以有时间打磨素完美的UI,而不用浪费时间在构建上。
  • 不需要在项目中做任何改动来支持 JRebel for Android。
  • 在调试程序的同时还能更新代码和资源文件。没错,JRebel for Android支持调试器的全部特性。

译者注

另外,Google 在前不久发布的 Android Studio 2.0 中也增加了类似 JRebel 功能,称之为 Instant Run。可谓良心,毕竟不用花银子去购买收费的 JRebel for Android, 不仅为我们节约了银子还节约了时间。使用方法也很简单,具体的可以查看官方文档

但是 JRebel for Android 与 Instant Run 之间还是存在区别的,我在 JRebel 的官网中找到一篇对比的文章,他们之间的差别可以用下面这张图概括。

JRebel for Android vs Instant Run

从对比图中了解到其实 JRebel for Android 所支持的特性是比 Instant Run 多的多的,当然,希望 Google 后续能够让 Instant Run 支持更多的特性。

[译] 我是如何为Mac应用Flinto设计UI图标的

发表于 2016-05-22   |  
  • 原文链接: Designing the Icons for Flinto’s UI
  • 原文作者 : Peter Nowell
  • 译者 : edvardhua
  • 校对者 : Ruixi, CoderBOBO

Flinto 团队 最近采访了我关于Flinto用户界面图标背后的设计流程。

你是怎么为Mac版本的Flinto贡献自己的设计?

我参与了Flinto新的Mac版本的用户界面部分和用户体验部分的设计,就在他发布之前的几个月。但是因为Flinto是一个特殊的工具,我们越是深入思考每一部分的用户体验,我们越感觉到Flinto需要大量的定制icons。譬如说Flinto应用的列表(List),工具栏(Toolbar),动画的设计面板(Transition Designer),下拉手势(Gestures dropdown)都需要他们独自的一套图标。所以,如何快速的设计图标变成了我的主要工作。

当设计大型应用的图标和菜单的时候,你采用什么样的设计策略?

设计总是情境驱动的。我惊奇的发现专业Mac应用的情境设计是最复杂的工作之一,就算你只是设计图标。工具栏(Toolbar)的图标大小必须一致,而且最好根据图标知道用途。这与侧边栏的图标和下拉菜单看到的图标的设计原则上有所差异。 一些图标会以不同的尺寸和不同的样式重复出现在不同的地方。不是只要调整图标尺寸或者样式就能够适用于每一个用户界面的,所以我在设计图标的时候需要考虑到图标是否具有通用性以及不破坏用户界面的整体一致性。

我都是在纸上开始设计图标的,我一直坚信这个原则。我会在纸上画下我想象中这个图标的所有可能性,譬如设计的这个图标包含了什么暗喻以及图标可能需要/产生的变化。所以在概念设计的这个阶段上,我尽量让自己将所有的内容都写在我的纸上,甚至是一些不相关的想法。下一步则分析概念设计中的内容如何能够更好符合我们设计的目标,已有的限制以及这个图标的情境联系。

我发现将绘图构思和评估这两个过程分开进行是很至关重要的。前者的工作需要想象力,好奇心,而且持有自己主观的判断,是一个加法的过程,是心血来潮的创作。评估则需要批判性,实用性,以及需要考虑图标背后一连串所延伸出来的隐喻,是一个做减法的过程。如果你尝试同时做这两件事情,那么你会考虑不过来从而得不到任何结果。

我最近还在网上授课讲述我认为在设计图标中最重要的原则。里面还包含了我是如何来评估我的想法和草稿的。

通常来说,只有一部分的设计想法会被保存到电脑里面。使用sketch可以提高我的生产效率并且在生产的过程中会有一些创造性的决定。但最主要的目的还是要完善和精炼图标的形式,保证每一个图标都是像素完美的。我对此具备相当大的热情,我对其他忽视这个细节的人感到很烦恼。

能够为我们再稍微解释一下什么是“像素完美”和如何实现?

像素完美其实意味着很多东西,它更像是一个想法而不是一个能够具体描述的特征。像“注意细节”一样,当被忽视的时候我们能够很容易的感觉出来。完美的像素对小图标的可辨别度有巨大的影响。想要实现像素完美不仅仅是将设计元素的像素网格对齐(如下图)。这基本上来说就是在和锯齿做斗争。使用抗锯齿是很好的一件事情,但它会让图像一些地方产生模糊,尤其是在对角线和曲线中。

举个例子,我们想在图层列表中加上一些注释来表明哪些层是被隐藏或者是被锁住的。当然给图层加上隐藏和锁定是很简单的事情,只需要点击按钮操作一下就好了。我们考虑的是我们有一个小的注释,他会占用一小部分空间,来注释两个已经隐藏和锁定图标。为了完成这个目标,我们的图标必须要做到像素完美。我对我设计的8x8大小的图标感到非常的自豪。

对于使用retina显示屏的读者,我们显示“一半尺寸”的位图,如图1x的全像素图标。对于非retina显示屏的读者,则使用“双倍尺寸”的位图,如图2x的全像素图标。 请以横向模式显示上图来获得最好的显示效果。

在一个理想的世界中,一枚制作精良矢量图标可以轻易地适应各种像素密度的输出,并在所有对应尺寸中显示效果良好。但是大部分时候,使用一倍大小的图标并不能够处理得到更高尺寸的图标。你可能需要先做一个完美的两倍尺寸的图标,然后再调整成一倍尺寸来创建一个新的视觉满意的图标。在Flinto中至少一半的图标都有其对应1倍和2倍尺寸,譬如贯穿整个过渡动画设计面板的”概念图层”图标。

针对于这块感兴趣的读者,这里有我是如何对Flinto图标的抗锯齿进行细调的技术细节。

  • 重新调整和重新定位图形来获得看上去视觉舒服的图形,尽管这样处理后位置或者像素值会有小数点,但在这个阶段视觉是重点。
  • 只使用曲线或者圆角时,至少要使用2px来渲染 90° 角的半径圆,或者使用3px渲染180°角的半径圆,来作为圆的线段末尾(如下图)。1pt大小线的线段的圆角线帽的效果是很糟糕的,至少我们使用的屏幕都会将其放大三倍来显示。

没有人想要模糊的线帽!只有三倍大小(或者更大)的显示器才能够对1pt大小的线条渲染清晰可见的圆角线帽。

  • 为了让线条粗细更加一致,调整边框宽度/粗细来达到稍宽或者稍厚会比使用1pt的细曲线或者斜线更好。
  • 消除不必要的模糊像素。这在你需要使用图形自身标记自己的时候将会很有效。
  • 通过复制图形或者边框(同一方向)来轻微调整图形的粗细。
  • 如果图标可以有小模糊锯齿能够为图标的其他部分提供一定帮助,这也是可以的。

当然还有其他有关于如何平滑抗锯齿的技巧,但是我刚才所说的是我从中获益最大的。

什么造就了一枚好图标?

这是个问题!尤其是当图标包含了很多设计原则的时候。我在我的图标设计课程里面通过讲我在Flinto工作遇到的一些故事来描述我是如何造就一枚好图标的。

其中的一个原则就是使用熟悉的符号并且让他显眼。当我们开始为Flinto的主页面的画布设计图标的时候,内森有一个想法,我们可以设计一个图标让我们回忆起艾西勒的住宅。艾西勒是一位建筑师,他设计了中世纪现代建筑的住宅,这种风格的住宅在加州很流行。

艾西勒的中世纪现代建筑的住宅给了我们灵感去探索设计一个独特的“home”图标。

我们认为这个想法很酷而且内森也买了一套使用这种设计元素的房子,所以我们对这个想法很有热情。我做了很多个home图标的概念设计,尝试着将艾西勒住宅的特点萃取到一个16*16正方形的图标里面,而且在图标不添加色彩和透明度效果。我们发现这些看似巧妙的图标并没有很好展现图标本身的职责而且作为home图标也不够显眼。于是我们决定做一个直观并且能够表达艾西勒住宅不对称特点而且对其他用户而言有高辨别度的图标。

直观胜过巧妙,我们选择了底部中间图标作为home键。

另外评价一个好图标的原则是他是否能够与周围的元素看上去融洽。这些元素包括图标周围的UI,邻接文本的大小和 权重,操作系统的约定(譬如说,在mac os下cmd+s是保存,而在win下则是ctrl + s),以及其他图标的集合。

所以尽管home图标基本上是单独存在的,但是工具图标,手势图标,排版图标都是集合方式存在的。设计图标的集合的挑战是很大的。你会在设计一个图标集合设计到一半的时候发现你所使用的视觉隐喻不能够适应每一个这个集合里面需要的图标,这意味着你需要重新做一遍。 🙈

这种情况通常发生在手势图标上(上图是以200%比例显示)现在这些图标看上去很简单和直观,然而我们在设计他时是有很多限制条件的,并且还要考虑未来的兼容性。一些我们在这里展示的图标还没有出现在Flinto中…但很重要的一点是,在遇到有需要的时候,我们设计的图标集能够扩展并且容纳它们。

[译] 在Android中使用并发提高应用性能

发表于 2016-03-03   |  
  • 原文链接 : Getting started with JRebel for Android
  • 原文作者 : Oleg Šelajev
  • 译者 : edvardhua
  • 校对者: DeadLion, circlelove

只要你的项目相对较小,开发Android应用的用户体验还是很棒的。然而随着项目功能的增加,你会发现构建项目的时间也会随着增长。这种情况会导致你的大部分时间都花在如何更快的构建项目,而不是为应用增加更多的价值。

网上有很多教你如何加快Gradle构建速度的教程。有一些很好的文章,譬如“Making Gradle builds faster”。 通过这些方法我们可以节省几秒甚至几分钟的构建时间,但是仍然存在一些构建上的瓶颈。举个例子,基于注释的依赖注入使得项目架构清晰,但是这对项目构建时间是有很大影响的。

但是你可以尝试一下使用JRebel for Android。每次改动代码后不需要重新安装新的 apk。而是在安装完一次应用后,通过增量包传递到设备或者模拟器上,并且能够在应用运行时进行更新。这个想法(热部署)已经在JRebel的java开发工具上面使用超过8年的时间。

拿Google IO 2015 app来看看如何使用JRebel for Android,以及它能为我们节省多少宝贵的时间。

安装 JRebel for Android

JRebel for Android 是一个Android Studio的插件,你可以直接点击IDE的 Plugins > Browse Repositories 键入“JRebel for Android”来搜索和安装插件。

如果因为某些原因你无法访问 maven 的公有仓库,你可以直接在 JetBrians 官网下载,然后通过 Plugins > Install plugin from disk… 来安装插件。

当你安装完插件后,你需要重启Android Studio,在重启之后,你需要提供你的姓名和邮箱来得到JRebel for Android的21天免费使用。

用 JRebel for Android 来运行你的应用程序

安装完插件后,只需要点击 Run with JRebel for Android 按钮,它会检测这次代码与上次是否有改动,然后决定是否构建一个新的apk。Run with JRebel for Android 其实和Android Studio中的 Run 操作是一样的。所以有同样的运行流程,首先需要你选择一个设备,然后再构建apk安装到那台设备上去。

为了更新代码和资源,JRebel for Android 需要处理项目 classes,并嵌入一个代理应用。JRebel for Android只会运行在调试模式下,所以对于正式发布的版本来说是没有影响的。另外,使用该插件也不需要你在项目中做任何改动。想要知道更多JRebel for Android的细节,请看under the hood post。(译者注:InfoQ的一篇介绍JRebel for Android的文章写的不错。)

所以在Google IO 2015应用上点击 Run with JRebel for Android 将会得到如下的结果:

在JRebel for Android应用代码修改

Apply changes 按钮是使用 JRebel for Android的关键,它将会做最少的工作来将你代码的改动更新到你的设备上去。如果你没有使用 Run with JRebel for Android 来部署应用的话,Apply changes 将会帮你做这部分的工作。

现在让我们在应用上做一个简单的功能改动。针对于GoogleIO中每一个举行的子会场你都可以发送反馈问卷,我们给这个问卷添加多一个输入框输入你的姓名,当你完成反馈的时候会弹出Toast来感谢你的反馈。

步骤一: 在 _session_feedbackfragment.xml 中添加一个EditTex组件。

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <EditText
        android:id="@+id/name_input"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</FrameLayout>

步骤2: 调整间距

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingLeft="@dimen/padding_normal"
    android:paddingStart="@dimen/padding_normal"
    android:paddingRight="@dimen/padding_normal"
    android:paddingEnd="@dimen/padding_normal"
    android:paddingTop="@dimen/spacing_micro"
    android:paddingBottom="@dimen/padding_normal">

步骤3: 添加提示

<EditText
    android:id="@+id/name_input"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="@string/name_hint"/>

这些改动现在都是在同一个页面上,每一次按下 Apply change 按钮后,JRebel for Android都会调用Activity.recreate()。在最顶部的activity将会同样的回调方法,就像设备从纵向切换到横向那样。

到目前为止我们都还只是改动resource文件,下面我们来改动Java代码。

步骤4: 在 SessionFeedbackFragment.sumbitFeedback() 方法中弹出Toast

EditText nameInput = (EditText) 

getView().findViewById(R.id.name_input);

Toast.makeText(getActivity(), "Thanks for the feedback " + 

nameInput.getEditableText().toString(), Toast.LENGTH_SHORT).show();

应用重启动 vs Activity重启动

并不是所有的改动都会触发调用Activity.recreate()的。如果你在AndroidManifest改动了一些内容,一个新的 apk 将会被构建并增加安装。在这种情况下,应用将会重新启动。或者你替换或改动了已经被实现的superclass或者interfaces的时候也会导致应用重启动。下面有一份完整的对照表:

为什么我要尝试使用 JRebel for Android

下面我列出了最有说服力的理由,来让你使用它。

  • 可以快速看到自己代码改动的效果。
  • 可以有时间打磨素完美的UI,而不用浪费时间在构建上。
  • 不需要在项目中做任何改动来支持 JRebel for Android。
  • 在调试程序的同时还能更新代码和资源文件。没错,JRebel for Android支持调试器的全部特性。

译者注

另外,Google 在前不久发布的 Android Studio 2.0 中也增加了类似 JRebel 功能,称之为 Instant Run。可谓良心,毕竟不用花银子去购买收费的 JRebel for Android, 不仅为我们节约了银子还节约了时间。使用方法也很简单,具体的可以查看官方文档

但是 JRebel for Android 与 Instant Run 之间还是存在区别的,我在 JRebel 的官网中找到一篇对比的文章,他们之间的差别可以用下面这张图概括。

JRebel for Android vs Instant Run

从对比图中了解到其实 JRebel for Android 所支持的特性是比 Instant Run 多的多的,当然,希望 Google 后续能够让 Instant Run 支持更多的特性。

Docker安装与入门

发表于 2016-02-03   |  

Docker安装与入门


官方教程的翻译简化版,环境为Ubuntu14.04


安装Docker

打开终端,如果没有wget,键入下面命令安装

$ sudo apt-get update
$ sudo apt-get install wget

随后,键入下面命令安装Docker,最好加上sudo,避免文件权限问题

$ sudo wget -qO- https://get.docker.com/ | sh

然后,键入

$ docker run hello-world

验证安装是否成功。但是我这里遇到问题,提示

`Cannot connect to the Docker daemon. Is the docker daemon running on this host?`

谷歌后,原因是Docker用户和当前用户不在一个组的原因,键入下面命令修改

$ sudo usermod -aG docker <your username>

随后,重启系统


运行whaleasy image

首先,需要在DockerHub注册一个账号,

随后在搜索栏键入docker/whaleasy,可以看到搜索结果。
接下来键入下面命令

$ docker run docker/whalesay cowsay boo

将DockerHub上的whaleasy下载下来在本机运行。

运行结果

创建自己的Image

创建Dockerfile文件

选择一个目录,键入下面命令

$ mkdir mydockerbuild
$ cd mydockerbuild
$ touch Dockerfile 

用一个文本编辑器打开Dockerfile,填入下面的内容

FROM docker/whalesay:latest
RUN apt-get -y update && apt-get install -y fortunes
CMD /usr/games/fortune -a | cowsay

其中,FROM命令表示你的基image,RUN表示你要为自己的image安装啥程序,在这里安装了一个fortune,这个东西可以让命令行的鲸鱼讲话,安装完成后。用CMD命令让fortune给一句俏皮的话给cowsay程序执行。

用Dockerfile创建Image

在当前路径打开终端,键入命令

$ docker build -t docker-whale .

特别要注意不要忘记键入最后的.

运行自己的Image

很简单,键入

$ docker run docker-whale

待出现Successfully built之后,便创建了属于自己的Image,可以通过键入

$ docker images

来查看本机上存在多少个images,其中有一个便是docker-whale。


Push Image 至 DockerHub

这里Push刚才创建的docker-whale
首先需要在DockerHub创建一个Repository,并设置为公开,这个跟使用GitHub有点类似。
记住刚才创建的Repository Name,通过在终端键入docker iamges来得到docker-whale的IMAGE ID。
随后用docker tag来给你的docker-whale打上tag,键入如下命令

$ docker tag <IMAGE ID> <DockerHub username>/<Repository Name>:latest

接下来,再次键入docker images可以看到本地新增了一个REPOSITORY,便是我们刚才创建的。最后在终端键入下面两条命令登录并Push到DockerHub上

$ docker login --username=yourhubusername --email=youremail@company.com
$ docker push <DockerHub username>/<Repository Name>

对应的,Pull Docker Image的命令如下

$ docker pull <Repository Name>/<Repository Name>

如果要删除本地的Image,可以使用下面的命令

$ docker rmi -f <IMAGE ID>
Edvard

Edvard

Edvard的博客

6 日志
0 分类
4 标签
github zhihu
© 2016 Edvard
由 Hexo 强力驱动
主题 - NexT.Pisces