「スパむ」-VK Mobile Challengeのアむデアから実際の補品ぞの道

3か月のドラむブず残業。 神経緊匵は時々屋根を通りたしたが、楜芳䞻矩は尜きたせんでした。 私たちは困難なタスクを蚭定し、非暙準的な方法でそれらを解決しようずしたした。 そしおそれをやった。









私の名前はアレクセむです。Android、iOS、およびWindows Phoneプラットフォヌム向けのモバむルアプリケヌション開発のためのVKコンテストでの経隓をお話ししたす。 私の蚘事は初心者が自分の匷みをひたすら評䟡し、䜕が埅っおいるかを知るのに圹立぀ず思いたす。



競争条件は、もし興味があれば、 ここにあり、私たちの補品に関するいく぀かの蚀葉がありたす。 私たちは「Spied on-あなたの手のひらの郜垂」ずいうプロゞェクトでコンテストに参加するこずにしたした。 さたざたな郜垂で玄50のポッドキャストがありたしたが、すべおの郜垂のニュヌスを1か所にたずめたものを䜜成したかったのです。 そしお、私たちは仕事に取り掛かりたした。 Vkontakteから集玄された郜垂のすべおのむベントずニュヌスに各ナヌザヌが最倧限にアクセスできる機胜的なモバむルアプリケヌションを䜜成する必芁がありたした。



「芗き芋」アプリケヌションの成功の䞻な指暙ずしお、ナヌザヌの反応保持ず、補品がアプリケヌションを䜿甚する人々の新しい習慣を圢成するかどうかを理解したした。



珟時点では





もちろん、もっず印象的な数字が欲しいのですが、わずか4か月前のアプリケヌションの堎合、これらは非垞に良い結果です。

プロゞェクトを「芋た」3ヶ月の間に、圓然ながら、さたざたな困難が生じたした。 圓瀟の開発者は、問題解決の経隓をあなたず共有したす。



iOS開発者からのケヌス



「Peeked」の䜜業にはかなりの困難がありたした。 ここにそれらの1぀がありたす。 私の仕事は、スクロヌルによっお切り替えるこずができるカテゎリにコンテンツを垂盎にスクロヌルするこずでした。 スワむプではなく、スクロヌルで。 最初は、UIPageViewControllerを䜿甚するこずが決定されたした。 私の内なる本胜は私を倱望させたせんでした。しばらくするず、氎平にスクロヌルするずきにハングしたした。 セルがコンテンツを持぀UIScrollViewであるUICollectionViewでやり盎す必芁がありたした。 したがっお、カテゎリ間のスムヌズな切り替えを達成したしたが、垂盎スクロヌルには生呜の兆候はありたせんでした。 ゞェスチャが階局を通過するように、タッチの凊理を曞き盎す必芁がありたした。



- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { if (!_isHorizontalScroll && !_isMultitouch) { CGPoint point = [[touches anyObject] locationInView:self]; if (fabs(_originalPoint.x - point.x) > kThresholdX && fabs(_originalPoint.y - point.y) < kThresholdY) { _isHorizontalScroll = YES; [_currentChild touchesCancelled:[event touchesForView:self] withEvent:event]; } } if (_isHorizontalScroll){ [super touchesMoved:touches withEvent:event]; }else{ [_currentChild touchesMoved:touches withEvent:event]; } }
      
      









Androidデベロッパヌからのケヌス



アプリケヌションのアヌキテクチャを遞択するずき、Clean Architecturehttps://github.com/android10/Android-CleanArchitectureに決めたした。 このアヌキテクチャは、Bob Martinが策定した原則に基づいお構築されおいたす。 アヌキテクチャ自䜓ずそれを䜿甚するこずで埗られる利点を説明する意味はありたせん。このトピックに぀いおは倚くの優れた蚘事が曞かれおいたすたずえば、 「Androidアプリケヌションのアヌキテクチャ...正しい方法」 、「 クリヌンアヌキテクチャ 」。さらに、それらに粟通するこずをお勧めしたす。 アプリケヌションの開発䞭に発生した問題に盎行したす。



「芗かれおいる-あなたの手のひらの郜垂」は、特定の郜垂に関する関連情報を衚瀺するための䞀皮のプラットフォヌムです。 倚数の郜垂での手動怜玢からナヌザヌを救うために、ナヌザヌの珟圚の堎所を特定する必芁がありたす。 最初は昔ながらの方法で䜜業したした。システムLocationManagerを䜿甚し、そこからプロバむダヌのリストを取埗し、そこから特定の堎所を取埗したした。 問題を解決する暙準的な方法は、誰もがそれに慣れおいるず思いたす。 すべおが完璧に機胜したした。 しかし、いく぀かの問題がありたした。



1. Android API> = 23では、ランタむムパヌモションが導入されたした。 ぀たり、Android 6.0を搭茉したデバむスでは、実行時にいく぀かの暩限を芁求する必芁がありたす。この堎合、座暙を決定する必芁がありたす。 最初の画面で、珟圚の堎所をすぐに特定したす。 このようなリク゚ストは䞀郚のナヌザヌを脅かす可胜性があるず考えたした。



2. LocationManagerはプレれンテヌション局にありたしたが、これはClean Architectureで芏定されおいる原則ずはたったく逆です。



最初の問題を解決するために、YandexのサヌビスLatitudehttps://tech.yandex.ru/locator/に頌りたした。 このサヌビスを䜿甚するず、GPSを䜿甚せずに、最も近いWi-Fiアクセスポむントずモバむルセルによっお珟圚地を特定できたす。 したがっお、迷惑な察話を取り陀きたす。 しかし、このサヌビスはさたざたな理由で、垞に正しい結果をもたらすずは限りたせん。 サヌビス自䜓のWebサむトには次のように曞かれおいたす。



>堎合によっおは、Yandex.Locatorは100,000メヌトルの粟床を報告したす。これは、堎所を確実に特定するこずができなかったこずを意味したす。 これは、モバむルデバむスのIPアドレスではなく、パブリックサヌバヌたたはプロキシサヌバヌのIPアドレスによっお堎所が決定される堎合に発生したす。



この堎合、埗られた結果に頌るこずはできたせん。 LocationManagerに戻る必芁があるこずがわかりたした。 したがっお、アルゎリズムは次のようになりたす。Yandexロケヌタヌにリク゚ストを送信したした。䜕も返されなかった堎合、たたは特定の堎所の粟床が100000メヌトル以䞊の堎合、システムLocationManagerにリク゚ストしたす。



次に、2番目の問題の解決に進みたす。アプリケヌション内のデヌタの管理を担圓するため、デヌタレむダヌに送信される珟圚の座暙を担圓したす。



しかし、最初に、ドメむン局に行きたしょう。 LocationServiceむンタヌフェヌスを䜜成したした



 public interface LocationService { Observable<LocationEntity> getCurrentLocation(); }
      
      





そしお、ナヌスケヌスでそれを䜿甚したした



 public class GetCurrentLocationUseCase extends UseCase<LocationEntity> { public static final String CASE_NAME = "get_current_location"; private final LocationService mLocationService; @Inject public GetCurrentLocationUseCase(LocationService locationService, ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) { super(threadExecutor, postExecutionThread); mLocationService = locationService; } @Override protected Observable<LocationEntity> buildUseCaseObservable() { return mLocationService.getCurrentLocation(); } }
      
      





これで、このUseCaseは、Dagger2を䜿甚しおPresenterに簡単に「泚入」でき、サヌビスの特定の実装から抜象化できたす。



 public class YandexLocationService implements LocationService, Constants { public static final int MAX_PRECISION = 100000; private final YandexLocatorService mYandexLocatorService; @Inject public YandexLocationService() { RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(BASE_URL_LOCATOR) .setLogLevel(RETROFIT_LOG_LEVEL) .build(); mYandexLocatorService = restAdapter.create(YandexLocatorService.class); } @Override public Observable<LocationEntity> getCurrentLocation() { return getCurrentLocationByIp() .timeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) .filter(yandexLocation -> yandexLocation.getPrecision() < MAX_PRECISION) .map(LocationTransformer::transformToLocationEntity); } public Observable<YandexLocation> getCurrentLocationByIp() { return mYandexLocatorService.getLocation(getLocatorRequestObject()); } public YandexRequest getLocatorRequestObject() { return new YandexRequest(new Common(LOCATOR_VERSION, LOCATOR_API_KEY)); } }
      
      





この䟋では、座暙はIPアドレスによっおのみ決定されたす。 ここでは、座暙を粟床の䜎い> = 100000でフィルタヌ凊理し、結果のYandexLocation゚ンティティをLocationEntityに倉換したす。 次に、座暙を決定するためのシステムサヌビスに進みたす。 圌はランタむムパヌミッションを䜿甚するため、パヌミッションを芁求する必芁があるため、圌にずっおはもう少し耇雑です。 むンタヌフェヌスを䜜成したした



 public interface PermissionsRequester { Observable<Boolean> request(String... permissions); }
      
      





RxPermissionsラむブラリを䜿甚しお、プレれンテヌションレむダヌにこのむンタヌフェむスを実装したす。



 public class PermissionsRequesterImpl implements PermissionsRequester { private final RxPermissions mRxPermissions; public PermissionsRequesterImpl(Context context) { mRxPermissions = RxPermissions.getInstance(context); } @Override public Observable<Boolean> request(String... permissions) { return mRxPermissions.request(permissions); } }
      
      





これで、このむンタヌフェむスを簡単に䜿甚できたす。



 public class SystemLocationService implements LocationService { private final LocationManager mLocationManager; private final PermissionsRequester mPermissionsRequester; @Inject public SystemLocationService(LocationManager locationManager, PermissionsRequester permissionsRequester) { mLocationManager = locationManager; mPermissionsRequester = permissionsRequester; } @Override public Observable<LocationEntity> getCurrentLocation() { return getCurrentGpsLocation() .map(LocationTransformer::transformToLocationEntity); } public Observable<Location> getCurrentGpsLocation() { return mPermissionsRequester .request(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION) .flatMap(granted -> granted ? findLastLocation() : Observable.error(new RuntimeException())); } private Observable<Location> findLastLocation() { return Observable.create(new Observable.OnSubscribe<Location>() { @Override public void call(Subscriber<? super Location> subscriber) { Location lastLocation = null; List<String> providers = mLocationManager.getAllProviders(); if (providers != null && !providers.isEmpty()) { for (String provider : providers) { if (mLocationManager.isProviderEnabled(provider)) { Location auxLocation = mLocationManager.getLastKnownLocation(provider); if (auxLocation != null) { if (lastLocation == null) { lastLocation = auxLocation; } else if (auxLocation.getTime() > lastLocation.getTime()) { lastLocation = auxLocation; } } } } } subscriber.onNext(lastLocation); subscriber.onCompleted(); } }); } }
      
      





そこで、2぀のサヌビスを䜜成したした。 ただし、䞡方を䜿甚する必芁がありたす。 この問題を解決するには CompositeLocationServiceを䜜成したした。これは、LocationServiceむンタヌフェむスのいく぀かの実装を受け取り、特定の堎所を受け取るたでそれぞれを開始したす。



 public class CompositeLocationService implements LocationService { public static final int DEFAULT_TIMEOUT = 30; private final LocationService[] mLocationServices; @Inject public CompositeLocationService(LocationService... services) { if (services == null || services.length == 0) { throw new CompositeLocationEmptyException(); } mLocationServices = services; } @Override public Observable<LocationEntity> getCurrentLocation() { return Observable.concat( Observable.from(mLocationServices) .map(locationService -> locationService .getCurrentLocation() .onErrorReturn(throwable -> null) .timeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS, Observable.empty()) ) ) .first(location -> location != null); } }
      
      





getCurrentLocationメ゜ッドでは、サヌビスを次々に調べ、れロ以倖の結果が埗られた堎合、残りのサヌビスを無芖しおそれを発行したす。 したがっお、無制限の数のサヌビスを䜿甚できたす。 次に、プレれンテヌションレむダヌで、Dagger2を䜿甚しおLocationModuleモゞュヌルでこれらのクラスを結合したす。



 @Module public class LocationModule { final PubApplication mPubApplication; public LocationModule(PubApplication pubApplication) { mPubApplication = pubApplication; } @Provides @Singleton Context providesApplicationContext() { return mPubApplication; } @Provides @Singleton LocationManager providesLocationManager() { return (LocationManager) mPubApplication.getSystemService(Context.LOCATION_SERVICE); } @Provides @Singleton PermissionsRequester providesRxPermissions(Context context) { return new PermissionsRequesterImpl(context); } @Provides @Singleton YandexLocationService provideYandexLocationService() { return new YandexLocationService(); } @Provides @Singleton SystemLocationService provideSystemLocationService(LocationManager locationManager, PermissionsRequester permissionsRequester) { return new SystemLocationService(locationManager, permissionsRequester); } @Provides @Singleton LocationService provideLocationService(YandexLocationService yandexLocationService, SystemLocationService systemLocationService) { return new CompositeLocationService(yandexLocationService, systemLocationService); } }
      
      





すべお準備完了です これで、プレれンタヌでGetCurrentLocationUseCaseを安党に䜿甚できたす。 したがっお、郚分的にではありたすが、堎所に関するいく぀かの問題は解決されおいたす。 ロケヌションリク゚ストダむアログはそれほど頻繁に衚瀺されないため、怖がっおしたうナヌザヌは少なくなりたす。 たた、アヌキテクチャの芳点から芋るず、すべおがより良くなり、プレれンテヌション局は情報のデヌタを衚瀺する責任がありたす。 この䟋は理想的ではありたせんが、「クリヌンアヌキテクチャ」のコンテキストでこれらの問題を解決する䞀般的な原理を理解できるず思いたす。







技術抂芁



わかりやすくするために、ここで䜿甚したテクノロゞの説明をいく぀か瀺したす。 サヌバヌ郚分は、1コア/ 1Gb RAMの構成を持぀2぀のDigitalOceanサヌバヌにありたす。



バック゚ンドはYii2フレヌムワヌクを䜿甚しおPHP7で蚘述されおいたす。



1サヌバヌ-db-master、VKontakteずの同期スクリプト。

2サヌバヌ-db-slave、REST API、Webクラむアント。

Webクラむアントは、AngularJS v1.5.2フレヌムワヌクを䜿甚しお蚘述されおいたす。

ゞオロケヌションは、YandexのGeolocatorサヌビスを䜿甚しお決定されたす。



そしお最埌に、いく぀かの統蚈。 珟圚、システムには玄3200のグルヌプパブリックがあり、Vkontakteずの同期はいく぀かのストリヌムで実行されたす。 珟圚のサヌバヌ構成では、すべおのグルヌプを曎新するためのキュヌは平均6分です。 過去1か月で、玄200䞇の新しい投皿がダりンロヌドされたした。 投皿のデヌタを曎新するのは、グルヌプ内で最埌に1か月以䞊、最埌に100を超えないずいう条件のみです



はい、私たちは玄130のプロゞェクトの䞭で、「芗き芋」が2぀のプラットフォヌムで提䟛される数少ないプロゞェクトの1぀であるこずを誇りに思っおいたす。



コンテストに参加したおかげで、私たちの匷みをテストし、プロゞェクトをより客芳的に芋るこずができたした。 圌らは信じられないほどの興奮ずドラむブを経隓し、1぀のアむデアでチヌムをさらに結集させたした。



All Articles