⒈是什么?

CacheManager差不多,两者的定位和功能都差不多。

EasyCaching主要提供了下面的几个功能

  1. 统一的抽象缓存接口
  2. 多种常用的缓存Provider(InMemory,Redis,Memcached,SQLite)
  3. 为分布式缓存的数据序列化提供了多种选择
  4. 二级缓存
  5. 缓存的AOP操作(able, put,evict)
  6. 多实例支持
  7. 支持Diagnostics
  8. Redis的特殊Provider

⒉示例(以InMemory为例)

  1.安装Nuget包

    EasyCaching.InMemory

  2.在Startup中配置服务及请求管道

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EasyCaching.Core;
using EasyCaching.InMemory;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; namespace Coreqi.EasyCaching
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddEasyCaching(option =>
{
// 使用InMemory最简单的配置
option.UseInMemory("default"); //// 使用InMemory自定义的配置
//option.UseInMemory(options =>
//{
// // DBConfig这个是每种Provider的特有配置
// options.DBConfig = new InMemoryCachingOptions
// {
// // InMemory的过期扫描频率,默认值是60秒
// ExpirationScanFrequency = 60,
// // InMemory的最大缓存数量, 默认值是10000
// SizeLimit = 100
// };
// // 预防缓存在同一时间全部失效,可以为每个key的过期时间添加一个随机的秒数,默认值是120秒
// options.MaxRdSecond = 120;
// // 是否开启日志,默认值是false
// options.EnableLogging = false;
// // 互斥锁的存活时间, 默认值是5000毫秒
// options.LockMs = 5000;
// // 没有获取到互斥锁时的休眠时间,默认值是300毫秒
// options.SleepMs = 300;
// }, "m2"); //// 读取配置文件
//option.UseInMemory(Configuration, "m3");
}); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
} app.UseStaticFiles();
app.UseCookiePolicy(); // 如果使用的是Memcached或SQLite,还需要下面这个做一些初始化的操作
app.UseEasyCaching(); app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}

  3.创建一个实体类

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; namespace Coreqi.EasyCaching.Models
{
[Serializable]
public class User
{
public int id { get; set; }
public string username { get; set; }
public string password { get; set; }
public int enabled { get; set; }
}
}

  4.模拟一个服务层

 using Coreqi.EasyCaching.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; namespace Coreqi.EasyCaching.Services
{
public class UserService:IUserService
{
public static IList<User> users;
public UserService()
{
users = new List<User>
{
new User{ id = ,username = "fanqi",password = "admin",enabled = },
new User{ id = ,username = "gaoxing",password = "admin",enabled = }
};
}
public IList<User> getAll()
{
return users;
}
public User getById(int id)
{
return users.FirstOrDefault(f => f.id == id);
}
public User add(User user)
{
users.Add(user);
return user;
}
public User modify(User user)
{
delete(user.id);
add(user);
return user;
}
public void delete(int id)
{
users.Remove(getById(id));
}
}
}

  5.控制器中使用缓存

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Coreqi.EasyCaching.Models;
using Coreqi.EasyCaching.Services;
using EasyCaching.Core;
using Microsoft.AspNetCore.Mvc; namespace Coreqi.EasyCaching.Controllers
{
[Route("api/[controller]")]
public class UserController : Controller
{
private readonly IEasyCachingProvider _cache;
private readonly IUserService _service;
public UserController(IEasyCachingProvider cache, IUserService service)
{
this._cache = cache;
this._service = service;
} [HttpGet]
[Route("add")]
public async Task<IActionResult> Add()
{
IList<User> users = _service.getAll();
_cache.Set("users", users, TimeSpan.FromMinutes());
await _cache.SetAsync("users2", users, TimeSpan.FromMinutes());
return await Task.FromResult(new JsonResult(new { message = "添加成功!" }));
} [HttpGet]
[Route("remove")]
public async Task<IActionResult> Remove()
{
_cache.Remove("users");
await _cache.RemoveAsync("users2");
return await Task.FromResult(new JsonResult(new { message = "删除成功!" }));
} [HttpGet]
[Route("get")]
public async Task<IActionResult> Get()
{
var users = _cache.Get<List<User>>("users");
var users2 = await _cache.GetAsync<List<User>>("users2");
return await Task.FromResult(new JsonResult(new { users1 = users.Value,users2 = users2.Value }));
}
}
}

⒊改造为Redis示例

  1.安装Nuget包

    EasyCaching.Redis

  2.在Startup中配置服务及请求管道

         // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IUserService, UserService>();
services.AddEasyCaching(option =>
{
//使用redis
option.UseRedis(config =>
{
config.DBConfig.Endpoints.Add(new ServerEndPoint("127.0.0.1", ));
}, "localhostRedis");
}); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

  3.启动程序

    然后就发现数据缓存到Redis中了,不过,

对于序列化,一般都会有一个基于BinaryFormatter的默认实现,因为这个并不依赖于第三方类库,如果我们没有指定其它的,EasyCaching就会使用这个去进行数据的序列化,除了这个默认的实现,EasyCaching还提供了三种额外的选择。Newtonsoft.Json,MessagePack和Protobuf。切换方法如下:

    1.安装以下Nuget包(你用那个序列化就装那个)

 EasyCaching.Serialization.Json
EasyCaching.Serialization.MessagePack
EasyCaching.Serialization.Protobuf

    2.配置序列化方式

         // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IUserService, UserService>();
services.AddEasyCaching(option =>
{
//使用redis
option.UseRedis(config =>
{
config.DBConfig.Endpoints.Add(new ServerEndPoint("127.0.0.1", ));
}, "localhostRedis")
//.WithMessagePack(); //使用MessagePack替换BinaryFormatter
//.WithProtobuf(); //使用Protobuf替换BinaryFormatter
.WithJson(); //使用Newtonsoft.Json替换BinaryFormatter
}); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

⒋多示例支持

  多实例指在同一个项目中同时使用多个缓存提供者,包括多个同一类型的缓存提供者或着是不同类型的缓存提供者。

  在代码中借助IEasyCachingProviderFactory来指定使用那个缓存提供者。

  1.先添加两个缓存提供者

         // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IUserService, UserService>();
services.AddEasyCaching(option =>
{
option.UseInMemory("m1"); //配置一个InMemory,名称为m1
//使用redis
option.UseRedis(config =>
{
config.DBConfig.Endpoints.Add(new ServerEndPoint("127.0.0.1", ));
}, "localhostRedis")
//.WithMessagePack(); //使用MessagePack替换BinaryFormatter
//.WithProtobuf(); //使用Protobuf替换BinaryFormatter
.WithJson(); //使用Newtonsoft.Json替换BinaryFormatter
}); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

  2.在代码中使用

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Coreqi.EasyCaching.Models;
using Coreqi.EasyCaching.Services;
using EasyCaching.Core;
using Microsoft.AspNetCore.Mvc; namespace Coreqi.EasyCaching.Controllers
{
[Route("api/[controller]")]
public class LoginController : Controller
{
private readonly IEasyCachingProviderFactory _cacheFactory;
private readonly IUserService _userService;
public LoginController(IEasyCachingProviderFactory cacheFactory, IUserService userService)
{
this._cacheFactory = cacheFactory;
this._userService = userService;
} [HttpGet]
[Route("add")]
public async Task<IActionResult> Add()
{
var _cache1 = _cacheFactory.GetCachingProvider("m1"); //获取名字为m1的provider
var _cache2 = _cacheFactory.GetCachingProvider("localhostRedis"); //获取名字为localhostRedis的provider
IList<User> users = _userService.getAll();
IList<string> loginNames = users.Select(s => s.username).ToList();
_cache1.Set("loginNames", loginNames, TimeSpan.FromMinutes());
await _cache2.SetAsync("users", users, TimeSpan.FromMinutes());
return await Task.FromResult(new JsonResult(new { message = "添加成功!" }));
} [HttpGet]
[Route("get")]
public async Task<IActionResult> Get()
{
var _cache1 = _cacheFactory.GetCachingProvider("m1"); //获取名字为m1的provider
var _cache2 = _cacheFactory.GetCachingProvider("localhostRedis"); //获取名字为localhostRedis的provider
IList<string> loginNames = _cache1.Get<List<string>>("loginNames").Value;
IList<User> users = (await _cache2.GetAsync<List<User>>("users")).Value;
return await Task.FromResult(new JsonResult(new { loginNames = loginNames,users = users}));
}
}
}

⒌缓存的Aop操作

  一句话,提供和Java Spring中的@Cacheable、@CacheEvict、@CachePut等注解类似的Aop操作

  例如,我们以前的查询代码一般是这样的

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Coreqi.EasyCaching.Models;
using Coreqi.EasyCaching.Services;
using EasyCaching.Core;
using Microsoft.AspNetCore.Mvc; namespace Coreqi.EasyCaching.Controllers
{
[Route("api/[controller]")]
public class CacheAopController : Controller
{
private readonly IEasyCachingProvider _cache;
private readonly IUserService _service;
public CacheAopController(IEasyCachingProvider cache, IUserService service)
{
this._cache = cache;
this._service = service;
} [HttpGet]
[Route("get/{id}")]
public async Task<User> GetUserByIdAsync(int id)
{
string cacheKey = $"user:{id}";
var userInfo = await _cache.GetAsync<User>(cacheKey);
if (userInfo.HasValue)
{
return userInfo.Value;
}
var user = _service.getById(id);
if (user != null)
{
_cache.Set<User>(cacheKey, user, TimeSpan.FromHours());
}
return user;
}
}
}

  先去查询缓存数据,没有的话再去查库然后保存到缓存中,一个查询是这样,那么多的CRUD都这样岂不是要写到吐?

  而我们使用EasyCaching的缓存AOP来简化这一操作。

  1.Nuget包

  EasyCaching.Interceptor.AspectCore

  2.在接口的定义上添加一个Attribute标识。

 using Coreqi.EasyCaching.Models;
using EasyCaching.Core.Interceptor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; namespace Coreqi.EasyCaching.Services
{
public interface IUserService
{
IList<User> getAll(); [EasyCachingAble(Expiration =)]
User getById(int id); User add(User user); User modify(User user); void delete(int id);
}
}

  3.配置服务使其生效

         // This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IUserService, UserService>(); //必须将Service添加到IOC中,否则AOP不成功
services.AddEasyCaching(option =>
{
option.UseInMemory("m1"); //配置一个InMemory,名称为m1
//使用redis
option.UseRedis(config =>
{
config.DBConfig.Endpoints.Add(new ServerEndPoint("127.0.0.1", ));
}, "localhostRedis")
//.WithMessagePack(); //使用MessagePack替换BinaryFormatter
//.WithProtobuf(); //使用Protobuf替换BinaryFormatter
.WithJson(); //使用Newtonsoft.Json替换BinaryFormatter
}); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); return services.ConfigureAspectCoreInterceptor(options =>
{
options.CacheProviderName = "localhostRedis"; //指定要使用的缓存提供者名称
});
}

  *也可以在Attribute特性上指定

 using Coreqi.EasyCaching.Models;
using EasyCaching.Core.Interceptor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; namespace Coreqi.EasyCaching.Services
{
public interface IUserService
{
IList<User> getAll(); [EasyCachingAble(Expiration =,CacheProviderName ="m1")]
User getById(int id); User add(User user); User modify(User user); void delete(int id);
}
}

 

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Coreqi.EasyCaching.Models;
using Coreqi.EasyCaching.Services;
using EasyCaching.Core;
using Microsoft.AspNetCore.Mvc; namespace Coreqi.EasyCaching.Controllers
{
[Route("api/[controller]")]
public class CacheAopController : Controller
{
private readonly IEasyCachingProvider _cache;
private readonly IUserService _service;
public CacheAopController(IEasyCachingProvider cache, IUserService service)
{
this._cache = cache;
this._service = service;
} [HttpGet]
[Route("get/{id}")]
public User GetUserById(int id)
{
return _service.getById(id);
}
}
}

完成上面的操作后可以在调用方法的时候优先取缓存,没有缓存的时候才会去执行方法。

EasyCaching提供的AOP Attritebute有一些通用的属性。

配置名 说明
CacheKeyPrefix 指定生成缓存键的前缀,正常情况下是用在修改和删除的缓存上
CacheProviderName 可以指定特殊的provider名字
IsHightAvailability 缓存相关操作出现异常时,是否还能继续执行业务方法

EasyCachingAble和EasyCachingPut还有一个同名和配置。

配置名 说明
Expiration key的过期时间,单位是秒

EasyCachingEvict有两个特殊的配置。

配置名 说明
IsAll 这个要搭配CacheKeyPrefix来用,就是删除这个前缀的所有key
IsBefore 在业务方法执行之前删除缓存还是执行之后

⒍支持Diagnostics【诊断系统】

  提供了Diagnostics的支持方便接入第三方的APM,实现追踪。

  有一个接入Jaeger的一个案例。大家可以去看看。

⒎二级缓存

二级缓存,多级缓存,其实在缓存的小世界中还算是一个比较重要的东西!

一个最为头疼的问题就是不同级的缓存如何做到近似实时的同步。

在EasyCaching中,二级缓存的实现逻辑大致就是下面的这张图。

如果某个服务器上面的本地缓存被修改了,就会通过缓存总线去通知其他服务器把对应的本地缓存移除掉

  1.添加Nuget包。

 EasyCaching.InMemory
EasyCaching.Redis
EasyCaching.HybridCache
EasyCaching.Bus.Redis

  2.添加配置

             services.AddEasyCaching(option =>
{
option.UseInMemory("m1"); //配置一个InMemory,名称为m1
//使用redis
option.UseRedis(config =>
{
config.DBConfig.Endpoints.Add(new ServerEndPoint("127.0.0.1", ));
config.DBConfig.Database = ;
}, "localhostRedis")
//.WithMessagePack(); //使用MessagePack替换BinaryFormatter
//.WithProtobuf(); //使用Protobuf替换BinaryFormatter
.WithJson(); //使用Newtonsoft.Json替换BinaryFormatter //使用Hybird
option.UseHybrid(config =>
{
config.EnableLogging = false; //是否开启日志
config.TopicName = "test_topic"; //缓存总线的订阅主题
config.LocalCacheProviderName = "m1"; //本地缓存的名字
config.DistributedCacheProviderName = "localhostRedis"; //分布式缓存的名字
}); //使用redis作为缓存总线
option.WithRedisBus(config =>
{
config.Endpoints.Add(new ServerEndPoint("127.0.0.1", ));
config.Database = ;
}); //使用CSRedis作为缓存总线
//option.WithCSRedisBus(config =>
//{
// config.ConnectionStrings = new List<string>
// {
// "127.0.0.1:6379,defaultDatabase=6,poolsize=10"
// };
//}); //使用RabbitMq作为缓存总线
//option.WithRabbitMQBus(config =>
//{
// config = new RabbitMQBusOptions();
//});
});

  3.使用

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EasyCaching.Core;
using Microsoft.AspNetCore.Mvc; namespace Coreqi.EasyCaching.Controllers
{
[Route("api/[controller]")]
public class HybridCachingController : Controller
{
private readonly IHybridCachingProvider _provider;
public HybridCachingController(IHybridCachingProvider provider)
{
this._provider = provider;
} [HttpGet]
[Route("add")]
public string Add()
{
string cacheKey = "coreqiTest";
_provider.Set(cacheKey, "", TimeSpan.FromSeconds());
return "添加成功!";
}
}
}

⒏特殊的Redis缓存提供者

Redis支持多种数据结构,还有一些原子递增递减的操作等等。为了支持这些操作,EasyCaching提供了一个独立的接口,IRedisCachingProvider。

这个接口,目前也只支持了百分之六七十常用的一些操作,还有一些可能用的少的就没加进去。

同样的,这个接口也是支持多实例的,也可以通过IEasyCachingProviderFactory来获取不同的provider实例。

在注入的时候,不需要额外的操作,和添加Redis是一样的。不同的是,在使用的时候,不再是用IEasyCachingProvider,而是要用IRedisCachingProvider

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EasyCaching.Core;
using Microsoft.AspNetCore.Mvc; namespace Coreqi.EasyCaching.Controllers
{
[Route("api/[controller]")]
public class MultiRedisController : Controller
{
private readonly IRedisCachingProvider _redis1;
private readonly IRedisCachingProvider _redis2; public MultiRedisController(IEasyCachingProviderFactory factory)
{
this._redis1 = factory.GetRedisProvider("redis1");
this._redis2 = factory.GetRedisProvider("redis2");
} [HttpGet]
[Route("get")]
public string Get()
{
_redis1.StringSet("keyredis1", "val"); var res1 = _redis1.StringGet("keyredis1");
var res2 = _redis2.StringGet("keyredis1"); return $"redis1 cached value: {res1}, redis2 cached value : {res2}";
}
}
}

⒐EasyCaching扩展性功能

除了这些基础功能,还有一些扩展性的功能,在这里要非常感谢yrinleung,他把EasyCaching和WebApiClient,CAP等项目结合起来了。感兴趣的可以看看这个项目EasyCaching.Extensions

参考文章:一篇短文带您了解一下EasyCaching-----作者@Catcher ( 黄文清 )

ASP.NET Core中使用EasyCaching作为缓存抽象层的更多相关文章

  1. ASP.NET Core 中的缓存

    目录 缓存的基本概念 缓存原理 缓存设计 分布式缓存 Memcache 与 Redis 的比较 缓存穿透,缓存击穿,缓存雪崩解决方案 数据一致性 使用内置 MemoryCache 使用分布式缓存 Re ...

  2. ASP.NET Core 中的SEO优化(1):中间件实现服务端静态化缓存

    分享 最近在公司成功落地了一个用ASP.NET Core 开发前台的CMS项目,虽然对于表层的开发是兼容MVC5的,但是作为爱好者当然要用尽量多的ASP.NET Core新功能了. 背景 在项目开发的 ...

  3. C# 嵌入dll 动软代码生成器基础使用 系统缓存全解析 .NET开发中的事务处理大比拼 C#之数据类型学习 【基于EF Core的Code First模式的DotNetCore快速开发框架】完成对DB First代码生成的支持 基于EF Core的Code First模式的DotNetCore快速开发框架 【懒人有道】在asp.net core中实现程序集注入

    C# 嵌入dll   在很多时候我们在生成C#exe文件时,如果在工程里调用了dll文件时,那么如果不加以处理的话在生成的exe文件运行时需要连同这个dll一起转移,相比于一个单独干净的exe,这种形 ...

  4. ASP.NET Core中使用Cache缓存

    ASP.NET Core中使用Cache缓存 缓存介绍: 通过减少生成内容所需的工作,缓存可以显著提高应用的性能和可伸缩性. 缓存对不经常更改的数据效果最佳. 缓存生成的数据副本的返回速度可以比从原始 ...

  5. ASP.NET Core中的Http缓存

    ASP.NET Core中的Http缓存 Http响应缓存可减少客户端或代理对web服务器发出的请求数.响应缓存还减少了web服务器生成响应所需的工作量.响应缓存由Http请求中的header控制. ...

  6. ASP.NET Core教程:ASP.NET Core中使用Redis缓存

    参考网址:https://www.cnblogs.com/dotnet261010/p/12033624.html 一.前言 我们这里以StackExchange.Redis为例,讲解如何在ASP.N ...

  7. 在ASP.NET Core中使用百度在线编辑器UEditor

    在ASP.NET Core中使用百度在线编辑器UEditor 0x00 起因 最近需要一个在线编辑器,之前听人说过百度的UEditor不错,去官网下了一个.不过服务端只有ASP.NET版的,如果是为了 ...

  8. ASP.NET Core 中文文档 第三章 原理(13)管理应用程序状态

    原文:Managing Application State 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:高嵩 在 ASP.NET Core 中,有多种途径可以对应用程序的状态进行 ...

  9. ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【总体设计 】

    本系列前面的文章我们主要以编程的角度对ASP.NET Core的依赖注入系统进行了详细的介绍,如果读者朋友们对这些内容具有深刻的理解,我相信你们已经可以正确是使用这些与依赖注入相关的API了.如果你还 ...

随机推荐

  1. CDQ解决一些三维偏序的问题

    本来几天前就该记录的东西,硬生生被我拖到了现在,太懒了... 在cdq学习时,二维偏序已经解决了,无非就是先sort使第一维有序,然后再用cdq或者数据结构处理第二维.而三维偏序的时候呢,大佬的做法好 ...

  2. carographer流程

    node_main.cc  93   Run启动              58   Node初始化              64   node.StartTrajectoryWithDefault ...

  3. func<T> 和 action<T>

    一.Func Func<Result>,Func<T1,Result>是一个.Net内置的泛型委托. Func<TResult> Func<T,TResult ...

  4. 将本地文件夹同步到github仓库中

    参考博客 本地生成rsa密钥 cd ~/.ssh # 查看是否已经生成过密钥 ssh-keygen -t rsa -C "username on github" # -C表示注释 ...

  5. Django的JWT机制工作流程

    https://blog.csdn.net/bin_1022/article/details/81278513 django-rest-framework-jwt token 怎么解码得到用户名? d ...

  6. onReachBottom 注意事项

    onReachBottom使用注意 可在pages.json里定义具体页面底部的触发距离onReachBottomDistance,比如设为50,那么滚动页面到距离底部50px时,就会触发onReac ...

  7. Thymeleaf Multiple Template Locations using Spring Boot

    1. Overview In this tutorial, we'll see how we can define multiple template locations using Thymelea ...

  8. js 处理url参数,应用导航分类

    1.先上图 2.代码 html <li><a href="javascript:void(0);" data-cid = "{$v['id']}&quo ...

  9. MVP模式入门案例

    随着UI创建技术的功能日益增强,UI层也履行着越来越多的职责.为了更好地细分视图(View)与模型(Model)的功能,让View专注于处理数据的可视化以及与用户的交互,同时让Model只关系数据的处 ...

  10. 自定义View绘制简单的圆环的实现

    package com.loaderman.mywave; import android.content.Context; import android.graphics.Canvas; import ...