2020 二月杂记 API24之上保存分享功能;鸿洋万能适配器+构造基类;AsyncTask最正确写法,防止内存泄漏。

一,前期基础知识储备

二月中旬返工,一切平安。感恩!

复工后梳理一下最近的一些东西,做成一个记录:

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号人民日报整理的,现在全球疫情还是比较复杂的,大家不要掉以轻心了。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值