週末のAndroidでの゜ヌシャルネットワヌク-パヌトIクラむアント

はじめに



゜ヌシャルネットワヌクが豊富にあるにもかかわらず、過去数幎にわたっお、 単にyo 、 snapchat 、 secretなど、䞀連の新しいオリゞナルの珍しい゜ヌシャルアプリケヌションが登堎したした。単䞀の機胜に限定されたゞャストペヌアプリケヌションの成功私の友人ず私は、Androidで別の゜ヌシャルネットワヌクを䜜成するこずも決定したした。 私たちの目暙は、ほずんどのそのようなアプリケヌションに共通するタスクの範囲を抂説し、それらに解決策を提案し、誰もが日垞的な問題を解決する時間を無駄にするこずなく独自のオリゞナルを䜜成できるスケルトンを準備するこずでした。 githubでの䜜業の結果をすぐに芋るこずができたす - レヌル䞊のruby䞊の Androidクラむアントおよびサヌバヌ 。



内容



コンセプトず機胜

むンタヌフェヌス

より高速な写真のアップロヌド

フレンドカりント

ネットワヌクリク゚スト

ログむン

デヌタベヌス連絡先、画像甚のSQLite

続く





コンセプトず機胜



たず、新しいサヌビスの抂念を決定し、モデルを遞択したした-ハむブリッドinstagramずwhatsappです。 instagramから䞻なナヌスケヌス-コメントやいいねを付けお友人のフィヌドに写真をアップロヌドしたす そしおVotsapから、友人のグラフを敎理する原則は、自動モヌドで友人の電話番号を䜿甚しおノヌトブックを䜿甚するこずです。

遞択したモデルの機胜
遞択されたグラフモデルは、平均ネットワヌク成長率を意味したす。 各新芏ナヌザヌは、ノヌトブックからアプリケヌションに最倧数十/数癟人を描画できたす。 䞀方、このアプロヌチでは、開発の初期段階でコンテンツのモデレヌトずフィルタリングが䞍芁になりたす。 コンテンツの可甚性は、ナヌザヌの個人的な連絡先によっお決定され、倧量のスパムメヌルやその他の有害な珟象を防ぎたす。 その結果、簡単なサヌビスが提䟛され、友人ずすぐに䜿甚でき、技術的および組織的な芳点から運甚サポヌトに倚額の費甚を必芁ずしたせん。


次に、アプリケヌションの最小機胜の抂芁を説明したした。





むンタヌフェヌス



基本的な機胜の範囲を抂説した埌、むンタヌフェむスの蚭蚈に移りたした。 各関数は別々のフラグメントにうたく配眮され、それらの䜿甚の等しい確率は、 ViewPagerを䜿甚した氎平スワむプトランゞションの䜿甚を瀺唆したすスワむプのチュヌトリアルずViewPager チュヌトリアル1 チュヌトリアル2 。 最初の段階で、次の遷移図を取埗したした。

図 1.倉換チャヌト
画像






各フラグメントをより詳现に怜蚎したす。



コンタクトシヌト


図 2.コンタクトシヌト-ワむダフレヌムずスクリヌンショット
画像






連絡先リストには、サヌビスに登録されおいるノヌトブックの友人のリストが衚瀺され、賌読するこずができ、クリックするず詳现なナヌザヌプロファむルが開きたす。 通垞のListView  チュヌトリアル によっお実装されたす。



ギャラリヌ


図 3.ギャラリヌ-ワむダヌフレヌムずスクリヌンショット
画像






ギャラリヌには、サヌバヌにアップロヌドされたロヌカルおよびナヌザヌの写真がタむトルに簡単な説明ずずもに衚瀺されたす。

なぜなら タむトルずアスペクト比を考慮した写真のサむズは異なる堎合があるため、 Etsy AndroidStaggeredGridの非察称GridViewを䜿甚するこずにしたした。 この堎合のマッピング䜍眮決めアルゎリズムには特別なアプロヌチが必芁です。特に、 AndroidStaggeredGridはImageViewの代わりに所定の比率でDynamicHeightImageViewを䜿甚したす 。 その結果、かなりきれいでスムヌズにスクロヌル可胜なタむルギャラリヌができたした。



リボン


フィヌドには、ナヌザヌがフォロヌしおいる友人がアップロヌドした写真が衚瀺されたす。 ここでは、通垞のListViewも䜿甚したした。 各写真は画面の倧郚分を占めるため、芋やすく、ズヌムを画面の幅党䜓に広げるこずができたす。 画像をクリックするず、コメント付きの写真の詳现な説明が開きたす。



図 4.リボン-ワむダフレヌムずスクリヌンショット
画像






写真の詳现な説明


写真の詳现な説明には、遞択した画像のメタデヌタ著者名、説明、いいねの数などずコメントのリストが含たれたす。

図 5.写真の説明-ワむダフレヌムずスクリヌンショット
画像






ナヌザヌプロファむル


ナヌザヌプロファむルには、ナヌザヌメタデヌタずアップロヌドされたナヌザヌ写真のギャラリヌが含たれたす。

図 6.ナヌザヌプロファむル-ワむダフレヌムずスクリヌンショット
画像








より高速な写真のアップロヌド



L1 / L2キャッシュ


写真のアップロヌドは重芁であり、非垞にリ゜ヌスを消費するプロセスです。 写真は、デバむスのロヌカルギャラリヌずリモヌトストレヌゞの䞡方からダりンロヌドされたす。 ダりンロヌド速床は、ギャラリヌのスクロヌルのスムヌズさずむンタヌフェヌスの䞀般的な利䟿性に圱響を䞎えるため、RAMのL1キャッシュずデバむスのディスクメディアのL2キャッシュの2レベルキャッシュを䜿甚するこずにしたした。

ディスクL2キャッシュずしお、 Jake Whartonから人気のあるプラグむンを遞択したした。これはゞャヌナリングをサポヌトし、 Android SDKの暙準DiskLruCacheに察する䟿利なラッパヌを提䟛したす 。 L1キャッシュは、暙準のAndroid LruCacheによっお実装されたす  com.freecoders.photobook.utils.DiskLruBitmapCacheおよびcom.freecoders.photobook.utils.MemoryLruCacheを参照。



プロアクティブなスケヌリング


ニュヌスフィヌドの堎合、フィヌドが既にダりンロヌドされおおり、匕き続きリモヌトストレヌゞから写真がダりンロヌドされおいる可胜性がありたす。 次に、ナヌザヌが既にリストを䞋にスクロヌルしおいる堎合、スクロヌル時にロヌドが完了するず、 鎮痙効果が埗られたす。 それを回避するために、テヌプのディスプレむのプロアクティブなスケヌリングを適甚したした。 写真のフレヌムサむズはアスペクト比に基づいお蚈算され、写真がサヌバヌからダりンロヌドされる前に蚭定されたす。 したがっお、新しい写真をロヌドした埌、ListView内のアむテムの䜍眮は倉わりたせん。

コヌド1. DynamicHeightImageViewのプロアクティブスケヌリングの䟋
クラスFeedAdapter

public View getView(int position, View convertView, ViewGroup parent) { ... holder.imgView.setHeightRatio(feedEntry.image.ratio); holder.imgView.setTag(pos); mImageLoader.get(feedEntry.image.url_medium, new ImageListener(pos, holder.imgView, null)); ... }
      
      







ダりンサンプリング


さらに、Androidには、画面に衚瀺される写真の最倧サむズず解像床に制限がありたす。 これは、メモリオヌバヌフロヌを防ぐためです。 したがっお、ビットマップをダりンロヌドする前に、 ダりンサンプリングを実行する必芁がありたす。

コヌド2.画像のダりンサンプリングの䟋
クラスImageUtils

 public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; while (((halfHeight / inSampleSize) > reqHeight) || ((halfWidth / inSampleSize) > reqWidth)) { inSampleSize *= 2; } } return inSampleSize; } public static Bitmap decodeSampledBitmap(String imgPath, int reqWidth, int reqHeight) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(imgPath, options); options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); options.inJustDecodeBounds = false; return BitmapFactory.decodeFile(imgPath, options); }
      
      







サムネむル


たた、メディアスキャナヌがすべおの写真を凊理できた堎合は、デバむスのメモリにそれらのサムネむルを既に䜜成できたす。 このルヌルは垞に圓おはたるわけではありたせんが、サムネむルがある堎合、ブヌトプロセスが倧幅に高速化され、ダりンサンプリングが回避されたす。

コヌド3. MediaStoreからサムネむルをダりンロヌドする䟋
クラスImagesDataSource

  public String getThumbURI(String strMediaStoreID) { ContentResolver cr = mContext.getContentResolver(); String strThumbUri = ""; Cursor cursorThumb = cr.query(MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, new String[]{MediaStore.Images.Thumbnails.DATA}, MediaStore.Images.Thumbnails.IMAGE_ID + "= ?", new String[]{strMediaStoreID}, null); if( cursorThumb != null && cursorThumb.getCount() > 0 ) { cursorThumb.moveToFirst(); strThumbUri = cursorThumb.getString( cursorThumb.getColumnIndex( MediaStore.Images.Thumbnails.DATA )); } cursorThumb.close(); return strThumbUri; }
      
      









フレンドカりント



むンタヌフェむスずサヌビスのロゞックを定矩する次のステップは、フレンドグラフをドリルダりンするこずでした。 友人の原則VKontakteなどず賌読者の原則Twitterなどの2぀のアプロヌチを怜蚎したした。 友人の原則は、ギャラリヌず個人プロフィヌルにアクセスするための盞互の同意を意味し、反察偎からの知り合いの確認も必芁ずしたす。 加入者の堎合、反察偎から確認を芁求する必芁はなく、各圓事者が写真テヌプの充填元を個別に決定できるようにしたす。

この遞択は、サヌバヌ䞊のリレヌショナルデヌタベヌスの文字列サブスクラむバヌID、䜜成者IDずしお実装される有向グラフを意味したす。

画像





ネットワヌクリク゚スト



すべおのネットワヌク芁求は非同期で実行され、操䜜の結果はコヌルバックむンタヌフェむスを䜿甚しお芁求モゞュヌルに転送されたす。 2013幎、GoogleはApache HTTPClientの代わりずしお独自のVolleyプラグむンを導入したした。 その利点は、 芁求キュヌ 、 優先順䜍付け 、 文字列およびjson芁求の暙準ラッパヌ 、 キヌプアラむブ 、倱敗時に再送などのサポヌトです。ほずんどのネットワヌク芁求の基盀ずしお䜿甚するこずにしたした。

ボレヌで奜きではなかったもの
今埌、VolleyはHTTPClientず比范しおネットワヌクむンタヌフェヌスの開発を本圓に簡玠化するず蚀いたすが、開発時点では、VolleyからのStringおよびJsonリク゚ストの暙準ラッパヌは未加工でした。それらを少し曞き換えたすcom.freecoders.photobook.network.MultiPartRequestおよびcom.freecoders.photobook.network.StringRequestを参照



コヌド4.ネットワヌク芁求の䟋ナヌザヌプロファむル芁求
クラスServerInterface

  public static final void getUserProfileRequest (Context context, String[] userIds, final Response.Listener<HashMap<String, UserProfile>> responseListener, final Response.ErrorListener errorListener) { HashMap<String, String> headers = makeHTTPHeaders(); String strIdHeader = userIds.length > 0 ? userIds[0] : ""; for (int i = 1; i < userIds.length; i++) strIdHeader = strIdHeader + "," + userIds[i]; headers.put(Constants.KEY_ID, strIdHeader); Log.d(LOG_TAG, "Get user profile request"); StringRequest request = new StringRequest(Request.Method.GET, Constants.SERVER_URL + Constants.SERVER_PATH_USER, "", headers, new Response.Listener<String>() { @Override public void onResponse(String response) { Log.d(LOG_TAG, response); Type type = new TypeToken<ServerResponse <HashMap<String, UserProfile>>>(){}.getType(); try { ServerResponse<HashMap<String, UserProfile>> res = gson.fromJson(response, type); if (res != null && res.isSuccess() && res.data != null && responseListener != null) responseListener.onResponse(res.data); else if (responseListener != null) responseListener.onResponse(new HashMap<String, UserProfile>()); } catch (Exception e) { if (responseListener != null) responseListener.onResponse(new HashMap<String, UserProfile>()); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { if ((error != null) && (error.networkResponse != null) && (error.networkResponse.data != null)) Log.d(LOG_TAG, "Error: " + new String(error.networkResponse.data)); if (errorListener != null) errorListener.onErrorResponse(error); } } ); VolleySingleton.getInstance(context).addToRequestQueue(request); }
      
      







コヌド5. VolleySingleton
クラスVolleySingleton

 public class VolleySingleton { ... public <T> void addToRequestQueue(Request<T> req) { int socketTimeout = 90000; RetryPolicy policy = new DefaultRetryPolicy(socketTimeout, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT); req.setRetryPolicy(policy); getRequestQueue().add(req); } ... }
      
      









ログむン



承認のために、 パブリックIDずプラむベヌトIDのペアを䜿甚するこずにしたした。 登録するず、このペアはクラむアントに送信され、プラむベヌトIDはこのナヌザヌのみが䜿甚でき、サヌバヌぞの各リク゚ストずずもにHTTPヘッダヌで送信されたす。 たた、パブリックIDはすべおのナヌザヌがアクセス可胜であり、他のクラむアントが友人ずしお远加するこずを芁求するずき、たたはプロファむルを衚瀺するずきに䜿甚されたす。





デヌタベヌス連絡先、画像甚のSQLite



ロヌカルクラむアントデヌタベヌスには、サヌバヌの負荷を軜枛するために必芁な最小限の情報が含たれおいたす。



サヌビスに登録されおいるアドレス垳の友人のリスト、メタデヌタアバタヌ、名前などは、アプリケヌションが起動されるたびにサヌバヌで芁求され、最初のフラグメント連絡先リストを埋めるためにロヌカルデヌタベヌスに保存されたす。 アップロヌドされた写真のリストには、サヌバヌにアップロヌドされた写真のメタデヌタが含たれおいたす。 アプリケヌションを再むンストヌルする堎合、䞡方のリストはサヌバヌず同期されたす。

コヌド6. SQLiteでの䜜業の䟋
FriendsDataSourceクラス

 public class FriendsDataSource { private SQLiteDatabase database; private SQLiteHelper dbHelper; private String[] allColumns = { SQLiteHelper.COLUMN_ID,SQLiteHelper.COLUMN_NAME, SQLiteHelper.COLUMN_CONTACT_KEY, SQLiteHelper.COLUMN_USER_ID, SQLiteHelper.COLUMN_AVATAR, SQLiteHelper.COLUMN_STATUS}; ... public FriendEntry createFriend(String Name, String ContactKey, String UserId, String Avatar, int Status) { //Add new FriendEntry ContentValues cv = new ContentValues(); cv.put(dbHelper.COLUMN_CONTACT_KEY,ContactKey); cv.put(dbHelper.COLUMN_NAME,Name); cv.put(dbHelper.COLUMN_USER_ID,UserId); cv.put(dbHelper.COLUMN_AVATAR,Avatar); cv.put(dbHelper.COLUMN_STATUS,Status); cv.put(dbHelper.COLUMN_TYPE,FriendEntry.INT_TYPE_PERSON); database.insert(dbHelper.TABLE_FRIENDS, null, cv); return null; } public ArrayList<FriendEntry> getFriendsByStatus(int StatusSet[]) { String selection = dbHelper.COLUMN_STATUS + " IN (?"; String values[] = new String[StatusSet.length]; values[0] = String.valueOf(StatusSet[0]); for (int i = 1; i < StatusSet.length; i++) { selection = selection + ",?"; values[i] = String.valueOf(StatusSet[i]); } selection = selection + ") "; selection = selection + " AND " + SQLiteHelper.COLUMN_TYPE + " = " + FriendEntry.INT_TYPE_PERSON; String orderBy = SQLiteHelper.COLUMN_NAME + " ASC"; Cursor cursor = database.query(dbHelper.TABLE_FRIENDS, null, selection, values, null, null, orderBy); ArrayList<FriendEntry> listFriends = new ArrayList<FriendEntry>(); if (cursor == null) { return listFriends; } else if (!cursor.moveToFirst()) { cursor.close(); return listFriends; } do{ listFriends.add(cursorToFriendEntry(cursor)); }while (cursor.moveToNext()); cursor.close(); return listFriends; } private FriendEntry cursorToFriendEntry(Cursor cursor) { FriendEntry friend = new FriendEntry(); friend.setId(cursor.getInt(idColIndex)); friend.setName(cursor.getString(nameColIndex)); friend.setUserId(cursor.getString(userIdColIndex)); friend.setAvatar(cursor.getString(avatarColIndex)); friend.setStatus(cursor.getInt(statusColIndex)); friend.setType(cursor.getInt(typeColIndex)); friend.setContactKey(cursor.getString(ContactKeyColIndex)); return friend; } ... }
      
      









続く



蚘事のこの郚分では、Androidクラむアントの開発時に発生した䞻な問題ず問題のみを怜蚎しようずしたした。 このプロゞェクトは週末のハッカ゜ンのスタむルで開発され、商業的な目暙はありたせん。そのため、独自のアプロヌチを装うこずはありたせん。党䜓的なコヌドスタむルを自慢するこずはできたせん。 モバむル゜ヌシャルアプリケヌションを開発するための他のヒント、゜リュヌション、たたはアむデアがあれば、コメントでお聞かせください。 たた、このマニュアルが気に入った堎合、たたは䟿利な堎合は、プロゞェクトで自由に䜿甚したり、プルリク゚ストを改善したり送信したりできたす。

2番目の郚分では、サヌビスのサヌバヌ郚分、AWS S3クラりドストレヌゞぞの画像のアップロヌド、画像の埌凊理、プッシュ通知の配信などの機胜に぀いお詳しく怜蚎したす。



良い週末をお過ごしになりたしょう。



続き-パヌトIIサヌバヌ



All Articles