腾讯
问题1、synchronize用法
synchronize翻译: vt. 使同步; 使同时; vi. 同时发生; 共同行动;
定义:
Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
解释:
一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
五、以上规则对其它对象锁同样适用.
问题2、volatile用法
volatile翻译:adj. 易变的,不稳定的;(液体或油)易挥发的;爆炸性的;快活的,轻快的
定义:volatile 变量提供了线程的可见性,并不能保证线程安全性和原子性。
volatile 相当于synchronized的弱实现,也就是说volatile实现了类似synchronized的语义,却又没有锁机制。它确保对volatile字段的更新以可预见的方式告知其他的线程。
解释:
锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。
Java存储模型不会对valatile指令的操作进行重排序:这个保证对volatile变量的操作时按照指令的出现顺序执行的。
volatile变量不会被缓存在寄存器中(只有拥有线程可见)或者其他对CPU不可见的地方,每次总是从主存中读取volatile变量的结果。也就是说对于volatile变量的修改,其它线程总是可见的,并且不是使用自己线程栈内部的变量。也就是在happens-before法则中,对一个valatile变量的写操作后,其后的任何读操作理解可见此写操作的结果。
尽管volatile变量的特性不错,但是volatile并不能保证线程安全的,也就是说volatile字段的操作不是原子性的,volatile变量只能保证可见性(一个线程修改后其它线程能够理解看到此变化后的结果),要想保证原子性,目前为止只能加锁!
使用Volatile变量的原则:
写入变量不依赖此变量的值,或者只有一个线程修改此变量
变量的状态不需要与其它变量共同参与不变约束
访问变量不需要加锁
Volatile变量 使用情景:
状态标志 :也许实现 volatile 变量的规范使用仅仅是使用一个布尔状态标志,用于指示发生了一个重要的一次性事件,例如完成初始化或请求停机。
一次性安全发布(one-time safe publication):缺乏同步会导致无法实现可见性,这使得确定何时写入对象引用而不是原语值变得更加困难。在缺乏同步的情况下,可能会遇到某个对象引用的更新值(由另一个线程写入)和该对象状态的旧值同时存在。(这就是造成著名的双重检查锁定(double-checked-locking)问题的根源,其中对象引用在没有同步的情况下进行读操作,产生的问题是您可能会看到一个更新的引用,但是仍然会通过该引用看到不完全构造的对象)。实现安全发布对象的一种技术就是将对象引用定义为 volatile 类型.
独立观察(independent observation)
安全使用 volatile 的另一种简单模式是:定期 “发布” 观察结果供程序内部使用。例如,假设有一种环境传感器能够感觉环境温度。一个后台线程可能会每隔几秒读取一次该传感器,并更新包含当前文档的 volatile 变量。然后,其他线程可以读取这个变量,从而随时能够看到最新的温度值。
“volatile bean” 模式:适用于将 JavaBeans 作为“荣誉结构”使用的框架。在 volatile bean 模式中,JavaBean 被用作一组具有 getter 和/或 setter 方法 的独立属性的容器。volatile bean 模式的基本原理是:很多框架为易变数据的持有者(例如 HttpSession)提供了容器,但是放入这些容器中的对象必须是线程安全的。 JavaBean 的所有数据成员都是 volatile 类型的,并且 getter 和 setter 方法必须非常普通 —— 除了获取或设置相应的属性外,不能包含任何逻辑。此外,对于对象引用的数据成员,引用的对象必须是有效不可变的。(这将禁止具有数组值的属性,因为当数组引用被声明为 volatile 时,只有引用而不是数组本身具有 volatile 语义)。对于任何 volatile 变量,不变式或约束都不能包含 JavaBean 属性。
开销较低的读-写锁策略:(牺牲 可读性/可维护性,来换取 可能的性能提升)如果读操作远远超过写操作,您可以结合使用内部锁和 volatile 变量来减少公共代码路径的开销。清单 6 中显示的线程安全的计数器使用 synchronized 确保增量操作是原子的,并使用 volatile 保证当前结果的可见性。如果更新不频繁的话,该方法可实现更好的性能,因为读路径的开销仅仅涉及 volatile 读操作,这通常要优于一个无竞争的锁获取的开销。
volatile更多使用
问题3、动态权限适配方案,权限组的概念
3.1、动态权限适配方案
Android动态权限申请流程代码实现逻辑流程:
1、使用Context.checkSelfPermission()接口先检查权限是否授权。
2、使用Activity.shouldShowRequestPermissionRationale()接口检查用户是否勾选不再提醒。
3、第2步返回为true时,表示用户并未勾选不再提醒选项,使用Activity.requestPermissions()接口向系统请求权限。
4、第2步返回为false时,表示用户已勾选不再提醒选项,则应用该弹框提示用户。
5、第3步执行后,不论用户是否授予权限,都会回调Activity.onRequestPermissionsResult()的函数。在Activity中重载onRequestPermissionsResult()函数,在接收授权结果,根据不同的授权结果做相应的处理。
Android动态权限适配_整个方案职能图分析:
在M版本之前,应用App需要用到什么权限只需要在AndroidManifest.xml配置文件中增加相对应权限的配置,然后在App中就可以随便无限制的使用这些权限来访问用户的设备了。由于这种权限的漏洞,导致了大量的用户信息的泄露。所以在M版本上,Android官方团队重新修改了这个权限的申请方式。在新的权限模式之下,用户将能够根据自己的实际需要在运行时中对各项权限进行审核,且随时关闭其中的部分权限。
在M版本中,android权限被分为了3大类,默认权限、签名权限和危险权限,其中默认权限和签名权限都为安装时权限,即只要在AndroidManifest.xml中配置,就可以在app运行时放心大胆的使用。但是对于危险权限,不仅要再AndroidManifest.xml中配置,还要在代码中动态去申请,因为这些权限不是默认就授予的,而是运行时去向用户申请的。
对于低于M版本的Android系统无需适配,直接返回true.
理论:
动态权限的申请流程会产生好几个分支处理逻辑,这样不善于管理和维护。所以对于此处,为了将写得代码更加整洁和更易于维护,我们可以将动态权限申请进行一次封装,新建一个空白的activity用户权限申请和回调,然后在activity外包装一层管理内,限制一个调用的入口和出口,对于外部暴露唯一的入口和出口,这样对于外部逻辑代码需要调用权限时,将变得异常简单,并且由于将权限申请封装在了管理类中,对于低于M版本的Android系统也将没有任何引用,在管理类中直接对于低于M版本的权限申请请求直接回调全部已授权即可。
由于权限的动态申请与管理应该是伴随着整个App的生命周期的,所以PermissionsManager设计为单例的,且初始化保存着applicationContext作为默认跳转方式。
PermissionsManager.java 核心代码:
/**
* 权限申请回调函数
*/
public interface PermissionsResultsCallback{
void onRequestPermissionsResult(int[] grantResults);
}
/** 请求权限CodeId对应到具体的每一次申请*/
private int mRequestCodeId;
/** 每一次申请对应回调函数*/
private final Map<Integer, PermissionsResultsCallback> mRequestIdToCallback = new HashMap<Integer, PermissionsResultsCallback>();
/** 每一次申请对应的权限表*/
private final Map<Integer, Map<String, Integer>> mRequestIdToPermissions = new HashMap<Integer, Map<String, Integer>>();
/** 当前请求权限*/
private final Map<String, Integer> mWaitingRequestPermissions = new HashMap<String, Integer>();
/**
* 新的请求权限方法,在内部处理M版本和非版本的适配, 同时也是PermissionsManager动态权限申请的入口
*
* @param callback 回调函数,回调申请权限的string数组对应的状态(有权限或没有权限)
* @param activity
* @param permissionsToRequest
*/
public synchronized void newRequestPermissions(PermissionsResultsCallback callback, Activity activity,
String... permissionsToRequest)
{
// 初始化申请权限状态表
Map<String, Integer> map = PermissionsUtil.getStatusPermissions(mApplicationContext, permissionsToRequest);
// 筛选出未获取用户授权的申请权限数组
String[] permissionsArray = PermissionsUtil.arrayDeniedPermissions(map);
// 分配申请Id给本次权限申请
int requestId = getNextRequestId();
LogC.i("requestPermissions, requestId:" + requestId, false);
// 存储本次申请授权的回调函数和权限状态表
mRequestIdToCallback.put(requestId, callback);
mRequestIdToPermissions.put(requestId, map);
permissionsArray = getDeniedOrRequestingPermissions(permissionsArray);
// 判断当申请权限全部为已授权时,直接回调
if (null == permissionsArray || permissionsArray.length == 0)
{
// 当前权限申请正在进行
LogC.i("PermissionsManager permissionsArray has a permission requesting or null", false);
onRequestPermissionsResult(requestId, permissionsArray, new int[0]);
return;
}
else
{
for (String permission : permissionsArray)
{
// 当前权限申请未正在进行
LogC.i("PermissionsManager permission === "+permission+" is requesting", false);
mWaitingRequestPermissions.put(permission,requestId);
}
}
// 判断当有申请权限为未授权时,执行权限申请操作
if (null != activity)
{
LogC.i("PermissionsManager requestPermissions startActivityForResult", false);
Intent intent = new Intent(activity, PermissionsActivity.class);
intent.putExtra(PermissionsActivity.EXTRA_PERMISSION_REQUESTED_PERMISSIONS, permissionsArray);
intent.putExtra(PermissionsActivity.EXTRA_PERMISSION_REQUEST_CODE, requestId);
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
activity.startActivityForResult(intent, REQUEST_CODE);
}
else
{
// 默认情况下申请权限方式
LogC.i("PermissionsActivity.run(mApplicationContext, requestId, permissionsArray)", false);
PermissionsActivity.run(mApplicationContext, requestId, permissionsArray);
}
}
/**
* 回调函数,回调每次申请权限的处理接口 同时PermissionsManager动态权限申请的出口
*
* @param requestCode
* @param permissions
* @param grantResults
*/
public synchronized void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
{
LogC.i("PermissionsManager onRequestPermissionsResult, requestCode:" + requestCode, false);
// 解析权限申请结果,将权限结果状态更新到权限状态表
Map<String, Integer> map = mRequestIdToPermissions.get(requestCode);
if (null != map)
{
// 再次检测刷新申请权限的状态
map = PermissionsUtil.getStatusPermissions(mApplicationContext, map);
for (int i = 0; i < grantResults.length; i++)
{
map.put(permissions[i], grantResults[i]);
}
grantResults = PermissionsUtil.arrayStatusPermissions(map);
for (String permission : map.keySet())
{
if(mWaitingRequestPermissions.containsKey(permission) && requestCode == mWaitingRequestPermissions.get(permission))
{
LogC.i("PermissionsManager key === "+ permission +" remove !", false);
mWaitingRequestPermissions.remove(permission);
}
}
}
// 将权限状态回调给本次申请的回调函数
PermissionsResultsCallback permissionsResultsCallback = mRequestIdToCallback.get(requestCode);
if (null == permissionsResultsCallback)
{
LogC.i("onRequestPermissionsResult permissionsResultsCallback null", false);
}
else
{
// 删除本次权限申请的回调和状态表
LogC.i("callback remove:" + mRequestIdToCallback.remove(requestCode), false);
LogC.i("requestMap remove:" + mRequestIdToPermissions.remove(requestCode), false);
permissionsResultsCallback.onRequestPermissionsResult(grantResults);
}
}
PermissionsActivity.java 核心代码
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
sp = getApplicationContext().getSharedPreferences(PERMISSION_CACHE_NAME, Context.MODE_PRIVATE);
LogC.i("permissions check onCreate", false);
mPendingRequestCode = (savedInstanceState != null) ? savedInstanceState.getInt(EXTRA_PERMISSION_REQUEST_CODE,
INVALID_REQUEST_CODE) : INVALID_REQUEST_CODE;
}
@Override
protected void onResume()
{
super.onResume();
// 当请求码为默认值时执行
if (mPendingRequestCode == INVALID_REQUEST_CODE)
{
Intent intent = getIntent();
Bundle extras = null;
if (null != intent)
{
extras = getIntent().getExtras();
}
if (null != extras)
{
final String[] permissionsToRequest = extras.getStringArray(EXTRA_PERMISSION_REQUESTED_PERMISSIONS);
mPendingRequestCode = extras.getInt(EXTRA_PERMISSION_REQUEST_CODE);
// 判断用户是否之前有拒绝权限申请且勾选了不再提醒选项
if (!PermissionsUtil.isShouldShowRequestPermissionRationale(this, permissionsToRequest)
&& !isHaveFirstRequestPermission(permissionsToRequest))
{
showPermissionDialog(PermissionsActivity.this, mPendingRequestCode, permissionsToRequest);
}
else
{
requestPermissions(permissionsToRequest,mPendingRequestCode);
}
}
}
}
/**
*
* 引导进入应用权限管理页面的dialog
*
* @param activity
* @param permissionsToRequest
*/
private void showPermissionDialog(final Activity activity, final int mPendingRequestCode,
String[] permissionsToRequest)
{
AlertDialog.Builder builder = new Builder(this);
AlertDialog dialog = null;
StringBuilder sb = new StringBuilder(getString(R.string.permissions_tips));
for (int i = 0; i < permissionsToRequest.length; i++)
{
sb.append("\n");
Integer key = PermissionsUtil.PERMISSION_TIPS.get(permissionsToRequest[i]);
if (null != key)
{
String tip = getString(key);
sb.append(tip);
}
}
builder.setMessage(sb.toString());
builder.setTitle(getString(R.string.huaweipay_note));
builder.setPositiveButton(getString(R.string.go_settings), new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
// 打开权限管理器的应用授权activity
openManangePermissionUI(activity, mPendingRequestCode);
dialog.dismiss();
}
});
builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
// 直接回调结果给Permissionsmanager,String[0],int[0]表示本次对于权限无任务修改
onRequestPermissionsResult(mPendingRequestCode, new String[0], new int[0]);
}
});
dialog = builder.create();
dialog.setOnKeyListener(new DialogInterface.OnKeyListener()
{
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event)
{
if (keyCode == KeyEvent.KEYCODE_BACK)
{
dialog.dismiss();
// 直接回调结果给Permissionsmanager,String[0],int[0]表示本次对于权限无任务修改
onRequestPermissionsResult(mPendingRequestCode, new String[0], new int[0]);
}
return false;
}
});
dialog.setCanceledOnTouchOutside(false);
dialog.show();
}
/**
*
* 调起系统应用的权限管理页面,依赖与android.permission.GRANT_RUNTIME_PERMISSIONS权限(
* 正式平台签名apk才会授予)若自身应用没有平台权限,建议引导用户跳转到setting页面即可
*
*/
public void openManangePermissionUI(Activity activity, int mPendingRequestCode)
{
if (null == activity)
{
LogC.w("openManangePermissionUI activity is null ", false);
return;
}
try
{
Intent intent = new Intent("android.intent.action.MANAGE_APP_PERMISSIONS");
intent.putExtra("android.intent.extra.PACKAGE_NAME", activity.getPackageName());
// 屏蔽掉权限管理页面右上角的应用详情按钮
intent.putExtra("hideInfoButton", true);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
// 使用startActivityForResult,避免权限管理器操作未完成,而权限申请结果已回调
activity.startActivityForResult(intent, mPendingRequestCode);
}
catch (ActivityNotFoundException e)
{
LogC.w("start action android.intent.action.MANAGE_APP_PERMISSIONS ActivityNotFoundException error", false);
onRequestPermissionsResult(mPendingRequestCode, new String[0], new int[0]);
}
catch (SecurityException ee)
{
LogC.w("start action android.intent.action.MANAGE_APP_PERMISSIONS java.lang.SecurityException==Permission Denial error", false);
onRequestPermissionsResult(mPendingRequestCode, new String[0], new int[0]);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
// 回调权限管理器的处理结果
onRequestPermissionsResult(requestCode, new String[0], new int[0]);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
{
LogC.i(" permission activity onRequestPermissionsResult ", false);
mPendingRequestCode = INVALID_REQUEST_CODE;
setPermissionCache(permissions, true);
finish();
PermissionsManager.getInstance().onRequestPermissionsResult(requestCode, permissions, grantResults);
}
注 : 对于动态权限,建议应用可以区分一下哪些是必要权限和哪些是非必要权限,比如读写sd卡这个权限完全是非必要的,在Android4.4之后,访问自身应用在sd卡的私有路径不需要任何权限(manifest中都不要去声明),对于非必要权限,可以允许用户继续操作,必要权限被拒绝,可以直接退出,避免处理太多的异常场景。
PS : TabActivity中装载了Activity。在Activity需要的时候申请权限;TabActivity继承onRequestPermissionsResult()方法, Result后通知Activity执行具体任务。
3.2、权限组的概念
定义:同一组的任何一个权限被授权了,其他权限也自动被授权
以下列出的需要手动申请的权限,即危险权限.(默认权限、签名权限 太多不一一列出)
Permission Group | Permissions |
---|---|
android.permission-group.CALENDAR | android.permission.READ_CALENDAR |
android.permission.WRITE_CALENDAR | |
android.permission-group.CAMERA | android.permission.CAMERA |
android.permission-group.CONTACTS | android.permission.READ_CONTACTS |
android.permission.WRITE_CONTACTS | |
android.permission.GET_ACCOUNTS | |
android.permission-group.LOCATION | android.permission.ACCESS_FINE_LOCATION |
android.permission.ACCESS_COARSE_LOCATION | |
android.permission-group.MICROPHONE | android.permission.RECORD_AUDIO |
android.permission-group.PHONE | android.permission.READ_PHONE_STATE |
android.permission.CALL_PHONE | |
android.permission.READ_CALL_LOG | |
android.permission.WRITE_CALL_LOG | |
com.android.voicemail.permission.ADD_VOICEMAIL | |
android.permission.USE_SIP | |
android.permission.PROCESS_OUTGOING_CALLS | |
android.permission-group.SENSORS | android.permission.BODY_SENSORS |
android.permission-group.SMS | android.permission.SEND_SMS |
android.permission.RECEIVE_SMS | |
android.permission.READ_SMS | |
android.permission.RECEIVE_WAP_PUSH | |
android.permission.RECEIVE_MMS | |
android.permission.READ_CELL_BROADCASTS | |
android.permission-group.STORAGE | android.permission.READ_EXTERNAL_STORAGE |
android.permission.WRITE_EXTERNAL_STORAGE |
官方:https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous
问题4、网络请求缓存处理,okhttp如何处理网络缓存的
#######4.1、网络请求缓存处理
在Android 2.2版本之前,HttpClient拥有较少的bug,因此使用它是最好的选择。
而在Android 2.3版本及以后,HttpURLConnection则是最佳的选择。它的API简单,体积较小,因而非常适用于Android项目。压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用。对于新的应用程序应该更加偏向于使用HttpURLConnection,因为在以后的工作当中我们也会将更多的时间放在优化HttpURLConnection上面。
HttpURLConnection在 Android 4.0版本中,添加了一些响应的缓存机制,适配以前的版本通常我们就可以使用反射的方式来启动响应缓存功能。下面的示例代码展示了如何在Android 4.0及以后的版本中去启用响应缓存的功能,同时还不会影响到之前的版本:
private void enableHttpResponseCache() {
try {
long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
File httpCacheDir = new File(getCacheDir(), "http");
Class.forName("android.net.http.HttpResponseCache")
.getMethod("install", File.class, long.class)
.invoke(null, httpCacheDir, httpCacheSize);
} catch (Exception httpResponseCacheNotAvailable) {
}
}
你也应该同时配置一下你的Web服务器,在HTTP响应上加入缓存的消息头。
HTTP缓存机制
缓存对于移动端是非常重要的存在。
减少请求次数,减小服务器压力.
本地数据读取速度更快,让页面不会空白几百毫秒。
在无网络的情况下提供数据。
缓存一般由服务器控制(通过某些方式可以本地控制缓存,比如向过滤器添加缓存控制信息)。通过在请求头添加下面几个字端:
Request
请求头字段 | 意义 |
---|---|
If-Modified-Since: Sun, 03 Jan 2016 03:47:16 GMT | 缓存文件的最后修改时间 |
If-None-Match: "3415g77s19tc3:0" | 缓存文件的Etag(Hash)值 |
Cache-Control: no-cache | 不使用缓存 |
Pragma: no-cache | 不使用缓存 |
Response
响应头字段 | 意义 |
---|---|
Cache-Control: public | 响应被共有缓存,移动端无用 |
Cache-Control: private | 响应被私有缓存,移动端无用 |
Cache-Control:no-cache | 不缓存 |
Cache-Control:no-store | 不缓存 |
Cache-Control: max-age=60 | 60秒之后缓存过期(相对时间) |
Date: Sun, 03 Jan 2016 04:07:01 GMT | 当前response发送的时间 |
Expires: Sun, 03 Jan 2016 07:07:01 GMT | 缓存过期的时间(绝对时间) |
Last-Modified: Sun, 03 Jan 2016 04:07:01 GMT | 服务器端文件的最后修改时间 |
ETag: "3415g77s19tc3:0" | 服务器端文件的Etag[Hash]值 |
正式使用时按需求也许只包含其中部分字段。客户端要根据这些信息储存这次请求信息。然后在客户端发起请求的时候要检查缓存。遵循下面步骤:
浏览器缓存机制
注意服务器返回304意思是数据没有变动滚去读缓存信息。
#######4.2、okhttp如何处理网络缓存的
Volley在功能拓展性上始终无法与OkHttp相比。Volley停止了更新,而OkHttp得到了官方的认可,并在不断优化。
OkHttp用法很友好的API与详尽的文档。这篇文章也写的很详细了。OkHttp使用Okio进行数据传输。都是Square家的。但并不是直接用OkHttp。Square公司还出了一个Retrofit库配合OkHttp战斗力翻倍。 Retrofit2.0教程。Retrofit最强大的地方在于支持RxJava。就像我上图中返回的是一个Observable。RxJava上手难度比较高,但用过就再也离不开了。Retrofit+OkHttp+RxJava配合框架打出成吨的输出,这里不再多说。
三级缓存
网上常说三级缓存--服务器,文件,内存。不过我觉得服务器不算是一级缓存,那就是数据源嘛。
内存缓存首先内存缓存使用LruCache。LRU是Least Recently Used 近期最少使用算法,这里确定一个大小,当Map里对象大小总和大于这个大小时将使用频率最低的对象释放。我将内存大小限制为进程可用内存的1/8.内存缓存里读得到的数据就直接返回,读不到的向硬盘缓存要数据。
硬盘缓存硬盘缓存使用DiskLruCache。这个类不在API中。得复制使用。看见LRU就明白了吧。我将硬盘缓存大小设置为100M。
翻译:
定义:
解释:
Android访问网络,使用HttpURLConnection还是HttpClient?
关于Handler原理一篇不错的文章
问题5、图片加载库相关,bitmap如何处理大图,如一张30M的大图,如何预防OOM
翻译:
定义:
解释:
问题6、进程保活
翻译:
定义:
解释:
问题7、listview图片加载错乱的原理和解决方案
翻译:
定义:
解释:
问题8、https相关,如何验证证书的合法性,https中哪里用了对称加密,哪里用了非对称加密,对加密算法(如RSA)等是否有了解
翻译:
定义:
解释:
问题
翻译:
定义:
解释:
总结
加我微信 共同探讨.
纯纯干货兴味索然
感谢阅读收获归你
不妥之处敬请指教
网友评论