@[TOC]
【82.Context启动startActivity注意】
intent.setFlags() 方法中参数的用例:
很多人在使用 startActivity时候,会碰到如下异常:
Caused by: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
都知道,Context中有一个startActivity方法。Activity继承自Context,重载了startActivity方法,如果使用Activity的startActivity方法不会有任何限制。而如果使用Context的startActivity方法的话,就需要开启一个新的task,遇到上面的异常就是因为此。
解决办法:代码中加一个flag,即Intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).这样就可以在新的task里启动这个Activity了。
【83.Android调用各系统界面】
startActivity(new Intent(Settings.ACTION_SETTINGS)); //系统设置界面
String pkg = context.getApplicationInfo().packageName;
int uid = context.getApplicationInfo().uid;
//有些特殊界面是没办法打开的,例如NFC等设置界面,需要手机硬件支持.
ACTION_APPLICATION_DETAILS_SETTINGS apk详情界面
intent.setData(Uri.parse("package:" + ));
ACTION_APP_NOTIFICATION_SETTING; apk通知界面
//API26 8.0以上
intent.putExtra(Settings.EXTRA_APP_PACKAGE, pkg);
intent.putExtra(Settings.EXTRA_CHANNEL_ID, uid);
//API21-25 5.0-7.1
intent.putExtra("app_package", pkg);
intent.putExtra("app_uid", pkg);
ACTION_SETTINGS 系统设置界面
ACTION_DATA_ROAMING_SETTINGS 双卡和移动网络设置界面
ACTION_WIFI_SETTINGS 无线网设置界面
ACTION_VPN_SETTINGS VPN设置界面,可能不存在
ACTION_WIFI_IP_SETTINGS WIFI的IP设置
ACTION_BLUETOOTH_SETTINGS 蓝牙设置
ACTION_DATE_SETTINGS 日期时间设置
ACTION_LOCALE_SETTINGS 语言设置
ACTION_SOUND_SETTINGS 声音设置
ACTION_SECURITY_SETTINGS 安全设置界面
ACTION_ACCESSIBILITY_SETTINGS 无障碍设置/辅助功能界面
ACTION_ADD_ACCOUNT 添加账户界面
ACTION_AIRPLANE_MODE_SETTINGS 更多连接方式设置界面
ACTION_APN_SETTINGS APN设置界面
ACTION_APPLICATION_DEVELOPMENT_SETTINGS 开发者选项设置
ACTION_APPLICATION_SETTINGS 应用程序列表界面
ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS //【所有的】
ACTION_MANAGE_APPLICATIONS_SETTINGS //【已安装的】
ACTION_BATTERY_SAVER_SETTINGS 节电助手界面
ACTION_CAPTIONING_SETTINGS 字幕设置的界面
ACTION_CAST_SETTINGS 投射设置 //【API 17及以上】
ACTION_DEVICE_INFO_SETTINGS 手机状态信息的界面
ACTION_DREAM_SETTINGS 互动屏保设置的界面
ACTION_DISPLAY_SETTINGS 手机显示设置
ACTION_HOME_SETTINGS 主屏幕设置界面
ACTION_INPUT_METHOD_SETTINGS 语言和输入法设置
ACTION_INPUT_METHOD_SUBTYPE_SETTINGS 多国语言选择
ACTION_INTERNAL_STORAGE_SETTINGS 存储空间设置的界面
ACTION_MEMORY_CARD_SETTINGS 记忆卡存储
ACTION_LOCATION_SOURCE_SETTINGS 定位设置界面
ACTION_NETWORK_OPERATOR_SETTINGS 选取运营商的界面
ACTION_NFCSHARING_SETTINGS 显示NFC共享设置 //【API 14及以上】
ACTION_NFC_SETTINGS 显示NFC设置 //【API 16及以上】
ACTION_NOTIFICATION_LISTENER_SETTINGS 通知使用权设置的界面
ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS 勿扰权限设置的界面
ACTION_PRINT_SETTINGS 打印设置界面
ACTION_PRIVACY_SETTINGS 备份重置设置界面
ACTION_QUICK_LAUNCH_SETTINGS 跳转快速启动设置界面
ACTION_SEARCH_SETTINGS 搜索设置界面
ACTION_SYNC_SETTINGS 同步设置界面
ACTION_USER_DICTIONARY_SETTINGS 个人字典设置界面
ACTION_VOICE_INPUT_SETTINGS 辅助应用和语音输入设置
【84.view淡入淡出效果】
淡入
View mContentView;
mContentView.setAlpha(0f);
mContentView.setVisibility(View.VISIBLE);
mContentView.animate()
.alpha(1f)
.setDuration(300)
.setListener(null);
//Kotlin
private lateinit var mContentView: View
mContentView.apply {
// Set the content view to 0% opacity but visible, so that it is visible
// (but fully transparent) during the animation.
alpha = 0f
visibility = View.VISIBLE
// Animate the content view to 100% opacity, and clear any animation
// listener set on the view.
animate()
.alpha(1f)
.setDuration(mShortAnimationDuration.toLong())
.setListener(null)
}
淡出
private View mLoadingView;
mLoadingView.animate()
.alpha(0f)
.setDuration(mShortAnimationDuration)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLoadingView.setVisibility(View.GONE);
}
});
//Kotlin
private lateinit var mLoadingView: View
mLoadingView.animate()
.alpha(0f)
.setDuration(mShortAnimationDuration.toLong())
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
mLoadingView.visibility = View.GONE
}
})
【85.view绘制监听】
//view??
addOnPreDrawListener
//view重绘时回调
view.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
@Override
public void onDraw() {
}
});
//view加载完成时回调(布局的状态发生变化或者可见性发生变化才会调用)
view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//todu 这里写你要在界面加载完成后执行的操作。
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
OnPreDrawListener
在调用onDraw()方法之前调用.此时,树中的所有视图都已经过测量并给出了一个框架.因此,您可以在此回调中正确操作视图
OnGlobalLayoutListener
这个监听器被调用: – 当可见性状态发生变化时在示例中,当绘制视图时,它变得可见,并且会调用它. – 当你添加View视图树的变化时
【86.Android危险权限】
普通权限直接在清单配置即可,危险权限需要用户手动动态申请。当用户同意了某一个权限时,那么该权限所对应的权限组下的所有其他权限也会同时被授权。
其中 Intent.ACTION_DIAL表示打开拨号界面,不需要动态权限。 Intent.ACTION_CALL则需要。
[](ttp://developer.android.com/reference/android/Manifest.permission.html) 系统完整权限列表。
【87.在不用事件总线时 处理方法】
1.广播
class ActivityA extends Activity{
..onCreate(..){
receiver = new BroadcastReceiver(){
@Override
public void onReceive(Context context, Intent intent){
User user=intent.getParcelableExtra("user");
//do sth
}
};
registerReceiver(receiver, new IntentFilter("my_action"));
}
..onDestroy(..){
unregisterReceiver(receiver);
}
}
class ActivityB extends Activity{
..onCreate(..){
Intent intent = new Intent("my_action");
intent.putExtra("user", new User("zxx"));
sendBroadcast(intent);
}
}
class User implements Parcelable{
String name;
....
}
2.接收来自子线程的消息
class ActivityA extends Activity{
..onCreate(..){
//创建一个子线程
new Thread(new Runnable(){
@Override
public void run(){
User user = new User("来自子线程");
Message msg=mHandler.obtainMessage();
msg.what=1;
msg.obj=user;
mHandler.sendMessage(msg);
}
}).start();
}
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg){
if(msg.what==1){
User user= (User)msg.obj;
//do sth 接收来自子线程的消息
}
}
}
}
3.请使用 EventBus
[](https://github.com/hehonghui/AndroidEventBus)
[](https://github.com/greenrobot/EventBus/)
与EventBus、otto的特性对比
名称 | 订阅函数是否可执行在其他线程 | 特点 |
---|---|---|
greenrobot的EventBus | 是 | 使用name pattern模式,效率高,但使用不方便。 |
square的otto | 否 | 使用注解,使用方便,但效率比不了EventBus。 |
AndroidEventBus | 是 | 使用注解,使用方便,但效率比不上EventBus。订阅函数支持tag(类似广播接收器的Action)使得事件的投递更加准确,能适应更多使用场景。 |
【88.多线程】
runnable 解决java单继承冲突的,重写的run 方法是task的核心,未开启的thread 跟runnable区别不大。
runnable本身跟线程没啥关系啊,只是作为对任务task 的包装,还是要作为载体加到thread 中去的。
无论用runnable还是thread 都需要 开启线程 new Thread ,start()
四种多线程的创建方法
第一种:继承Thread类:
1.创建一个继承于Thread类的子类
2.重写Thread类的run() 将此线程执行的的操作声明在run()中
3.创建Thread类的子类的对象
4.通过此对象调用start()
// 1.创建一个继承于Thread类的子类
class MyThread extends Thread {
//2.重写Thread类的run()
@Override
public void run() {
for (int i = 0;i < 100 ;i++){
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public static void main(String[] args) {
//3.创建Thread类的子类的对象
MyThread myThread = new MyThread();
//4.通过此对象调用start() ①启动当前线程 ②调用当前线程的run()
myThread.start();
//不能通过myThread.run()的方式启动线程
//再启动一个线程
MyThread myThread1 = new MyThread();
myThread1.start();
//如下操作仍然是在main()线程中执行的
for (int i = 0;i < 100 ;i++){
if (i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":"+"hello");
}
}
}
第二种:实现Runnable接口:
1.创建一个实现了Runnable接口的类
2.实现类去实现Runnable中的抽象方法:run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()
开发中优先选择实现Runnable接口的方式
* 原因:1.实现的方式没有类的单继承性的局限性
* 2.实现的方式更适合来处理多个线程有共享数据的情况
*
* 联系:都实现了Runnable接口
* 相同点:两种方式都需要重写run()方法,将线程要执行的逻辑声明在run()中
// * 1.创建一个实现了Runnable接口的类
class MyThread implements Runnable{
// * 2.实现类去实现Runnable中的抽象方法:run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public static void main(String[] args) {
// * 3.创建实现类的对象
MyThread myThread = new MyThread();
// * 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread thread1 = new Thread(myThread);
// * 5.通过Thread类的对象调用start() ①启动线程 ②调用当前线程的run()
thread1.start();
Thread thread2 = new Thread(myThread);
thread2.start();
}
第三种:实现Callable接口:JDK5.0新增
1.创建一个实现Callable的实现类
2.实现call方法,将此线程需要执行的操作声明在call()中
3.创建Callable接口实现类的对象
4.将Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法
6.获取Callable中call方法中的返回值
* 如何理解实现Callable接口的方式创建多线程的方式比是实现Runnable接口创建多线程方式强大
* 1.call()可以有返回值
* 2.call()可以抛出异常
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法
Thread thread1 = new Thread(futureTask);
thread1.start();
try {
//6.获取Callable中call方法中的返回值
//get()返回值即为FutureTask构造器参数Callable实现重写的call()返回值。
Object sum = futureTask.get();
System.out.println(" 总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
第四组:线程池的方式:
1.提供指定线程数量的线程池
2.执行指定线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
/**
* 创建线程的方式四:使用线程池
*
* 好处:1.提高响应速度(减少创建新线程的时间)
* 2.降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
* 3.便于线程管理
*
* corePoolSize:核心池的大小
* maximumPoolSize:最大线程数
* keepAliveTime:线程没有任务时最多保持多长时间后会终止
*/
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) executorService;
//设置线程池的属性
service1.setCorePoolSize(10);
//2.执行指定线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
executorService.execute(new NumberThread());//适合使用于Runnable
//executorService.submit();//适合使用与Callable
executorService.execute(new NumberThread1());
//关闭线程池
executorService.shutdown();
}
}
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100 ; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" +i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100 ; i++) {
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" +i);
}
}
}
}
Handler开启线程:这个并没有开启新线程
private Handler mHandler=new Handler();
mHandler.postDelayed(gotoLoginAct, 2000);//post(r)
Runnable gotoLoginAct = new Runnable() {
@Override
public void run() {
Log.e("gotoLoginAct ");
mHandler.removeCallbacks(gotoLoginAct);//停止线程
}
};
//上面这个并没有开启新线程,而要用
HandlerThread handlerThread = new HandlerThread("handlerThread");
handlerThread.start();
MyHandler handler = new MyHandler(handlerThread.getLooper());
【标题导航栏】
values-21这个文件夹,这个文件夹就是指只会在API21以上才会使用
[]
通知栏,标题栏,视图框,导航栏
<item name="android:navigationBarColor">@color/colorPrimary</item>
/**
* Set the navigation bar's color.
* 该方法是在Android5.0中才加入的,而且在设置之前还需要判断手机是否支持虚拟导航栏。
* @param window The window.
* @param color The navigation bar's color.
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public static void setNavBarColor(@NonNull final Window window, @ColorInt final int color) {
window.setNavigationBarColor(color);
}
/**
* Return whether the navigation bar visible.
*
* @return {@code true}: yes<br>{@code false}: no
*/
public static boolean isSupportNavBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
WindowManager wm = (WindowManager) BaseApplication.getsApplication().getSystemService(Context.WINDOW_SERVICE);
if (wm == null) return false;
Display display = wm.getDefaultDisplay();
Point size = new Point();
Point realSize = new Point();
display.getSize(size);
display.getRealSize(realSize);
return realSize.y != size.y || realSize.x != size.x;
}
boolean menu = ViewConfiguration.get(BaseApplication.getsApplication()).hasPermanentMenuKey();
boolean back = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
return !menu && !back;
}
如果要想改变导航栏按键的颜色,需要设置属性:windowLightNavigationBar为true,也可以在代码中调用方法:
View.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
需要注意的是,该API是在27中才加入的。
总结一下:
- 设置导航栏背景色:设置navigationBarColor属性即可;
- 改变导航栏按键颜色为深色:设置标志位View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,或者属性中设置windowLightNavigationBar为true;
Activity设置横屏、竖屏的方法
<activity android:name=".MyAcitivty" android:screenOrientation="landscape" />
注:landscape为横屏,portrait为竖屏。
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
注:SCREEN_ORIENTATION_LANDSCAPE为横屏,SCREEN_ORIENTATION_PORTRAIT为竖屏。
【时间】
[]
SystemClock.elapsedRealtime() & SystemClock.elapsedRealtimeNanos
这个值与SystemClock.upTimeMillis()类似。它是系统启动到当前时刻经过的时间,包括了系统睡眠经过的时间。在CPU休眠之后,它依然保持增长。所以它适合做更加广泛通用的时间间隔的统计。
System.currentTimeMillis() 标准的“墙”时钟(时间和日期),表示从纪元到现在的毫秒数。
如果你使用System.currentTimeMillis(),可以考虑监听ACTION为ACTION_TIME_TICK、
ACTION_TIME_CHANGED、ACTION_TIMEZONE_CHANGED 的广播去监听时间变化。
SystemClock.uptimeMillis() 表示自系统启动时开始计数,以毫秒为单位。
返回的是从系统启动到现在这个过程中的处于非休眠期的时间。当系统进入深度睡眠时(CPU关闭,设备变黑,等待外部输入装置)该时钟会停止。
但是该时钟不会被时钟调整,闲置或其他节能机所影响。这是大多数间隔时间的基本点,例如Thread.sleep(millls)、Object.wait(millis)和System.nanoTime()。该时钟被保证是单调的,适用于检测不包含休眠的间隔时间的情况。
SystemClock.elapsedRealtime() & SystemClock.elapsedRealtimeNanos()
返回系统启动到现在的时间,包含设备深度休眠的时间。该时钟被保证是单调的,即使CPU在省电模式下,该时间也会继续计时。该时钟可以被使用在当测量时间间隔可能跨越系统睡眠的时间段。
SystemClock.
- 1、public static long currentThreadTimeMillis () 返在当前线程运行的毫秒数。
- 2、public static long elapsedRealtime () 返回系统启动到现在的毫秒数,包含休眠时间。
- 3、public static long elapsedRealtimeNanos () 返回系统启动到现在的纳秒数,包含休眠时间。
- 4、public static boolean setCurrentTimeMillis (long millis) 设置当前的"墙"时间,要求调用进程有许可权限。返回是否成功。
- 5、public static void sleep (long ms) 等待给定的时间。和Thread.sleep(millis)类似,但是它不会抛出InterruptedException异常。事件被推迟到下一个中断操作。该方法直到指定的时间过去才返回。
- 6、public static long uptimeMillis () 返回系统启动到现在的毫秒数,不包含休眠时间。就是说统计系统启动到现在的非休眠期时间。
【89.ImageView加载图片】
//设置网络图片
public void setImageURL(final String path) {
//开启一个线程用于联网
new Thread() {
@Override
public void run() {
try {
//把传过来的路径转成URL
URL url = new URL(path);
//获取连接
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
//使用GET方法访问网络
connection.setRequestMethod("GET");
//超时时间为10秒
connection.setConnectTimeout(10000);
//获取返回码
int code = connection.getResponseCode();
if (code == 200) {
InputStream inputStream = connection.getInputStream();
//使用工厂把网络的输入流生产Bitmap
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
//利用Message把图片发给Handler
Message msg = Message.obtain();
msg.obj = bitmap;
msg.what = GET_DATA_SUCCESS;
handler.sendMessage(msg);
inputStream.close();
}else {
//服务启发生错误
handler.sendEmptyMessage(SERVER_ERROR);
}
} catch (IOException e) {
e.printStackTrace();
//网络连接错误
handler.sendEmptyMessage(NETWORK_ERROR);
}
}
}.start();
}
Glide缓存-介绍郭霖
[]
这个diskCacheStrategy()方法基本上就是Glide硬盘缓存功能的一切,它可以接收五种参数:
DiskCacheStrategy.NONE: 表示不缓存任何内容。
DiskCacheStrategy.DATA: 表示只缓存原始图片。
DiskCacheStrategy.RESOURCE: 表示只缓存转换过后的图片。
DiskCacheStrategy.ALL : 表示既缓存原始图片,也缓存转换过后的图片。
DiskCacheStrategy.AUTOMATIC: 表示让Glide根据图片资源智能地选择使用哪一种缓存策略(默认选项)。
其中,DiskCacheStrategy.DATA对应Glide 3中的DiskCacheStrategy.SOURCE,
DiskCacheStrategy.RESOURCE对应Glide 3中的DiskCacheStrategy.RESULT。
而DiskCacheStrategy.AUTOMATIC是Glide 4中新增的一种缓存策略,并且在不指定diskCacheStrategy的情况下默认使用就是的这种缓存策略。
Glide加载网络图片,
显示之前的URL图片,换了URL图片还是没变的问题
1、每次加载都清理缓存。这是个很垃圾的解决方法,相当于舍弃了缓存这个非常重要的功能。
.diskCacheStrategy(DiskCacheStrategy.NONE) //禁用磁盘缓存
.skipMemoryCache(true); //跳过内存缓存
2、图片地址采用:url+?随机数。当图片更换的时候,后台改变随机数就可以,这样你本地就会重新加载网络图片。如果后台没有这样做那你可以自己加随机数,在url后面添加“?”和随机的key+随机数,通过Math.random()返回一个0到1之间的double值。
Glide.with(getContext())
.load(url + "?key=" + Math.random())
.centerCrop()
.into(imageUser);
3、使目标图片显示的所有地方均更新为服务器新数据——治本,永久性
在加载图片的时候调用下方给出的updateOptions()方法(PS:这个方法可以放在工具类中使用),给RequestOptions指定signature属性,当modified改变时,Glide会判断为新图片,并且重新缓存,这样加载的就是服务器最新图片了。
这个要求后台要有修改的时间变量。
/**
* RequestOptions 一般指定以下属性即可
* placeholder : 图片加载时的站位图,可使用loading动画或默认图片
* error : 图片加载失败时的显示图片
* diskCacheStrategy : 图片缓存路径
*/
RequestOptions options = new RequestOptions()
.placeholder(R.drawable.bg_round_graylight10)
.error(R.drawable.def_avatar)
.diskCacheStrategy(DiskCacheStrategy.DATA);
/**
* 刷新RequestOptions,解決Glide图片缓存导致不刷新问题
*
* @param options
* @param url 图片地址
* @param modified 图片修改时间
*/
public static void updateOptions(RequestOptions options, String url, long modified){
if(!Util.isEmpty(url) && url.contains(".")){
try{
String tail = url.substring(url.lastIndexOf(".") + 1).toLowerCase();
String type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(tail);
options.signature(new MediaStoreSignature(type, modified, 0));
}catch (Exception e){}
}
}
modified改变时,Glide会将当前URL判断为新资源并且重新缓存,并不会覆盖之前的缓存记录。
也就是说如果APP中有多处地方需要显示这个图片,
那么就需要在所有显示这个图片的位置给RequestOptions指定signature属性。
.signature(new StringSignature(UUID.randomUUID().toString()))//增加签名
如果链接是文件,就用StringSignature,
如果链接是多媒体,就用MediaStoreSignature,
//用的此处
Glide 4.0中.signature()方法形参找不到StringSignature,而且形参改为Key。
.signature(new ObjectKey(System.currentTimeMills()))