获取系统时间的24小时制与12小时制
最近在做项目的时候发生了一点错误,服务器端是24小时制的时间,而本地数据库则是12小时制的时间
1、获取24小时制的时间
public static String showDate() {
SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = sDateFormat.format(new Date());
return date;
}
2、获取12小时制的时间
public static String showDate() {
SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String date = sDateFormat.format(new Date());
return date;
}
两者区别就在于HH和hh,可以看下文档的其他示例
ViewPager中的Fragment在切换时不重新加载
最近在做项目的时候发生了一点错误,在ViewPager中的Fragment中切换到第三页重新切回第一页时,会重新加载第一页,显得页面一直在加载,降低了用户体验,因为viewPager会事先加载好当前页的前后两页,也就是到了第三页的时候,第一页已经被销毁了,回到第二页的时候会重新创建,解决方法如下:
//默认是1
mViewPager.setOffscreenPageLimit(3);
优化购物车,选中物品时价钱相加减的精确运算
做到商品购物车模块的时候,发现价钱的加减并不能单纯的使用+、-来实现,由于我们的价格都是double类型的,如10.24元,相互加减的时候会出现20.45555555的情况,所以我们需要使用到API中BigDecimal这个类进行包装,然后运算
public void selectSingle() {
//创建BigDecimal对象
BigDecimal bj1 = new BigDecimal(Double.toString(money1));
BigDecimal bj2 = new BigDecimal(Double.toString(money2));
if (selected_Id.contains(shop.get_id())) {//相减
sum_money = bj1.subtract(bj2).doubleValue();
} else {//相加
sum_money = bj1.add(bj2).doubleValue();
}
}
效果如下:可以看到价钱已经是正常的加减了
启动模式的细节
情景发生:当我们应用程序按Home键返回桌面时,再次点击应用程序不能恢复到离开时的Activity
解决方案:只要该栈中之前的任何一个Activity在manifest文件中定义了启动模式为singleTask,那么再次点击应用时会启动第一个Activity,只要去除之前Activity中singleTask属性就能恢复回离开时的Activity
通过广播监听网络的变化清况
通常在使用软件的时候会出现网路变化的情况,比如Wifi断线导致使用流量上网,这个时候作为我们的软件就必须通知用户在使用流量上网。首先,Manifests中注册网络变化情况的广播
<!-- 广播 -->
<receiver android:name=".Receiver.NetReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
接着,创建该Receiver,根据网络的变化情况进行相对应的提醒。这里当用户打开或关闭Wifi和移动数据时,该广播可以收到
public class NetReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//网络广播接收者
if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
ConnectivityManager connectivityManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeInfo = connectivityManager.getActiveNetworkInfo();
NetworkInfo netInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
NetworkInfo wifiInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (activeInfo != null) {
//网络可用
if (activeInfo.getState().equals(NetworkInfo.State.CONNECTED)) {
//判断移动数据
if (netInfo.getState().equals(NetworkInfo.State.CONNECTED)) {
Toast.makeText(context, "您正在使用移动数据", Toast.LENGTH_SHORT).show();
}
//判斷Wifi數據
if (wifiInfo.getState().equals(NetworkInfo.State.CONNECTED)) {
Toast.makeText(context, "您正在使用Wifi数据", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(context, "请检查网络是否已联网", Toast.LENGTH_SHORT).show();
}
}
}
}
}
Fragment的懒加载基类
在项目中添加该基类,新的Fragment继承该BaseFragment就可以轻松实现Fragment的懒加载
public abstract class BaseFragment extends Fragment implements View.OnClickListener {
private boolean isPrepared;
private boolean isVisible;
public abstract View initViews(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);
public abstract void initData();
public abstract void initListener();
public abstract void processClick(View v);
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getUserVisibleHint()) {
isVisible = true;
lazyLoad();
} else {
isVisible = false;
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return initViews(inflater, container, savedInstanceState);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
isPrepared = true;
lazyLoad();
}
/**
* 懒加载
*/
private void lazyLoad() {
if (!isVisible || !isPrepared) {
return;
}
//加载数据
initData();
initListener();
}
@Override
public void onClick(View v) {
processClick(v);
}
}
Context的应用场景
解决PhotoView在ViewPager中多点触摸时,报错崩溃的方法
使用PhotoView在ViewPager中展示出多图操作,如果操作过于频繁,那么下面这个错误
java.lang.IllegalArgumentException: pointerIndex out of range
其解决方法就是在PhotoView所在的Activity中添加下面的处理即可
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
try {
return super.dispatchTouchEvent(ev);
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
}
return false;
}
使用Html.fromhtml显示图片
在项目开发中,后台常常会使用这样的编辑器来编辑图文
返回在android端是一串html字符串,这个时候单单靠Html.fromhtml是显示不出来图片的,图片将会被小方格替代,下面是我已经处理好的一个类,该类只要Html.fromhtml识别到图片就会回调该类的getDrawable()方法。该类兼容网络图片和服务器图片,兼容以下两种格式
服务器图片url:/upload/2017/05/04/590aaae1b0d4a.png
网络图片url:https://p.ssl.qhimg.com/t0140ba2595bb1b8c4a.png
/**
* @author 许英俊 2017/6/7
*/
public class ImageGetterImpl implements Html.ImageGetter {
private int width, height;
private TextView tv;
private String html;
private File file;
public ImageGetterImpl(TextView tv, String html, int width, int height) {
this.tv = tv;
this.html = html;
this.width = width - 80;
this.height = height;
}
@Override
public Drawable getDrawable(String source) {
Drawable drawable = null;
// 区分网络图片和自己服务器图片,如果是服务器图片,则加个根目录
if (!source.startsWith("http")) {
source = RequestCenter.ROOT_URL + source;
}
//获取图片后缀名作为文件名
String[] fileName = source.split("/");
file = new File(Environment.getExternalStorageDirectory(), fileName[fileName.length - 1]);
// 判断是否以http开头
if (source.startsWith("http")) {
// 判断路径是否存在
if (file.exists()) {
// 存在即获取drawable
drawable = Drawable.createFromPath(file.getAbsolutePath());
// 根据屏幕的宽高比等于图片的宽高比
height = (width) * drawable.getIntrinsicHeight() / drawable.getIntrinsicWidth();
drawable.setBounds(0, 0, width, height);
} else {
// 不存在即开启异步任务加载网络图片
AsyncLoadNetworkPic networkPic = new AsyncLoadNetworkPic();
networkPic.execute(source);
}
}
return drawable;
}
public class AsyncLoadNetworkPic extends AsyncTask<String, Integer, Void> {
@Override
protected Void doInBackground(String... params) {
// 加载网络图片
loadNetPic(params);
return null;
}
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
// 当执行完成后再次为其设置一次
tv.setText(Html.fromHtml(html, new ImageGetterImpl(tv, html, width, height), null));
}
/**
* 加载网络图片
*/
private void loadNetPic(String... params) {
String path = params[0];
InputStream in = null;
FileOutputStream out = null;
try {
URL url = new URL(path);
HttpURLConnection connUrl = (HttpURLConnection) url.openConnection();
connUrl.setConnectTimeout(10000);
connUrl.setRequestMethod("GET");
if (connUrl.getResponseCode() == 200) {
in = connUrl.getInputStream();
out = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
} else {
Log.e("TAG", connUrl.getResponseCode() + "");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
对应的在安卓端使用三个参数的方法进行解析html字符串,其中DensityUtils两个方法表示获取屏幕的宽和高
tv_content.setText(Html.fromHtml(data.getContent(),
new ImageGetterImpl(tv_content, data.getContent(),
DensityUtils.getDisplayWidth(this),
DensityUtils.getDisplayHeight(this)), null));
在手机的显示效果如下
网络缓存
打开缓存
try {
File httpCacheDir = new File(context.getCacheDir(), "http");
long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
HttpResponseCache.install(httpCacheDir, httpCacheSize);
} catch (IOException e) {
Log.i(TAG, "HTTP response cache installation failed:" + e);
}
清除缓存
HttpResponseCache cache = HttpResponseCache.getInstalled();
if (cache != null) {
cache.delete();
}
如果对于某个请求我不想用cache,则可以使用下面代码
connection.addRequestProperty("Cache-Control", "no-cache");
我想用cache但是又怕服务器的数据已经发生改变了,则可以使用下面代码
connection.addRequestProperty("Cache-Control", "max-age=0");
由于HttpResponseCache的API是Android4.0提供的,兼容Android4.0以下版本
try {
File httpCacheDir = new File(context.getCacheDir(), "http");
long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
Class.forName("android.net.http.HttpResponseCache")
.getMethod("install", File.class, long.class)
.invoke(null, httpCacheDir, httpCacheSize);
} catch (Exception httpResponseCacheNotAvailable) {
}
}
屏幕像素的适配
一般我们开发的app不会去适配小屏幕的手机,普遍适配1080 x 1920的手机,通过图中可以看出其density密度为480,那么通过dp与px的运算公式(1dp x 像素密度 / 160 = 实际像素数)计算得出,dp与px的比例是1:3,1080 x 1920的手机就需要在UI设计图中,将px除于3,即可获得dp值
模拟Json数据接口
使用EasyMock的Api,特别简单,只需要输入模拟的Json数据,访问其Api就能获取Json数据
安卓7.0照相机的适配
在安卓7.0中,官方对照相机的启动做了改变,开发者也要进行适配
1、首先需要在Manifest中声明Provider,注意:如果你引入的第三方图片选择库有开启的相机的Provider,那么此时会冲突
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="{你的应用的包名}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
2、创建xml目录和创建file_path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="Android/data/com.handsome.teacherproject/files/Pictures" />
</paths>
3、在代码中启动我们的相机,注意:只兼容7.0以上的相机,其他相机采用另一方法
private static int REQUEST_IMAGE_CAPTURE = 0x01;
//拍照后照片的路径
private String mCurrentPhotoPath;
/**
* 开启系统相机
*/
private void startPhotoCapture() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//判断是否有相机应用
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = null;
try {
//创建临时图片文件
photoFile = createImageFile();
} catch (IOException ex) {
ex.printStackTrace();
}
if (photoFile != null) {
//FileProvider使用 content:// Uri 代替了 file:// Uri
Uri photoURI;
if (Build.VERSION.SDK_INT >= 24) {
photoURI = FileProvider.getUriForFile(this, "{你的应用的包名}.fileprovider", photoFile);
} else {
photoURI = Uri.fromFile(photoFile);
}
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}
}
}
/**
* 创建Image的临时文件
*/
private File createImageFile() throws IOException {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.CHINA).format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
//.getExternalFilesDir()方法可以获取到 SDCard/Android/data/你的应用的包名/files/ 目录,一般放一些长时间保存的数据
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
//创建临时文件,文件前缀不能少于三个字符,后缀如果为空默认未".tmp"
File image = File.createTempFile(
imageFileName, /* 前缀 */
".jpg", /* 后缀 */
storageDir /* 文件夹 */
);
mCurrentPhotoPath = image.getAbsolutePath();
return image;
}
/*
* 在返回的结果中拿到图片
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
//mCurrentPhotoPath,这个全局变量就是我们拍照后的文件路径,直接想怎么用就怎么用
//这个是高清图,不是缩略图,所以要上传服务器的时候记得压缩后上传
}
}
封装UniversalImageLoader
public class ImageManager {
private static final int THREAD_COUNT = 2;
private static final int PRIORITY = 2;
private static final int MEMORY_CACHE_SIZE = 2 * 1024 * 1024;
private static final int DISK_CACHE_SIZE = 30 * 1024 * 1024;
private static final int CONNECT_TIME_OUT = 5 * 1000;
private static final int READ_TIME_OUT = 30 * 1000;
private static volatile com.hensen.shixiongsdk.ImageLoader.ImageManager ImageManager;
private ImageLoader imageLoader = null;
private ImageManager(Context context) {
initDefaultConfiguration(context);
}
public static com.hensen.shixiongsdk.ImageLoader.ImageManager getInstance(Context context) {
if (ImageManager == null) {
synchronized (com.hensen.shixiongsdk.ImageLoader.ImageManager.class) {
if (ImageManager == null) {
ImageManager = new ImageManager(context);
}
}
}
return ImageManager;
}
private void initDefaultConfiguration(Context context) {
ImageLoaderConfiguration configuration = new ImageLoaderConfiguration
.Builder(context)
.threadPoolSize(THREAD_COUNT)
.threadPriority(Thread.NORM_PRIORITY - PRIORITY)
.denyCacheImageMultipleSizesInMemory()
.memoryCacheSize(MEMORY_CACHE_SIZE)
.diskCacheSize(DISK_CACHE_SIZE)
.diskCacheFileNameGenerator(new Md5FileNameGenerator())
.tasksProcessingOrder(QueueProcessingType.LIFO)
.defaultDisplayImageOptions(getDefaultOptions())
.imageDownloader(new BaseImageDownloader(context, CONNECT_TIME_OUT, READ_TIME_OUT))
.build();
com.nostra13.universalimageloader.core.ImageLoader.getInstance().init(configuration);
imageLoader = com.nostra13.universalimageloader.core.ImageLoader.getInstance();
}
private DisplayImageOptions getDefaultOptions() {
DisplayImageOptions defaultOptions = new DisplayImageOptions
.Builder()
.cacheOnDisk(true)
.cacheInMemory(true)
.considerExifParams(true)
.imageScaleType(ImageScaleType.IN_SAMPLE_INT)
.bitmapConfig(Bitmap.Config.RGB_565)
.decodingOptions(new BitmapFactory.Options())
.resetViewBeforeLoading(true)
.build();
return defaultOptions;
}
public void displayImage(String url, ImageView imageView,
DisplayImageOptions options,
ImageLoadingListener loadingListener) {
imageLoader.displayImage(url, imageView, options, loadingListener);
}
public void displayImage(String url, ImageView imageView,
ImageLoadingListener loadingListener) {
displayImage(url, imageView, null, loadingListener);
}
public void displayImage(String url, ImageView imageView) {
displayImage(url, imageView, null);
}
}
启动页面优化
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1、ViewStub优化
viewStub = (ViewStub)findViewById(R.id.content_viewstub);
//判断当窗体加载完毕的时候,立马再加载真正的布局进来
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
mHandler.post(new Runnable() {
@Override
public void run() {
viewStub.inflate();
}
} );
}
});
//2、判断当窗体加载完毕的时候执行,延迟一段时间执行闪屏页面动画并关闭
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
mHandler.postDelayed(new DelayRunnable() ,2000);
}
});
}
IdleHandler的使用
-
问题:在App中,需要在空闲的时候执行一些上报操作,或者后台更新等等操作的时候
-
解决:使用IdleHandler,其调用时机是在线程空闲的时候,可以执行回调中的方法
queueIdle
,一般实现线程空闲的操作
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
/**
* @return true,表示要保留保留,代表不移除这个idleHandler,可以反复执行
* @return false,代表执行完毕之后就移除这个idleHandler, 也就是只执行一次
*/
@Override
public boolean queueIdle() {
return false;
}
});
ListView覆盖Item的点击事件
-
问题:自己定义listview,自己定义的Adapter,在adapter中存在自己定义的Button,CheckBox等子控件,将会发生点击每一个item的时候没有反应,无法获取的焦点。原因是这些子控件会将焦点获取到,item本身的点击没有响应
-
解决:使用descendantFocusability,该属性是当一个view获取焦点时,定义viewGroup和其子控件两者之间的关系
- beforeDescendants:viewgroup会优先其子类控件而获取到焦点
- afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点
- blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点
通常我们用到的是第三种,即在Item根布局加上android:descendantFocusability="blocksDescendants"
的属性
自定义异常性能优化
-
问题:在自定义
Throwable
或者是Error
类的时候,在创建当前异常类的时候,默认情况下该父类会进行扒栈操作,此操作很耗性能,由于是自定义的异常类,经常知道自己想要输出什么信息的message
字段,而不需要扒栈操作 -
解决:重写
fillInStackTrace()
方法,让自定义的异常类不再会进行扒栈操作,返回当前的对象,从而提高性能
private class _Thread extends Throwable {
@Override
@NonNull
public Throwable fillInStackTrace() {
return this;
}
}
在string.xml自定义字体颜色和大小
-
问题:平时我们用textView设置富文本的时候需要通过Spannable去对文字进行设计,但如果是简单的改大小和颜色这块,有没有更好更简洁的办法做到
-
解决:在
string.xml
中是可以做得到的,利用安卓提供的CDATA
标签
//string.xml定义
<string name="text">
<![CDATA[直播天数:<font color=\'#ff0000\' size="20">%d%%</font>]]>
<![CDATA[直播时长:<font color=\'#0000ff\' size="40">%d天</font>]]>
</string>
在代码上的使用
textview.setText(Html.fromHtml(getResources.getString(R.string.text)));
ListView根据数据更新高度
-
问题:由于用PopupWindow去承载ListView,并且复用了PopupWindow对象去动态更新ListView数据,导致数据变长了,但是UI还是短的
-
解决:每次刷新数据后,还要调用一次动态刷新ListView高度
public static void setListViewHeightBasedOnChildren(ListView listView) {
// 获取ListView对应的Adapter
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
return;
}
int totalHeight = 0;
for (int i = 0, len = listAdapter.getCount(); i < len; i++) { // listAdapter.getCount()返回数据项的数目
View listItem = listAdapter.getView(i, null, listView);
listItem.measure(0, 0); // 计算子项View 的宽高
totalHeight += listItem.getMeasuredHeight(); // 统计所有子项的总高度
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
// listView.getDividerHeight()获取子项间分隔符占用的高度
// params.height最后得到整个ListView完整显示需要的高度
listView.setLayoutParams(params);
}
数据更新后调用
if (mAdapter != null) {
mAdapter.setInfo(giftInfo);
}
if (mListView != null) {
setListViewHeightBasedOnChildren(mListView);
mListView.invalidate();
}