Android开发框架Android-Sun-Framework

作者: 安卓程序猿 | 来源:发表于2017-05-15 10:08 被阅读453次

一. 写在前面

Android-Sun-Framework是一个Android组件化开发框架,可用于中大型项目。对应框架的一系列文章包含内容:设计思路、实现、如何使用等。我的宗旨是:授之以渔而不是授之以鱼! 同时我也感谢大家对我的支持,当前github star已经有38个了,Issues里也有对应问题。你们的鼓励就是我坚持写下去的源动力!!


Android开发框架Android-Sun-Framework(一)分享了:配置中心、Module跳转和网络层封装,详细内容可以点我查看

欢迎Star or Follow我的GitHub

欢迎搜索微信公众号SamuelAndroid关注我,定期推送原创文章和代码。

今天我们主要讲启用StrictMode,基础库归类、图片选择和分享

二. 启用StrictMode

    Android平台中(Android 2.3起),新增加了一个新的类,叫StrictMode(android.os.StrictMode)。这个类可以用来帮助开发者改进他们编写的应用,并且提供了各种的策略,这些策略能随时检查和报告开发者开发应用中存在的问题,比如可以监视那些本不应该在主线程中完成的工作或者其他的一些不规范和不好的代码。
    本框架是在[BaseApplication](https://github.com/samuelhuahui/Sun/blob/master/library/baselibrary/src/main/java/com/ody/library/base/BaseApplication.java)里开启的,具体代码如下:
if (BuildConfig.IS_DEBUG) {
            logBuilder.setLogSwitch(true);
            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                    .detectAll()
                    .penaltyLog()
                    .penaltyDialog()
                    .build());
            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                    .detectAll()
                    .penaltyLog()
                    .penaltyDeath()
                    .build());
        } else {
            logBuilder.setLogSwitch(true);
        }

我们设定只有是debug状态才会开启StrictMode,以免影响线上版本。具体StrictMode的配置自行Google,这里就不多赘述。

三. 基础库归类

    细心的朋友会发现以前的library更名为baselibrary,并将baselibrary移至library目录下,同时增加了模块PhotoPicker,PhotoPicker我下面会讲解,我们先来说一下目录变更的原因。
    当前我们已有的Module如下:app,login, usercenter,guide,trade,baselibrary和PhotoPicker,根据他们的命名,我们知道app,login, usercenter,guide和trade是业务模块,而baselibrary和PhotoPicker是基础模块,当然基础模块我们还会继续增加,比如推送,客服等。所以如果把基础模块放在和业务模块同目录下,难免会笔记混乱,故我们把基础模块做了归类,都放在library目录下。我们讲一下如何移动Module。

步骤1. 右击工程,新建文件夹library
步骤2. 移动已有Module至library文件夹下
步骤3. 打开setting.gradle修改“:baselibrary”为":library:baselibrary"
步骤4. 打开所以有依赖的gradle,compile project(':library:baselibrary')
调整后目录结构如下图:


四. 图片选择库(PhotoPicker)实现和使用

先看效果


a. 图片选择参数
    这里我们采用Builder模式,简化调用,主要代码如下:
public class PhotoPicker {

    public static final int REQUEST_CODE = 233;

    public final static int DEFAULT_MAX_COUNT = 9;
    public final static int DEFAULT_COLUMN_NUMBER = 3;

    public final static String KEY_SELECTED_PHOTOS = "SELECTED_PHOTOS";

    public final static String EXTRA_MAX_COUNT = "MAX_COUNT";
    public final static String EXTRA_SHOW_CAMERA = "SHOW_CAMERA";
    public final static String EXTRA_SHOW_GIF = "SHOW_GIF";
    public final static String EXTRA_GRID_COLUMN = "column";
    public final static String EXTRA_ORIGINAL_PHOTOS = "ORIGINAL_PHOTOS";
    public final static String EXTRA_PREVIEW_ENABLED = "PREVIEW_ENABLED";
    public static ImageLoader mImageLoader;

    public static PhotoPickerBuilder builder() {
        return new PhotoPickerBuilder();
    }

    public static class PhotoPickerBuilder {
        private Bundle mPickerOptionsBundle;
        private Intent mPickerIntent;


        public PhotoPickerBuilder() {
            mPickerOptionsBundle = new Bundle();
            mPickerIntent = new Intent();
        }

        /**
         * Send the Intent from an Activity with a custom request code
         *
         * @param activity    Activity to receive result
         * @param requestCode requestCode for result
         */
        public void start(@NonNull Activity activity, int requestCode) {
            if (PermissionsUtils.checkReadStoragePermission(activity)) {
                activity.startActivityForResult(getIntent(activity), requestCode);
            }
        }

        /**
         * @param fragment    Fragment to receive result
         * @param requestCode requestCode for result
         */
        public void start(@NonNull Context context,
                          @NonNull android.support.v4.app.Fragment fragment, int requestCode) {
            if (PermissionsUtils.checkReadStoragePermission(fragment.getActivity())) {
                fragment.startActivityForResult(getIntent(context), requestCode);
            }
        }

        /**
         * Send the Intent with a custom request code
         *
         * @param fragment Fragment to receive result
         */
        public void start(@NonNull Context context,
                          @NonNull android.support.v4.app.Fragment fragment) {
            if (PermissionsUtils.checkReadStoragePermission(fragment.getActivity())) {
                fragment.startActivityForResult(getIntent(context), REQUEST_CODE);
            }
        }

        /**
         * Get Intent to start {@link PhotoPickerActivity}
         *
         * @return Intent for {@link PhotoPickerActivity}
         */
        public Intent getIntent(@NonNull Context context) {
            mPickerIntent.setClass(context, PhotoPickerActivity.class);
            mPickerIntent.putExtras(mPickerOptionsBundle);
            return mPickerIntent;
        }

        /**
         * Send the crop Intent from an Activity
         *
         * @param activity Activity to receive result
         */
        public void start(@NonNull Activity activity) {
            start(activity, REQUEST_CODE);
        }

        public PhotoPickerBuilder setPhotoCount(int photoCount) {
            mPickerOptionsBundle.putInt(EXTRA_MAX_COUNT, photoCount);
            return this;
        }

        public PhotoPickerBuilder setGridColumnCount(int columnCount) {
            mPickerOptionsBundle.putInt(EXTRA_GRID_COLUMN, columnCount);
            return this;
        }

        public PhotoPickerBuilder setShowGif(boolean showGif) {
            mPickerOptionsBundle.putBoolean(EXTRA_SHOW_GIF, showGif);
            return this;
        }

        public PhotoPickerBuilder setShowCamera(boolean showCamera) {
            mPickerOptionsBundle.putBoolean(EXTRA_SHOW_CAMERA, showCamera);
            return this;
        }

        public PhotoPickerBuilder setSelected(ArrayList<String> imagesUri) {
            mPickerOptionsBundle.putStringArrayList(EXTRA_ORIGINAL_PHOTOS, imagesUri);
            return this;
        }

        public PhotoPickerBuilder setPreviewEnabled(boolean previewEnabled) {
            mPickerOptionsBundle.putBoolean(EXTRA_PREVIEW_ENABLED, previewEnabled);
            return this;
        }

        public PhotoPickerBuilder setImageLoader(ImageLoader loader) {
            mImageLoader = loader;
            return this;
        }
    }
}
b. 图片加载器
  1. 目的:PhotoPicker作为一个基础库且需要大量加载图片,简单的方式就是库里添加第三方框架的依赖,但是如果项目中和库里的依赖不一致,势必会导致冗余增加包大小。如过用的库相同,但版本不一致的话,将导致包冲突,所以我们这里通过实现图片加载器来解耦。

  2. 具体实现:通过定义一个接口ImageLoader,代码如下:

public interface ImageLoader {
//加载列表时调用
    void displayImage(ImageView imageView, String path);

  //预览图片时调用
   void displayImage(ImageView imageView, Uri uri) ;

    void clear(View imageView);
}

所以PhotoPicker的图片加载都使用displayImage方法,

c. 使用:详细使用请查看MainActivity
PhotoPicker.builder()
                        .setGridColumnCount(2)
                        .setPhotoCount(9)
                        .setImageLoader(new ImageLoader() {
                            @Override
                            public void displayImage(ImageView imageView, String path) {
                                Glide.with(imageView.getContext()).load(path).thumbnail(0.3f).into(imageView);
                            }

                            @Override
                            public void displayImage(ImageView imageView, Uri uri) {
                                Glide
                                        .with(imageView.getContext()).load(uri)
                                        .thumbnail(0.1f)
                                        .dontAnimate()
                                        .dontTransform()
                                        .override(800, 800)
                                        .into(imageView);
                            }

                            @Override
                            public void clear(View view) {
                                GlideUtil.clear(view);
                            }
                        })
                        .start(MainActivity.this);

五 分享库封装

分享功能几乎是每个APP必备功能,所以本开发框架必然不会少了他的封装,提高开发效率。分享库的实现思路和图片选择相似。具体实现步骤如下:

a. 新建Module Share

由于分享平台众多,为了能简化开发,这里使用ShareSdk来实现我们的分享功能,当前框架仅引入了新浪、QQ、微信三个平台的分享,如有其它需求,可以到ShareSdk下载更多。

b. 新建ShareHelper类,详细代码如下:


/**
 * Created by Samuel on 2017/5/12.
 */

public class ShareHelper {
    public final static String QQ_NAME = QQ.NAME;
    public final static String QZONE_NAME = QZone.NAME;
    public final static String WECHAT_NAME = Wechat.NAME;
    public final static String WECHATMOMENTS_NAME = WechatMoments.NAME;

    public static void init(Context context, String APP_KEY) {
        ShareSDK.initSDK(context, APP_KEY);
        ShareSDK.setPlatformDevInfo(QQ.NAME, BuildConfig.QQ);
        ShareSDK.setPlatformDevInfo(QZone.NAME, BuildConfig.QZone);
        ShareSDK.setPlatformDevInfo(Wechat.NAME, BuildConfig.Wechat);
        ShareSDK.setPlatformDevInfo(WechatMoments.NAME, BuildConfig.WechatMoments);
    }

    public static Builder builder(String platformName) {
        return new Builder().setPlatformName(platformName);
    }

    public static class Builder {
        private String title;
        private String titleUrl;
        private String text;
        private String url;
        private String imageUrl;
        private String comment;
        private String site;
        private String siteUrl;
        private String platformName;
        private ShareListener shareListener;

        public Builder() {

        }

        public Builder setTitle(String title) {
            this.title = title;
            return this;
        }

        // titleUrl是标题的网络链接,QQ和QQ空间等使用
        public Builder setTitleUrl(String titleUrl) {
            this.titleUrl = titleUrl;
            return this;
        }

        // text是分享文本,所有平台都需要这个字段
        public Builder setText(String text) {
            this.text = text;
            return this;
        }

        // imagePath是图片的本地路径,Linked-In以外的平台都支持此参数
        //setImagePath("/sdcard/test.jpg");//确保SDcard下面存在此张图片
        // url仅在微信(包括好友和朋友圈)中使用
        public Builder setUrl(String url) {
            this.url = url;
            return this;
        }

        // url仅在微信(包括好友和朋友圈)中使用
        public Builder setImageUrl(String imageUrl) {
            this.imageUrl = imageUrl;
            return this;
        }

        // comment是我对这条分享的评论,仅在人人网和QQ空间使用
        public Builder setComment(String comment) {
            this.comment = comment;
            return this;
        }

        // site是分享此内容的网站名称,仅在QQ空间使用
        public Builder setSite(String site) {
            this.site = site;
            return this;
        }

        public Builder setPlatformName(String platformName) {
            this.platformName = platformName;
            return this;
        }

        // siteUrl是分享此内容的网站地址,仅在QQ空间使用
        public Builder setSiteUrl(String siteUrl) {
            this.siteUrl = siteUrl;
            return this;
        }

        public Builder setShareListener(ShareListener listener) {
            shareListener = listener;
            return this;
        }

        public void share() {
            Platform.ShareParams sp = null;
            Platform platform;
            if (platformName == null || platformName.trim().length() == 0)
                throw new RuntimeException("还未设置分享平台");

            if (platformName.equals(QQ_NAME)) {
                sp = new QQ.ShareParams();

            } else if (platformName.equals(QZONE_NAME)) {
                sp = new QZone.ShareParams();
            } else if (platformName.equals(WECHAT_NAME)) {
                sp = new Wechat.ShareParams();
            } else if (platformName.equals(WECHATMOMENTS_NAME)) {
                sp = new WechatMoments.ShareParams();
            }
            platform = ShareSDK.getPlatform(platformName);
            sp.setTitle(title);
            sp.setTitleUrl(titleUrl); // 标题的超链接
            sp.setText(text);
            sp.setImageUrl(imageUrl);
            sp.setSite(site);
            sp.setSiteUrl(siteUrl);
            // 设置分享事件回调(注:回调放在不能保证在主线程调用,不可以在里面直接处理UI操作)
            platform.setPlatformActionListener(new PlatformActionListener() {
                public void onError(Platform arg0, int arg1, Throwable arg2) {
                    //失败的回调,arg:平台对象,arg1:表示当前的动作,arg2:异常信息
                    if (shareListener != null) {
                        shareListener.onError();
                    }
                }

                public void onComplete(Platform arg0, int arg1, HashMap<String, Object> arg2) {
                    //分享成功的回调
                    if (shareListener != null) {
                        shareListener.onComplete();
                    }
                }

                public void onCancel(Platform arg0, int arg1) {
                    //取消分享的回调
                    if (shareListener != null) {
                        shareListener.onCancel();
                    }
                }
            });
            // 执行图文分享
            platform.share(sp);
        }
    }

    public interface ShareListener {
        void onError();

        void onComplete();

        void onCancel();
    }
}

这里主要讲一下init方法中,ShareSDK动态参数(BuildConfig.QQ、BuildConfig.Wechat等)的配置
首先打开config.gradle:

 //分享配置
    QQ = [
            AppId : "100371282",
            AppKey: "aed9b0303e3ed1e27bae87c33761161d"
    ]
    QZone = [
            AppId : "100371282",
            AppKey: "aed9b0303e3ed1e27bae87c33761161d"
    ]
    Wechat = [
            AppId    : "wx4868b35061f87885",
            AppSecret: "64020361b8ec4c99936c0e3999a9f249"
    ]
    WechatMoments = [
            AppId    : "wx4868b35061f87885",
            AppSecret: "64020361b8ec4c99936c0e3999a9f249"
    ]

这里是各个分享平台的配置参数,使用分享功能前只需要修改这里的配置参数即可,如果需要支持更多平台,可以提issue给我,我会增加配置入口。大家都知道这里只是配置入口,这些输入如何才能让代码使用,这里我们就要看一下share模块下的build.gradle

defaultConfig {
       ......
        //推送配置
        manifestPlaceholders = [
                QQ_SCHEME: "tencent${rootProject.ext.QQ.AppId}"
        ]

        buildConfigField "java.util.HashMap<String, Object>", "QQ",
                "new java.util.HashMap<String, Object>() {" +
                        "{ put(\"AppId\", \"${rootProject.ext.QQ.AppId}\"); put(\"AppKey\",  \"${rootProject.ext.QQ.AppKey}\"); }}"

        buildConfigField "java.util.HashMap<String, Object>", "QZone",
                "new java.util.HashMap<String, Object>() {" +
                        "{ put(\"AppId\", \"${rootProject.ext.QZone.AppId}\"); put(\"AppKey\",  \"${rootProject.ext.QZone.AppKey}\"); }}"

        buildConfigField "java.util.HashMap<String, Object>", "Wechat",
                "new java.util.HashMap<String, Object>() {" +
                        "{ put(\"AppId\", \"${rootProject.ext.Wechat.AppId}\"); put(\"AppSecret\",  \"${rootProject.ext.Wechat.AppSecret}\"); }}"

        buildConfigField "java.util.HashMap<String, Object>", "WechatMoments",
                "new java.util.HashMap<String, Object>() {" +
                        "{ put(\"AppId\", \"${rootProject.ext.WechatMoments.AppId}\"); put(\"AppSecret\",  \"${rootProject.ext.WechatMoments.AppSecret}\"); }}"
    }

到这里你应该明白我们在初始化方法中使用的BuildConfig.QQ是从哪里来的吧!!!
我们在看一下上面的manifestPlaceholders 配置,做过分享的童鞋都知道,腾讯的分享需要通过设定scheme,我们打开share模块清单文件查看一下:

<activity
            android:name="com.mob.tools.MobUIShell"
            android:theme="@android:style/Theme.Translucent.NoTitleBar"
            android:configChanges="keyboardHidden|orientation|screenSize"
            android:windowSoftInputMode="stateHidden|adjustResize">

            <intent-filter>
                <data android:scheme="${QQ_SCHEME}" />
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.BROWSABLE" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

b. 如何使用

ShareHelper.builder(ShareHelper.QQ_NAME)
                        .setTitle("测试分享的标题")
                        .setTitleUrl("http://sharesdk.cn")
                        .setText("测试分享的文本")
                        .setImageUrl("http://www.someserver.com/测试图片网络地址.jpg")
                        .setSite("发布分享的网站名称")
                        .setSiteUrl("发布分享网站的地址")
                        .setShareListener(new ShareHelper.ShareListener() {
                            @Override
                            public void onError() {

                            }

                            @Override
                            public void onComplete() {
                                Toast.makeText(mContext, "success", Toast.LENGTH_LONG).show();
                            }

                            @Override
                            public void onCancel() {
                                Toast.makeText(mContext, "cancel", Toast.LENGTH_LONG).show();
                            }
                        })
                        .share();

是不是很简单???!!!!!

TODO

  1. hybrid支持(WebView 容器封装)
  2. 混淆
  3. 轮播
  4. 下拉刷新

欢迎Star or Follow我的GitHub

欢迎搜索微信公众号SamuelAndroid关注我,定期推送原创文章和代码。

相关文章

网友评论

    本文标题:Android开发框架Android-Sun-Framework

    本文链接:https://www.haomeiwen.com/subject/xrpgtxtx.html