一,前期基础知识储备
二月中旬返工,一切平安。感恩!
复工后梳理一下最近的一些东西,做成一个记录:
1)API24之上的保存分享,谷歌弃用了此前的很多的存储API,这里拿一个项目里使用的做示例使用;另外就是面向API24的智能终端(现在几乎没有低于24的了吧),分享功能得依靠fileprovider来实现。
2)RecyclerView万能适配器的使用,这里使用的不是此前博客里的那个,而是鸿洋大神的万能适配器,搭配构造基类。由于是多个Fragment的项目,所以此种构造方法非常合适。
3)这里给出AsyncTask的弱引用的写法,包括AsyncTask中加入接口回调,既能防止内存泄漏,还能进行外部通信。
二,上代码,具体实现
1. API24之上保存分享功能
① 保存图片至本地,使用获取外部存储路径的API,保存在以包名命名的文件夹下。删除应用时,会一起删除。
if (activity != null) {
path = activity.getExternalFilesDir("ColorFy").getAbsolutePath();
Log.d(TAG, "doInBackground: path,," + path);
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
File file = new File(dir, imgName);
Log.d(TAG, "doInBackground: file,," + file.getAbsolutePath());
FileOutputStream out = null;
try {
out = new FileOutputStream(file);
saveBitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
out.close();
return true;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "doInBackground: wrong3,," + e.getMessage());
}
}
return false;
}
保存路径为:
② API24之上的分享功能。使用fileprovider实现
1)res下新建xml文件夹,写入一份xml文件;
<?xml version="1.0" encoding="utf-8"?>
<resource>
<external-path
name="images"
path="" />
</resource>
注意,本例中分享的是图片,分享途径是各类app,所以写法中的“name”和“path”要注意,是有其他情况的。
2)AndroidManifest.xml文件中注册provider;
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.paint.color.paint.number.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths_share_img" />
</provider>
注意以包名命名,同时记得指向上一步中命名好的文件。
③ 分享的代码;
public static void sharePic(Context context, String picFilePath) {
File shareFile = new File(picFilePath);
if (!shareFile.exists()) return;
Intent intent = new Intent(Intent.ACTION_SEND);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri contentUri = FileProvider.getUriForFile(context,
context.getPackageName() + ".fileprovider", shareFile);
intent.putExtra(Intent.EXTRA_STREAM, contentUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(shareFile));
}
intent.setType("image/*");
Intent chooser = Intent.createChooser(intent, context.getString(R.string.share));
if (intent.resolveActivity(context.getPackageManager()) != null) {
context.startActivity(chooser);
}
}
外部调用时传入上下文和图片的本地存储路径即可。
分享功能的本质是使用Uri去传递数据,而Android7.0之后,构造Uri需要使用到FileProvider;举一个例子,通过Uri的形式打开本地path的文件。
private void initNewCrop() {
Uri imageUri_1 = resToUri(R.drawable.photo2);
/*android.resource://camera.editor.art.editor/2131165305*/
Log.d(TAG, "initNewCrop 00: " + imageUri_1);
String path = "/storage/emulated/0/Android/data/poster.maker.art.design/files/tempates/87747047405088370535/1615944854941.png";
Log.d(TAG, "initNewCrop 11: " + path);
Uri imageUri_2 = Uri.parse(path);
/*/storage/emulated/0/Android/data/poster.maker.art.design/files/tempates/87747047405088370535/1615944854941.png*/
Log.d(TAG, "initNewCrop 22: " + imageUri_2);
File f = new File(path);
Uri imageUri = null;
if (f != null && f.exists() && f.isFile()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
imageUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".fileprovider", f);
} else {
imageUri = Uri.fromFile(f);
}
}
/*content://camera.editor.art.editor.fileprovider/external_files/Android/data/poster.maker.art.design/files/tempates/87747047405088370535/1615944854941.png*/
Log.d(TAG, "initNewCrop 33: " + imageUri);
}
private Uri resToUri(@DrawableRes int res) {
return Uri.parse(String.format(
Locale.US, "android.resource://%s/%d",
getPackageName(),
res));
}
1)可以直接将res文件夹下面的一张本地图片通过Uri的方式加载,得到的结果:
android.resource://camera.editor.art.editor/2131165305
2)如果是本地SDCard下的一张图片,可以通过FileProvider的方式加载,得到的结果:
content://camera.editor.art.editor.fileprovider/external_files/Android/data/poster.maker.art.design/files/tempates/87747047405088370535/1615944854941.png
这里需要注意的是,不能直接使用Uri.parse方法去解析,这样得到的结果没有用。
另外在使用之前,要先配置好FileProvider。
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="camera.editor.art.editor.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path" />
</provider>
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="save_bitmap" path="DCIM/PosterMaker" />
<external-path name="external_files" path="."/>
</paths>
2. 鸿洋万能适配器+构造基类
达到的效果如下图:
从Animal分类开始,共有10个分类,每个分类都是由一个Fragment构成,同时每个碎片的组成相同,并且列表里填充都是图片,每个分类的图片资源不一样。本例中图片资源放在assets文件夹下。
1)所有Fragment的共有基类,主要负责,引入布局,初始化控件和绑定ButterKnife;
public abstract class BaseFragment extends Fragment {
private static final String TAG = "BaseFragment";
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View root = inflater.inflate(getLayoutResId(), null);
ButterKnife.bind(this, root);
return root;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
init();
}
// protected void init(){}
protected abstract void init();
protected abstract int getLayoutResId();
/*应用进程被杀死时 保存状态*/
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
// super.onSaveInstanceState(outState);
}
@Override
public void onResume() {
super.onResume();
MobclickAgent.onResume(getActivity());
}
@Override
public void onPause() {
super.onPause();
MobclickAgent.onPause(getActivity());
}
}
定义两个抽象方法,引入布局和初始化相关数据内容。
2)定义Fragment的直接父类,使用万能适配器;
public abstract class BaseColorFyFragment extends BaseFragment {
private static final String TAG = "BaseColorFyFragment";
@BindView(R.id.rv)
ChooseCenterRecyclerview recyclerView;
private CardView homerootCard;
public ArrayList datas = new ArrayList();
public CommonAdapter<PictureBean.Picture> adapter;
public abstract void getDatas(ArrayList datas);
@Override
protected int getLayoutResId() {
return R.layout.fragment_base_color_tab;
}
@Override
protected void init() {
recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 2));
adapter = new CommonAdapter<PictureBean.Picture>(getActivity(), R.layout.fragment_recy_color_item, datas) {
@Override
protected void convert(ViewHolder holder, PictureBean.Picture bean, int position) {
AppCompatImageView imageView = holder.getView(R.id.iv_effectmore_cover);
String name = bean.getUri();
String[] strs = name.split("_");
String resType = strs[0];
String url = " ";
if (resType.equals("abstract")) {
url = PaintApplication.SHOW_ASSETS_ABSTRACT + bean.getUri();
} else if (resType.equals("animal")) {
url = PaintApplication.SHOW_ASSETS_ANIMAL + bean.getUri();
} else if (resType.equals("cute")) {
url = PaintApplication.SHOW_ASSETS_CUTE + bean.getUri();
} else if (resType.equals("art")) {
url = PaintApplication.SHOW_ASSETS_ART + bean.getUri();
} else if (resType.equals("doodle")) {
url = PaintApplication.SHOW_ASSETS_DOODLE + bean.getUri();
} else if (resType.equals("fashion")) {
url = PaintApplication.SHOW_ASSETS_FASHION + bean.getUri();
} else if (resType.equals("floral")) {
url = PaintApplication.SHOW_ASSETS_FLORAL + bean.getUri();
} else if (resType.equals("mandala")) {
url = PaintApplication.SHOW_ASSETS_MANDALA + bean.getUri();
} else if (resType.equals("modern")) {
url = PaintApplication.SHOW_ASSETS_MODERN + bean.getUri();
} else if (resType.equals("place")) {
url = PaintApplication.SHOW_ASSETS_PLACE + bean.getUri();
}
/*这里传过来的只是assets下的文件名;是否本地存储里? 需要再做判断 判断完毕 再走分支进行图片展示*/
RequestOptions options = new RequestOptions()
.centerCrop().skipMemoryCache(false).diskCacheStrategy(DiskCacheStrategy.NONE)
.placeholder(R.color.pureWhite)
.error(R.drawable.ic_no_network_error);
if (FileUtils.hasSavedFile(mContext, name)) {
String path = FileUtils.getSDPath(mContext, name);
if (path != null) {
Glide.with(mContext)
.load(path)
.apply(options)
.into(imageView);
} else {
Glide.with(mContext)
.load("file:///android_asset/" + url)
.apply(options)
.into(imageView);
}
} else {
Glide.with(mContext)
.load("file:///android_asset/" + url)
.apply(options)
.into(imageView);
}
}
};
recyclerView.setAdapter(adapter);
adapter.setOnItemClickListener(new CommonAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
// 首页模板点击时 做UI变化 先缩小后放大
if (holder.getLayoutPosition() == position) {
homerootCard = holder.itemView.findViewById(R.id.home_root_card);
ObjectAnimator scaleAnimatorX = ObjectAnimator.ofFloat(homerootCard, "scaleX", 1f, 0.95f);
ObjectAnimator scaleAnimatorSX = ObjectAnimator.ofFloat(homerootCard, "scaleX", 0.95f, 1f);
ObjectAnimator scaleAnimatorY = ObjectAnimator.ofFloat(homerootCard, "scaleY", 1f, 0.95f);
ObjectAnimator scaleAnimatorSY = ObjectAnimator.ofFloat(homerootCard, "scaleY", 0.95f, 01f);
scaleAnimatorSY.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
onRecyItemClick.onClickRecyItem(position);
}
});
AnimatorSet animSet = new AnimatorSet();
animSet.play(scaleAnimatorX).with(scaleAnimatorY).before(scaleAnimatorSX).before(scaleAnimatorSY);
animSet.setDuration(200L);
animSet.start();
}
}
@Override
public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
return false;
}
});
}
public interface OnRecyItemClick {
void onClickRecyItem(int pos);
}
public OnRecyItemClick onRecyItemClick;
public void setOnRecyItemClick(OnRecyItemClick onRecyItemClick) {
this.onRecyItemClick = onRecyItemClick;
}
/*刷新列表*/
@Override
public void onResume() {
super.onResume();
onItemUpdate();
}
public void onItemUpdate() {
if (adapter != null) {
adapter.notifyDataSetChanged();
}
}
}
该类继承自BaseFragment,写入自身的布局文件,然后做好初始化。由于所有Fragment共同使用,所以为适配器写入一个布局文件即可,然后做好适配器和列表的处理,同时写入抽象方法,用以传输数据。其他继承自该类的子类,实现父类定义好的抽象方法,传入需要的集合数据即可。
3)继承自BaseColorFyFragment的子类写法;
public class ColorFragment1 extends BaseColorFyFragment {
public static ColorFragment1 newInstance() {
return new ColorFragment1();
}
@Override
public void getDatas(ArrayList list) {
datas = list;
}
}
Activity中实例化该子类,然后实例调用getDatas()方法,传入数据即可。
3. AsyncTask最正确写法,防止内存泄漏
① 弱引用处理;
final WeakReference<FingerPaintActivity> paintWeakReference;
public SaveImageTask(Bitmap saveBitmap, String name, FingerPaintActivity activity) {
this.saveBitmap = saveBitmap;
this.imgName = name;
paintWeakReference = new WeakReference<>(activity);
}
以弱引用的形式传入上下文,接下来的代码中需要的地方都是使用该对象进行处理,防止出现内存泄漏。
② AsyncTask中使用接口,与外部环境进行通信;
@Override
protected void onPostExecute(Boolean result) {
if (result) {
super.onPostExecute(result);
FingerPaintActivity activity = paintWeakReference.get();
if (activity != null && !activity.isFinishing()) {
if (onSaveFinishListener != null) {
onSaveFinishListener.onSaveFinish(path + "/" +imgName);
}
} else {
if (onSaveFinishListener != null) {
onSaveFinishListener.onSaveFinish(null);
}
}
} else {
if (onSaveFinishListener != null) {
onSaveFinishListener.onSaveFinish(null);
}
}
}
public interface OnSaveFinishListener {
/**
* @param path if null save failed
*/
void onSaveFinish(String path);
}
public OnSaveFinishListener getOnSaveFinishListener() {
return onSaveFinishListener;
}
public void setOnSaveSuccessListener(OnSaveFinishListener onSaveFinishListener) {
this.onSaveFinishListener = onSaveFinishListener;
}
这里在AsyncTask中定义好接口,在耗时方法执行完毕的回调中调用,监听耗时方法的回调结果,用以外部环境处理。
完整代码如下:是一个存储图片的AsyncTask例子。
public class SaveImageTask extends AsyncTask<Void, Void, Boolean> {
private static final String TAG = "SaveImageTask";
private OnSaveFinishListener onSaveFinishListener;
private Bitmap saveBitmap;
private String imgName;
private String path;
final WeakReference<FingerPaintActivity> paintWeakReference;
public SaveImageTask(Bitmap saveBitmap, String name, FingerPaintActivity activity) {
this.saveBitmap = saveBitmap;
this.imgName = name;
paintWeakReference = new WeakReference<>(activity);
}
@Override
protected Boolean doInBackground(Void... voids) {
FingerPaintActivity activity = paintWeakReference.get();
if (activity != null) {
path = activity.getExternalFilesDir("ColorFy").getAbsolutePath();
Log.d(TAG, "doInBackground: path,," + path);
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
File file = new File(dir, imgName);
Log.d(TAG, "doInBackground: file,," + file.getAbsolutePath());
FileOutputStream out = null;
try {
out = new FileOutputStream(file);
saveBitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
out.close();
return true;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "doInBackground: wrong3,," + e.getMessage());
}
}
return false;
}
return false;
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
super.onPostExecute(result);
FingerPaintActivity activity = paintWeakReference.get();
if (activity != null && !activity.isFinishing()) {
if (onSaveFinishListener != null) {
onSaveFinishListener.onSaveFinish(path + "/" +imgName);
}
} else {
if (onSaveFinishListener != null) {
onSaveFinishListener.onSaveFinish(null);
}
}
} else {
if (onSaveFinishListener != null) {
onSaveFinishListener.onSaveFinish(null);
}
}
}
public interface OnSaveFinishListener {
/**
* @param path if null save failed
*/
void onSaveFinish(String path);
}
public OnSaveFinishListener getOnSaveFinishListener() {
return onSaveFinishListener;
}
public void setOnSaveSuccessListener(OnSaveFinishListener onSaveFinishListener) {
this.onSaveFinishListener = onSaveFinishListener;
}
}
外部调用该类示例如下:
private void saveToLocal() {
MyProgressDialog.show(FingerPaintActivity.this, null, getString(R.string.savingimage));
SaveImageTask saveImageTask = new SaveImageTask(colourImageView.getmBitmap(), imgName, FingerPaintActivity.this);
if (fromSDcard) {
saveImageTask.execute();
} else {
saveImageTask.execute();
}
saveImageTask.setOnSaveSuccessListener(new SaveImageTask.OnSaveFinishListener() {
@Override
public void onSaveFinish(String path) {
MyProgressDialog.DismissDialog();
if (path == null) {
Toast.makeText(FingerPaintActivity.this, getString(R.string.saveFailed), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(FingerPaintActivity.this, getString(R.string.saveSuccess) + path, Toast.LENGTH_SHORT).show();
}
}
});
}
实现AsyncTask中的接口,得知耗时操作的结果,利用回调做UI变化。
三,最后祝愿大家复工顺利,一切平安
2月28号人民日报整理的,现在全球疫情还是比较复杂的,大家不要掉以轻心了。