標籤:服務 sync ast 使用者 jpeg 拷貝 錯誤 地方 線性
緣起
想做一個笑話App的原因是由於在知乎上看過一個文章。做Android能夠有哪些資料能夠練手,裡面推薦了幾個資料開放平台。
在這些平台中無一不是有公用的笑話介面,當時心想這個能夠拿來練手啊,還挺有意思的,預計還能積累一點使用者。
碰巧(真的好巧)在Github中遇到了一個MVP設計模式的架構Beam,作者Jude95有一個笑話倉庫————Joy(豆逼)。就是一個做笑話的!
更巧的是用到的介面也是我在關注的介面。心想不如改造一下吧,做個升級版。自己也能夠在這個中學到別人是怎麼寫App的。
後來發現這是一個非常正確的決定。
雛形
由於是基於別人的改進。所以在寫之前就已經有雛形。當然這個雛形不是非常完好,這恰恰給了我改動的空間。在獲得作者的改動允許後,我就進一步研究這個利用MVP架構書寫的App。未改動之前:
raw=true" title="">
首先。豆逼僅僅能查看段子和查看圖片,我覺得主要的複製文本和查看大圖以及下載圖片。這些都沒有。
作者僅僅是用這個倉庫來說明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
這些功能做完之後我改動了一下配色。終於效果大體,部分功能未。
raw=true" title="">
應用市場
嗯,這些都實現了之後就上線市集了,主要有這幾個:
能夠掃碼下載:
應用寶下載:
raw=true" title="">
豌豆莢下載:
Fir.im下載:
盡量不要用Fir。由於Fir沒有直觀的下載數目統計,嗯。盡量通過正規市集吧。
下載這事還得大家捧個場。
結語
儘管是一個非常easy的App,可是卻包括著非常多的心思在裡面。並且嘗試新的東西的時候能夠學到不少東西。這個是值得肯定的。畢竟我如今有種想把之前的App都揉碎又一次來寫的衝動。畢竟抵擋不住 MVP + Material Design的雙重誘惑啊!
Android 開發還有非常長的路要走。
本項目已經全然開源,代碼在:https://github.com/fuxuemingzhu/FindJoy,歡迎Star和Fork.
【Android開發】找樂,一個笑話App的製作過程記錄