美文网首页AndroidWeli收藏的文章AndroidAndroid开发
教你如何实现 Splash 页面三秒跳转和动态下载最新背景图

教你如何实现 Splash 页面三秒跳转和动态下载最新背景图

作者: 醒着的码者 | 来源:发表于2017-06-14 20:01 被阅读5396次

    本文已授权公众号hongyangAndroid原创首发

    最近公司产品大大说我们需要一个动态替换的闪屏页面,like 某猫,某东一样,可以动态替换。
    产品大大就是厉害,说一句话我们就需要实现好几个功能:

    1. 创建一个冷启动后的闪屏页面(Splash 页面)
    1. 这个页面默认 3s 倒计时,点击倒计时按钮可以跳转并结束倒计时
    1. 点击图片如果有外链,则跳转应用的 web 页面用来作为活动页面(没错这点和某猫很像)
    1. 动态替换厉害了,我们需要在进入这个页面后去后台请求一下是否有新的图片,如果是新的图片则下载到本地,替换掉原来的图片,下次用户在进入 Splash 就会看到一个崭新的图片。
    效果图

    一、布局实现

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent">
        <ImageView
            android:id="@+id/sp_bg"
            android:src="@mipmap/icon_splash"
            android:scaleType="centerCrop"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    
        <Button
            android:visibility="invisible"
            android:gravity="center"
            android:textSize="10sp"
            android:textColor="@color/white"
            android:id="@+id/sp_jump_btn"
            android:background="@drawable/btn_splash_shape"
            android:layout_width="60dp"
            android:layout_height="30dp"
            android:layout_alignParentRight="true"
            android:layout_alignParentTop="true"
            android:layout_marginRight="20dp"
            android:layout_marginTop="20dp"/>
    
    </RelativeLayout>
    
    

    布局文件文件相对来说还是比较简单,就需要一个 ImageView 和 Button 即可,Button 的背景是一个自定义的 shape,透明度颜色啥的,根据UI妹砸说的算就好了。

    <shape xmlns:android="http://schemas.android.com/apk/res/android"
           android:shape="rectangle">
        <solid android:color="#99c4c4c4"/>
        <corners android:radius="20dp"/>
        <stroke
            android:width="0.7dp"
            android:color="#7fffffff"/>
    
    </shape>
    

    二、倒计时功能实现

    实现倒计时的功能方法有很多,最基本的你可以使用 Handler 来实现吧,还可以是用 Timer 吧。

    但是由于之前写验证码倒计时的时候发现 android.os 中有一个神奇的类叫 CountDownTimer 的类,此类神奇之处就在于你完全不需要理会那些线程交互他都给你处理好了,你只管在回调中处理时间设置跳转逻辑就好了。

    但是有一个不足的地方就它的第一秒的倒计时有时候会不可见,所以我们将倒计时总时间设置为 3200ms 。

      private CountDownTimer countDownTimer = new CountDownTimer(3200, 1000) {
            @Override
            public void onTick(long millisUntilFinished) {
                mSpJumpBtn.setText("跳过(" + millisUntilFinished / 1000 + "s)");
            }
    
            @Override
            public void onFinish() {
                mSpJumpBtn.setText("跳过(" + 0 + "s)");
                gotoLoginOrMainActivity();
            }
        };
    

    最后需要在有闪屏页面的情况下,进入开启倒计时:

        private void startClock() {
            mSpJumpBtn.setVisibility(View.VISIBLE);
            countDownTimer.start();
        }
    

    三、下载功能实现点击跳转功能实现

    上边说了我们 APP 点击图片需要可以跳转,下面代码给出了背景点击跳转的逻辑:

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_splash);
            ButterKnife.bind(this);
            checkSDCardPermission();
        }
    
    
        @OnClick({R.id.sp_bg, R.id.sp_jump_btn})
        public void onViewClicked(View view) {
            switch (view.getId()) {
                case R.id.sp_bg:
                    gotoWebActivity();
                    break;
                case R.id.sp_jump_btn:
                    gotoLoginOrMainActivity();
                    break;
            }
        }
    

    跳转逻辑可以根据实际的项目需求来规定,下面的代码中 Splash 为本地序列化的 model 用来存储网络下载的闪屏页面信息,稍后会有详细的序列化过程,此刻我们只需要关注跳转逻辑:

       private Splash mSplash;
       private void gotoWebActivity() {
            if (mSplash != null && mSplash.click_url != null) {
                Intent intent = new Intent(this, BannerActivity.class);
                intent.putExtra("url", mSplash.click_url);
                intent.putExtra("title", mSplash.title);
                intent.putExtra("fromSplash", true);
                intent.putExtra("needShare", false);
                startActivity(intent);
                finish();
            }
        }
        
    

    机智的你可能看出来我们并没有在离开页面的时候结束掉 timer,其实我们是复写了 onDestroy 方法。

        @Override
        protected void onDestroy() {
            super.onDestroy();
            if (countDownTimer != null)
                countDownTimer.cancel();
        }
    

    其实跳转以后还有一个坑就是,从 web 页面返回的时候,因为闪屏页面是你应用的第一个页面,而跳转到 web 页面的是你 finish 掉了该页面,那么从 web 页返回的时候不做处理,用户就直接退出了 app 这样当然是不允许的。

    所以请在 web 页面中添加以下逻辑:

        //此方法是toolbar 的返回事件调用的方法 mFromSplash 为启动页面传递过来的参数
        @Override
        protected void onLeftClick(View view) {
            if (mFromSplash) {
                gotoLoginOrMainActivity();
            } else {
                super.onLeftClick(view);
            }
        }
    
        // 此方法为系统返回键的监听
        @Override
        public void onBackPressed() {
            if (mWebView.canGoBack()) {
                mWebView.goBack();
            } else if (mFromSplash) {
                gotoLoginOrMainActivity();
            } else {
                super.onBackPressed();
            }
        }
         // 下面是跳转逻辑 
         private void gotoLoginOrMainActivity() {
            if (UserCenter.getInstance().getToken() == null) {
                gotoLoginActivity();
            } else {
                gotoMainActivity();
            }
        }
    
        .... gotoLoginActivity,gotoMainActivity 太长了,不给了自己写 (*^__^*) 嘻嘻…… 
    

    四、下载网络图片以及序列化本地

    上边说了我们有这样一个需求,就是如果后台的接口返回的图片与本地序列化的图片不同,我们需要将新的图片下载到本地,然后下次进入 Splash 的时候就展示的新的图片了。

    这里你需要知道知识有下边几个:

    1. java bean 序列化与反序列化的知识
    2. IntentService 服务的知识
    3. AsycTask 的使用
    4. 6.0 以上权限申请 EasyPermissions 的使用。

    以上不熟悉的同学,看到下边的代码可能会引起适量身体不适


    其实这里更好的操作,我们可以将图片下载到内存中,这样并不需要申请sdk权限。这里当时实现的时候有点欠考虑了。如果您们保存图片的地址在内存中,就可以跳过这一步。

    1. 权限管理

    首先我们注意到已进入 Splash 页面我们就进行权限检查,因为我们需要下载最新的闪屏到本地,并取出序列化的对象,来展示对应的内容。

    其中 checkSDCardPermission 涉及到 6.0 以上下载最新图片的逻辑,这里采用的是 官方的 EasyPermissions 来处理,关于 EasyPermissions 的使用这里就不多说了,需要了解的请移步 EasyPermissions

        public static final int RC_PERMISSION = 123;
    
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
        @AfterPermissionGranted(RC_PERMISSION)
        private void checkSDCardPermission() {
            if (EasyPermissions.hasPermissions(this, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)) {
                initSplashImage();
                startImageDownLoad();
            } else {
                EasyPermissions.requestPermissions(this, "需要您提供【**】App 读写内存卡权限来确保应用更好的运行", RC_PERMISSION, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE);
            }
        }
    

    简单来说在 EasyPermissions.hasPermissions 的回调中我们就可以正确的做我们下载图片的工作了。

        private void initSplashImage() {
            mSplash = getLocalSplash();  
            //如果取出本地序列化的对象成功 则进行图片加载和倒计时
            if (mSplash != null && !TextUtils.isEmpty(mSplash.savePath)) {
                Logcat.d("SplashActivity 获取本地序列化成功" + mSplash);
                Glide.with(this).load(mSplash.savePath).dontAnimate().into(mSpBgImage);
                startClock();//加载成功 开启倒计时
            } else {
            // 如果本地没有 直接跳转
                mSpJumpBtn.setVisibility(View.INVISIBLE);
                mSpJumpBtn.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        gotoLoginOrMainActivity();
                    }
                }, 400);
            }
        }
        
        // 取出本地序列化的 Splash 
        private Splash getLocalSplash() {
            Splash splash = null;
            try {
                File serializableFile = SerializableUtils.getSerializableFile(Constants.SPLASH_PATH, Constants.SPLASH_FILE_NAME);
                splash = (Splash) SerializableUtils.readObject(serializableFile);
            } catch (IOException e) {
                Logcat.e("SplashActivity 获取本地序列化闪屏失败" + e.getMessage());
            }
            return splash;
        }
        
    

    2. 创建本地序列化对象 Splash Entity

    Splash 内容如下:

    public class Splash implements Serializable {
    
        private static final long serialVersionUID = 7382351359868556980L;//这里需要写死 序列化Id
        public int id;
        public String burl;//大图 url
        public String surl;//小图url
        public int type;//图片类型 Android 1 IOS 2
        public String click_url; // 点击跳转 URl
        public String savePath;//图片的存储地址
        public String title;//图片的存储地址
    
        public Splash(String burl, String surl, String click_url, String savePath) {
            this.burl = burl;
            this.surl = surl;
            this.click_url = click_url;
            this.savePath = savePath;
        }
    
        @Override
        public String toString() {
            return "Splash{" +
                    "id=" + id +
                    ", burl='" + burl + '\'' +
                    ", surl='" + surl + '\'' +
                    ", type=" + type +
                    ", click_url='" + click_url + '\'' +
                    ", savePath='" + savePath + '\'' +
                    '}';
        }
    }
    

    3. 序列化反序列话的工具类 SerializableUtils

    由于项目用到序列化地方还有挺多的,所以这里封装了一个序列化工具类SerializableUtils

    public class SerializableUtils {
    
        public static <T extends Serializable> Object readObject(File file) {
            ObjectInputStream in = null;
            T t = null;
            try {
                in = new ObjectInputStream(new FileInputStream(file));
                t = (T) in.readObject();
            } catch (EOFException e) {
                // ... this is fine
            } catch (IOException e) {
                Logcat.e("e " + e.getMessage());
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (in != null) in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return t;
        }
    
        public static <T extends Serializable> boolean writeObject(T t, String fileName) {
            ObjectOutputStream out = null;
            try {
                out = new ObjectOutputStream(new FileOutputStream(fileName));
                out.writeObject(t);
                Logcat.d("序列化成功 " + t.toString());
                return true;
            } catch (IOException e) {
                e.printStackTrace();
                Logcat.d("序列化失败 " + e.getMessage());
                return false;
            } finally {
                try {
                    if (out != null) out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static File getSerializableFile(String rootPath, String fileName) throws IOException {
            File file = new File(rootPath);
            if (!file.exists()) file.mkdirs();
            File serializable = new File(file, fileName);
            if (!serializable.exists()) serializable.createNewFile();
            return serializable;
        }
    }
    

    经过上边的努力我们已经完成了从本地反序列化内容,然后加载图片的工作了,剩下的需要做的就是下载最新图片的工作。

    4. 请求接口下载最新的闪屏信息和图片

    这里经过考虑,我决定采用服务去下载,因为这样可以少很多麻烦,也不影响程序的正常运行。但是绝不是你们要采用这样的方法,你们也可以单独写个工具类内部去开线程做这件事。

    项目中使用开启 IntentServie 来下载图片,关于这中服务的最大的好处就是,我们不需要关注服务是否执行完任务,当他执行完 onHandleIntent 方法后他就自己挑用 stop 方法了。我们只需要关注下载逻辑和序列化逻辑就好。

    checkSDCardPermission 中调用的 startImageDownLoad() 方法:

     private void startImageDownLoad() {
        SplashDownLoadService.startDownLoadSplashImage(this, Constants.DOWNLOAD_SPLASH);
     }
    

    SplashDownLoadService 内容,IntentService 在调用了 startService 后会执行 onHandleIntent 方法,在这方法中我们去请求服务器最新的数据即 loadSplashNetDate

        public SplashDownLoadService() {
            super("SplashDownLoad");
        }
    
        public static void startDownLoadSplashImage(Context context, String action) {
            Intent intent = new Intent(context, SplashDownLoadService.class);
            intent.putExtra(Constants.EXTRA_DOWNLOAD, action);
            context.startService(intent);
        }
    
        @Override
        protected void onHandleIntent(@Nullable Intent intent) {
            if (intent != null) {
                String action = intent.getStringExtra(Constants.EXTRA_DOWNLOAD);
                if (action.equals(Constants.DOWNLOAD_SPLASH)) {
                    loadSplashNetDate();
                }
            }
        }
    

    由于是公司项目,请求方法就不给出了,但是需要讲下请求数据后如何判断是否需要执行下载任务:

       mScreen = common.attachment.flashScreen;
               Splash splashLocal = getSplashLocal();
               if (mScreen != null) {
                   if (splashLocal == null) {
                      Logcat.d("splashLocal 为空导致下载");
                      startDownLoadSplash(Constants.SPLASH_PATH, mScreen.burl);
                    } else if (isNeedDownLoad(splashLocal.savePath, mScreen.burl)) {
                          Logcat.d("isNeedDownLoad 导致下载");
                          startDownLoadSplash(Constants.SPLASH_PATH, mScreen.burl);
                   }
               } else {//由于活动是一段时间,等活动结束后我们并不需要在进入闪屏页面,这个时候我们就需要将本地文件删除,下次在进来,本地文件为空,就会直接 finish 掉 Splash 页面,进入主页面。
                  if (splashLocal != null) {
                        File splashFile = SerializableUtils.getSerializableFile(Constants.SPLASH_PATH, SPLASH_FILE_NAME);
                         if (splashFile.exists()) {
                                 splashFile.delete();
                                 Logcat.d("mScreen为空删除本地文件");
                           }
                    }
               }
    

    由于活动是一段时间,等活动结束后我们并不需要在进入闪屏页面,这个时候我们就需要将本地文件删除,下次在进来,本地文件为空,就会直接 finish 掉 Splash 页面,进入主页面。

    getSplashLocal 方法即反序列话本地存储的 Splash Entity 的过程,上边已经给出这里就不细说,主要讲一下判断逻辑 isNeedDownLoad

        /**
         * @param path 本地存储的图片绝对路径
         * @param url  网络获取url
         * @return 比较储存的 图片名称的哈希值与 网络获取的哈希值是否相同
         */
        private boolean isNeedDownLoad(String path, String url) {
            // 如果本地存储的内容为空则进行下载
            if (TextUtils.isEmpty(path)) {
                return true;
            }
            // 如果本地文件不存在则进行下载,这里主要防止用户误删操作
            File file = new File(path);
            if (!file.exists()) {
                return true;
            }
            // 如果两者都存在则判断图片名称的 hashCode 是否相同,不相同则下载
            if (getImageName(path).hashCode() != getImageName(url).hashCode()) {
                return true;
            }
            return false;
        }
    

    分隔 uri 取图片名称的方法:

    private String getImageName(String url) {
            if (TextUtils.isEmpty(url)) {
                return "";
            }
            String[] split = url.split("/");
            String nameWith_ = split[split.length - 1];
            String[] split1 = nameWith_.split("\\.");
            return split1[0];
        }
    

    满足下载条件后则调用 DownLoadTask 下载。

    public class DownLoadUtils {
    
        public interface DownLoadInterFace {
            void afterDownLoad(ArrayList<String> savePaths);
        }
    
        public static void downLoad(String savePath, DownLoadInterFace downLoadInterFace, String... download) {
            new DownLoadTask(savePath, downLoadInterFace).execute(download);
        }
    
        private static class DownLoadTask extends AsyncTask<String, Integer, ArrayList<String>> {
            private String mSavePath;
            private DownLoadInterFace mDownLoadInterFace;
    
            private DownLoadTask(String savePath, DownLoadInterFace downLoadTask) {
                this.mSavePath = savePath;
                this.mDownLoadInterFace = downLoadTask;
            }
    
            @Override
            protected ArrayList<String> doInBackground(String... params) {
                ArrayList<String> names = new ArrayList<>();
                for (String url : params) {
                    if (!TextUtils.isEmpty(url)) {
                        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                            // 获得存储卡的路径
                            FileOutputStream fos = null;
                            InputStream is = null;
                            try {
                                URL downUrl = new URL(url);
                                // 创建连接
                                HttpURLConnection conn = (HttpURLConnection) downUrl.openConnection();
                                conn.connect();
                                // 创建输入流
                                is = conn.getInputStream();
                                File file = new File(mSavePath);
                                // 判断文件目录是否存在
                                if (!file.exists()) {
                                    file.mkdirs();
                                }
    
                                String[] split = url.split("/");
                                String fileName = split[split.length - 1];
                                File mApkFile = new File(mSavePath, fileName);
                                names.add(mApkFile.getAbsolutePath());
                                fos = new FileOutputStream(mApkFile, false);
                                int count = 0;
                                // 缓存
                                byte buf[] = new byte[1024];
                                while (true) {
                                    int read = is.read(buf);
                                    if (read == -1) {
                                        break;
                                    }
                                    fos.write(buf, 0, read);
                                    count += read;
                                    publishProgress(count);
                                }
                                fos.flush();
    
                            } catch (Exception e) {
                                Logcat.e(e.getMessage());
                            } finally {
                                try {
                                    if (is != null) {
                                        is.close();
                                    }
                                    if (fos != null) {
                                        fos.close();
                                    }
                                } catch (IOException e1) {
                                    e1.printStackTrace();
                                }
                            }
                        }
                    }
                }
                return names;
            }
    
            @Override
            protected void onPostExecute(ArrayList<String> strings) {
                super.onPostExecute(strings);
                if (mDownLoadInterFace != null) {
                    mDownLoadInterFace.afterDownLoad(strings);
                }
            }
        }
    }
    
    

    由于下载完成后需要拿到文件存储地址这里写了一个 mDownLoadInterFace.afterDownLoad 的回调在 service 拿到回调后:

    public void afterDownLoad(ArrayList<String> savePaths) {
                    if (savePaths.size() == 1) {
                        Logcat.d("闪屏页面下载完成" + savePaths);
                        if (mScreen != null) {
                            mScreen.savePath = savePaths.get(0);
                        }
                        // 序列化 Splash 到本地
                        SerializableUtils.writeObject(mScreen, Constants.SPLASH_PATH + "/" + SPLASH_FILE_NAME);
                    } else {
                        Logcat.d("闪屏页面下载失败" + savePaths);
                    }
                }
    

    写在最后

    上边 bb 这么多,我们可以看出产品一句话,我们程序员可能就需要工作一天了,所以我们需要将这个常见的功能记录下,下个公司产品再说实现一个闪屏功能,然后我们就可以说 这功能可能需要 1天时间,然后等他答应了,copy 一下,其他的时间你就可以学习下 Rxjava2 ,kotlin, js 之类的了。哈哈哈哈 我真tm机智。

    后记:

    这篇文章投稿到掘金和鸿洋大神的公众号后,大家对我的代码提出了许多建议,我感谢大家能帮助我成长。大家普遍要求一个Demo,花了几个小时时间,将其从项目中抽出来。希望大家赏脸 star 或者fork:

    项目地址:SplashActivityDemo

    相关文章

      网友评论

      • mimimomo:楼主。这种直接在theme里面放一个图片的,会遇到全面屏的启动图会被拉伸。
      • 李云龙_:老哥,你的demo里没有动态申请权限啊,而且 访问的 url 是404
        李云龙_:@醒着的码者 额,我看到了,你demo没用 sdcard,所以没动态申请
        醒着的码者:@dazeSimpleBook 我都从之前公司离职一年多了…… 估计是之前公司服务器关了吧
      • 李云龙_:老哥你把源码放出来就很舒服,哎,我反手就是一个 star
      • 721d739b6619:还有一个比较严重的问题博主:IntentService的onHandleIntent()方法本来是在工作线程执行,而你的loadSplashNetDate()方法里面okhttp又帮你开多了个线程。异步任务的doInBackground()也是在工作线程执行,亦就是你的IntentService的线程时多余的吧。增加额外的开销了。
      • 721d739b6619:不知道博主这么久回复,你还记得这个项目否?
        想问问博主,如果用你这种设计流程,举例:促销期1月1日到1月15日,我在此期间下载了这个app,但第一次打开按你的设计,应该是在splash这个页面什么都没有显示吧。因为你的设计是这次下载下次的图片。我下次打开已经不在促销期内了。这样应该是这个用户错过了一次浏览哪个splash页面的机会吧。不知道博主理不理解我的意思。
      • 街道shu记:大神带带我
      • 03bc7db99a1b:赞赞赞
      • 后来Memory:您好我不明白为什么非要这样做!我项目做的时候是每次进去加载缓存,获取数据在刷新缓存。下次进去就加载的是最新的了.我紧紧是缓存的json。不知这样做的不足之处是什么.还望前辈指导
        醒着的码者: @后来Memory 如果图片显示速度不会有问题就可以了,闪屏页面的图片会有3兆大小,如果弱网条件下,使用图片加载框架也会有一段时间的空白
      • thaixp:不多说,学习一波,哈哈
      • Waino_m:大神~EasyPermissions 我写在非点击事件里面的话,就Manifest.permission.WRITE_EXTERNAL_STORAGE 里面的WRITE_EXTERNAL_STORAGE会爆红,找不到这个变量。 有碰到过嘛?
      • 陈大头铃儿响叮当:源码跑不起来。。。
        醒着的码者:报什么错呢
      • cca02b5bdc0e:没有看见跳过三秒这块啊
      • Silence潇湘夜雨:你好,IOS程序猿,听说ios不好做了,来我们Android的大家庭吧。
      • e6850ef60d12:SplashDownLoadService中的loadSplashNetDate方法 HttpClient.getInstance()
        .getSplashImage(TYPE_ANDROID)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Consumer<Common>() {},new Consumer<Throwable>() {
        @Override
        public void accept(@NonNull Throwable throwable) throws Exception {

        }
        });


        subscribeOn报错 Cannot resolve method 'subscribeOn(io.reactivex.Scheduler)'怎么处理
        醒着的码者:@Android快乐者 我今天又看了眼项目,发现了你说的问题,你注意下 APIService这个类中的Observable倒包是不是 import io.reactivex.Observable;如果不是改成这个应该就没问题了
        e6850ef60d12:@我姓王叫叔叔 就是到的这个包 直接复制的你的代码 jar包也都添加!
        醒着的码者:@Android快乐者 这个是Rxjava2的线程调度 你看看Schedulers 的倒包有没有问题,应该是依赖
        import io.reactivex.schedulers.Schedulers;
      • zwill:赞
      • gznbl:又叼又机智的老王,哈哈哈
        醒着的码者:O(∩_∩)O哈哈~
      • 心中客:坐等大神源码:relaxed:
        醒着的码者:@心中客 :flushed:好的代码不需要注释 看方法名就知道了
        心中客:@我姓王叫叔叔 你这连注释都不加的
        醒着的码者:@心中客 已更新,欢迎 github 上赏脸
      • 风_清扬:源码,源码呢
        醒着的码者:@风_清扬 已更新,欢迎 github 上赏脸
      • Cui_IT:大神 来一波源码福利,让我们膜拜一把!:smile:
        醒着的码者:@Cui_IT 编程界的小学生,被你们玩坏了, 已更新,欢迎 github 上赏脸
      • 6262ea579008:坐等源码:relieved:
        6262ea579008:@我姓王叫叔叔 真机上面可以
        6262ea579008:@我姓王叫叔叔 我在7.0的模拟器上面测试好像没有效果
        醒着的码者:@xibo 已更新,欢迎 github 上赏脸
      • 来自唐朝的栗子:每次我们做了一个功能,隔着2,3天,鸿洋大神的公众号就会推出一片类似的文章,我觉着我们的工期应该跟鸿洋大神的公众号保持一致
        醒着的码者:@Sunny1986 编程界的小学生,被你们玩坏了, 已更新,欢迎 github 上赏脸
        Sunny1986:坐等开源精神大大的大神贴源码
        醒着的码者:@来自唐朝的栗子 巧了么这不是
      • 风影_638f:要是把源码 demo 发出来就好了
        风影_638f:@我姓王叫叔叔 已star
        醒着的码者: 已更新,欢迎 github 上赏脸
        醒着的码者:@风影_638f 正在整理 下午放出来 第四十九欢迎fork
      • 谁的春春不迷茫:作者,你的文件保存路径Constans可以贴下不
        醒着的码者:具体详情请看下 demo
      • 风影_638f:我们产品总监也这样,但我不鸟他
        醒着的码者: @风影_638f 666
      • pengpengli:有没想过一个问题,当你这次进来的时候下载的了一张图片保存在本地,而这张图片刚好是京东618,下次打开的时候已经是8月份了,然之前的保存的图片显示出来已经不合适了。
        醒着的码者:@pengpengli 判断条件需要后台支持,等活动结束后将返回的 Screen 至为空,本地将删除已经保存的闪屏信息,下次在进入就不会有闪屏了。

        每次进入App 都会请求后台信息,通过图片名称的hashCode 是否相同,判断是否需要下载新的闪屏信息到本地。
        pengpengli:@我姓王叫叔叔 恩恩,你删除的判断是什么,定期?有一种情况,在本次显示本地图片,同时还要下载下次展示。你有考虑过吗
        醒着的码者:嗯嗯,看的很仔细,在写完文章的第二天,我就加上了删除本地操作的判断,判断条件是后台传一个空的Screen对象。 现在已经更新文章,多谢
      • OpenMountain:产品当bug提了类似需求。。。:cold_sweat:
      • ed6918f57426:我是京东的 斜眼笑
      • ed6918f57426:某猫某东?为什么这么说,不就是天猫京东吗?打出来又不会死,难道还怕打广告不成?
        醒着的码者: @清风折柳 他又不给我钱,给我就写全名🤣
      • a6c7667b5f08:我就喜欢你 真tm机智的样子
      • 陈小窝:这是安卓么
        陈小窝:可是为啥我看到的代码都跟安卓相似啊
        b09dbea7de6a:@我姓王叫叔叔 ios程序员表示讲的真不错
        醒着的码者:@陈小窝 不是,这是iOS
      • Waizau:为啥不直接用图片加载sdk==
        醒着的码者:@Marco黑八 这种手动下载的方法并不需要等待网络框架加载的时间,采用有本地文件才展示,没有就不展示,也就是说用户如果网不好导致这次进入app没有下载下来最新的图片,那么就不会展示Splash,知道某次用户重新进入app的时候下载完成了最新的图片,之后在进入就可看到了,这样比让用户在空白页等待,效果更好。
        Marco黑八:@我姓王叫叔叔 不太明白,图片下载到本地不也受网络影响么...如果弱网环境或者下载一半断了咋办?
        醒着的码者:因为图片加载会受网络影响,不能进入 app 直接给用户展示一个空白页面吧,所以保存到本地稳妥,加载速度快。
      • bug玩编程:6666
        醒着的码者:@AutismBug :smile: 好开心
      • 但愿人长久_f282:666啊666 佳佳就是叼
        醒着的码者:@但愿人长久_f282 哪里哪里

      本文标题:教你如何实现 Splash 页面三秒跳转和动态下载最新背景图

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