1. 程式人生 > >【Android開發】找樂,一個笑話App的制作過程記錄

【Android開發】找樂,一個笑話App的制作過程記錄

override pbo rdm data root 恰恰 功能 sql htm

緣起

想做一個笑話App的原因是由於在知乎上看過一個帖子。做Android能夠有哪些數據能夠練手,裏面推薦了幾個數據開放平臺。

在這些平臺中無一不是有公共的笑話接口,當時心想這個能夠拿來練手啊,還挺有意思的,預計還能積累一點用戶。

碰巧(真的好巧)在Github中遇到了一個MVP設計模式的框架Beam,作者Jude95有一個笑話倉庫————Joy(豆逼)。就是一個做笑話的!

更巧的是用到的接口也是我在關註的接口。心想不如改造一下吧,做個升級版。自己也能夠在這個中學到別人是怎麽寫App的。

後來發現這是一個非常正確的決定。

技術分享

雛形

由於是基於別人的改進。所以在寫之前就已經有雛形。當然這個雛形不是非常完好,這恰恰給了我改動的空間。在獲得作者的改動允許後,我就進一步研究這個利用MVP框架書寫的App。未改動之前:

技術分享

首先。豆逼僅僅能查看段子和查看圖片,我覺得主要的復制文本和查看大圖以及下載圖片。這些都沒有。

作者僅僅是用這個倉庫來說明MVP模式的。所以僅僅做了最主要的功能。作者也說。笑話連個id都沒有。點贊、評論什麽的根本沒法做。

那好,我就把我覺得的文本復制和圖片相關的做一下吧。

研究

MVP模式在這個項目之前我研究非常少。僅僅是聽說,可是這個項目全然給我耳目一新的感覺。MVP對Android來說實在是太實用了!

關於MVP我以後想細致寫個帖子研究一下。這裏僅僅想說明MVP使Android項目層次分明,代碼結構簡單。復用性高。參考作者的Beam。

這個項目用了非常棒的一個開源控件。也是項目作者自己的控件EasyRecyclerView,這個控件對我來說相見恨晚。線性布局仿EasyRecyclerView已經實現了下拉刷新,上拉載入很多其它,錯誤提示等,簡直把項目開發中可能遇到的坑都給做好了。我之前僅僅能一個一個的去實現這些功能!為什麽沒有早早的用上這個控件。

其它的沒有重大的驚喜。可是項目整體感覺代碼量非常少,非常精簡。假設是我完畢同樣的功能的App。可能須要3倍的代碼才幹實現。

改進

查看大圖

首先實現點擊查看大圖的功能。

PhotoView這個控件也是之前不久在Github中遇到的,使用的時候沒想到居然這麽easy!僅僅須要在xml中聲明一個PhotoView。主要的放大、縮小、手勢識別都有了!太方便。可能也是北郵人論壇官方client採用的一個查看大圖的工具。

在java文件載入圖片時則與ImageView全然同樣,這個不在贅述。

另一個拓展的地方是,單擊圖片返回(= = 一般都有吧?)。這個須要依據PhotoView的官方說明,使用Attacher來管理點擊事件,經過我測試,貌似直接聲明ImageView的點擊是不會有效果的。

圖片下載

這個App採用的是Glide載入網絡圖片。而Glide並沒有直接的下載存儲的方法,僅僅有自己拓展,耽誤了些功夫。

直接分享一段圖片下載和通知圖庫的代碼吧。

    public void saveImage(String imageUrl) {

        String[] names = new String[0];
        if (imageUrl != null) {
            names = imageUrl.split("/");
        }
        String imageName = names[names.length - 1];
        Glide
                .with(getView())
                .load(imageUrl)
                .asBitmap()
                .toBytes(Bitmap.CompressFormat.JPEG, 100)
                .into(new SimpleTarget<byte[]>() {
                    @Override
                    public void onResourceReady(final byte[] resource, GlideAnimation<? super byte[]> glideAnimation) {
                        new AsyncTask<Void, Void, Void>() {
                            @Override
                            protected Void doInBackground(Void... params) {

                                if (ImageStorage.checkifImageExists(imageName)) {
                                    Snackbar.make(getView().fab, "圖片已存在", Snackbar
                                            .LENGTH_LONG)
                                            .setAction("Action", null).show();
                                    return null;
                                }
                                String path = Environment.getExternalStorageDirectory().toString();
                                JUtils.Log("path", path);

                                Bitmap bitmap = BitmapFactory.decodeByteArray(resource, 0, resource.length);
                                JUtils.Log("imageName", imageName);

                                ImageStorage.saveToSdCard(getView(), bitmap, imageName);

                                Snackbar.make(getView().fab, "圖片已下載", Snackbar.LENGTH_LONG)
                                        .setAction("Action", null).show();

                                return null;
                            }
                        }.execute();
                    }
                });
    }

當中ImageStorage.java:

    public class ImageStorage {

        public static String saveToSdCard(Context context, Bitmap bitmap, String filename) {

            String stored = null;

            File sdcard = Environment.getExternalStorageDirectory();

            File folder = new File(sdcard.getAbsoluteFile(), "FindJoy");//the dot makes this directory hidden to
            // the
            // user
            folder.mkdir();
            File file = new File(folder.getAbsoluteFile(), filename + ".jpg");
            if (file.exists())
                return stored;

            try {
                FileOutputStream out = new FileOutputStream(file);
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
                out.flush();
                out.close();
                stored = "success";
                JUtils.Log("stored", stored);
            } catch (Exception e) {
                e.printStackTrace();
            }
            // 其次把文件插入到系統圖庫
            try {
                MediaStore.Images.Media.insertImage(context.getContentResolver(),
                        file.getAbsolutePath(), filename, null);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            // 最後通知圖庫更新
            context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + file
                    .getAbsolutePath())));
            return stored;
        }

        public static File getImage(String imagename) {

            File mediaImage = null;
            try {
                String root = Environment.getExternalStorageDirectory().toString();
                File myDir = new File(root);
                if (!myDir.exists())
                    return null;

                mediaImage = new File(myDir.getPath() + "/FindJoy/" + imagename);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return mediaImage;
        }

        public static boolean checkifImageExists(String imagename) {
            Bitmap b = null;
            File file = ImageStorage.getImage("/" +
                    imagename + "" +
                    ".jpg");
            String path = file.getAbsolutePath();

            if (path != null)
                b = BitmapFactory.decodeFile(path);

            if (b == null || b.equals("")) {
                return false;
            }
            return true;
        }
    }

為什麽之前我試了非常久可是一直發現圖庫沒有圖片呢?一直以為是自己的圖片沒有存儲下來,後來用圖庫的查看文件夾的方式發現了FindJoy文件夾。

原來是須要通知圖庫更新,否則圖片不會再圖庫中顯示。詳細請看上面代碼。

復制段子

這個本身是不麻煩的,出現故障的地方在於,這個MVP框架中怎麽對這個List加上OnItemClilkListner。

本身我就不非常熟。這個地方犯了不少錯誤,我怎麽沒想到看EasyRecyclerView的官方說明呢?

解決方法是在TextViewHolder中的itemView加上:

    itemView.setOnClickListener(view ->
            new MaterialDialog.Builder(getContext())
                    .title(R.string.select)
                    .content(R.string.copy)
                    .positiveText(R.string.agree)
                    .negativeText(R.string.disagree)
                    .onPositive((dialog, which) -> {
                        // Gets a handle to the clipboard service.
                        ClipboardManager clipboard = (ClipboardManager) getContext().
                                getSystemService(Context.CLIPBOARD_SERVICE);
                        // Creates a new text clip to put on the clipboard
                        ClipData clip = ClipData.newPlainText("joy", data.getText());
                        // Set the clipboard‘s primary clip.
                        clipboard.setPrimaryClip(clip);
                        Snackbar.make(itemView, "已將該段子拷貝到粘貼板", Snackbar.LENGTH_SHORT).show();
                    })
                    .show()
    );

官方庫還有能夠設置EasyRecyclerView的監聽的方法,效果是一樣的。

友盟統計

友盟統計可能是我自己往外發包的一個必選的項了,由於要知道App的使用情況啊。

這次發現友盟統計比曾經好用多了。jar包也放到了jCenter()倉庫,非常方便了。

這裏要贊一下這個MVP庫的優點了。居然能夠讓全部的Activity的生命周期都調用同一段代碼來實現友盟統計中要求的全部Actvity的OnResume()和OnPause()方法中都調用統計方法。

實現是通過一個頂級管理類MyActivityLifeCycleDelegate繼承ActivityLifeCycleDelegate,在裏面設置友盟統計的方法。

    public class MyActivityLifeCycleDelegate extends ActivityLifeCycleDelegate {
        public MyActivityLifeCycleDelegate(Activity act) {
            super(act);
        }

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            JUtils.Log("onCreate" + getActivity().getClass().getName());
        }

        @Override
        protected void onPause() {
            super.onPause();
            JUtils.Log("onPause");
            MobclickAgent.onPause(getActivity());
        }

        @Override
        protected void onResume() {
            super.onResume();
            JUtils.Log("onResume");
            MobclickAgent.onResume(getActivity());
        }
    }

然後在App的Application中

Beam.setActivityLifeCycleDelegateProvider(MyActivityLifeCycleDelegate::new);

上面這行代碼是IDE自己簡化的。好高端啊,居然有點不明確是怎麽回事了。。)

哦,對了,不要忘記在Manifest中聲明友盟的appkey。

嗯。統計就集成好了。

自己主動更新

同樣是友盟的服務。我也以為僅僅是幾分鐘的事情就搞定了,可是由於自己的問題,耽誤了一段時間,居然還想著把這個鍋扔給友盟。

好吧,我錯了。

這個和統計不一樣的是須要手動下載包放到項目當中,當中包括了一個.so文件。

由於在app的gradle中聲明了這句:

    compile fileTree(include: [‘*.jar‘], dir: ‘libs‘)

我就以為萬事大吉了。其實我開啟了友盟的debug模式才看了出來是我的.so沒有載入進去。

嗯。jni應該這麽聲明。我給忘了:

    sourceSets {
        main {
            jniLibs.srcDirs = [‘libs‘]
        }
    }

這樣.so文件就能載入進去了。

而友盟自己主動更新僅僅須要在MainActivity中寫一句代碼:

    UmengUpdateAgent.update(this);

非常酷對不正確?

自己主動更新是依據app versionCode來推斷的。更新的時候註意改動。

App截圖

這些功能做完之後我改動了一下配色。終於效果大體如圖,部分功能未截圖。

技術分享

應用市場

嗯,這些都實現了之後就上線應用商店了,主要有這幾個:

  • 小米應用商店:http://app.mi.com/detail/286105
  • 應用寶:http://android.myapp.com/myapp/detail.htm?

    apkName=com.fuxuemingzhu.findjoy

  • 豌豆莢:http://www.wandoujia.com/apps/com.fuxuemingzhu.findjoy
  • Fir.im:http://fir.im/axy4

能夠掃碼下載:

應用寶下載:
技術分享

豌豆莢下載:
技術分享

Fir.im下載:
技術分享

盡量不要用Fir。由於Fir沒有直觀的下載數目統計,嗯。盡量通過正規應用商店吧。

下載這事還得大家捧個場。

結語

盡管是一個非常easy的App,可是卻包括著非常多的心思在裏面。並且嘗試新的東西的時候能夠學到不少東西。這個是值得肯定的。畢竟我如今有種想把之前的App都揉碎又一次來寫的沖動。畢竟抵擋不住 MVP + Material Design的雙重誘惑啊!

Android 開發還有非常長的路要走。

本項目已經全然開源,代碼在:https://github.com/fuxuemingzhu/FindJoy,歡迎Star和Fork.

【Android開發】找樂,一個笑話App的制作過程記錄