ç§ã®ååã¯ã¢ã¬ã¯ã»ã€ã§ããAndroidãiOSãããã³Windows Phoneãã©ãããã©ãŒã åãã®ã¢ãã€ã«ã¢ããªã±ãŒã·ã§ã³éçºã®ããã®VKã³ã³ãã¹ãã§ã®çµéšãã話ãããŸãã ç§ã®èšäºã¯åå¿è ãèªåã®åŒ·ã¿ãã²ãããè©äŸ¡ããäœãåŸ ã£ãŠããããç¥ãã®ã«åœ¹ç«ã€ãšæããŸãã
競äºæ¡ä»¶ã¯ãããèå³ãããã°ã ããã«ãããç§ãã¡ã®è£œåã«é¢ããããã€ãã®èšèããããŸãã ç§ãã¡ã¯ãSpied on-ããªãã®æã®ã²ãã®éœåžããšãããããžã§ã¯ãã§ã³ã³ãã¹ãã«åå ããããšã«ããŸããã ããŸããŸãªéœåžã§çŽ50ã®ããããã£ã¹ãããããŸãããããã¹ãŠã®éœåžã®ãã¥ãŒã¹ã1ãæã«ãŸãšãããã®ãäœæãããã£ãã®ã§ãã ãããŠãç§ãã¡ã¯ä»äºã«åãæãããŸããã ïŒVkontakteããéçŽãããïŒéœåžã®ãã¹ãŠã®ã€ãã³ããšãã¥ãŒã¹ã«åãŠãŒã¶ãŒãæ倧éã«ã¢ã¯ã»ã¹ã§ããæ©èœçãªã¢ãã€ã«ã¢ããªã±ãŒã·ã§ã³ãäœæããå¿ èŠããããŸããã
ãèŠãèŠãã¢ããªã±ãŒã·ã§ã³ã®æåã®äž»ãªææšãšããŠããŠãŒã¶ãŒã®åå¿ïŒä¿æïŒãšã補åãã¢ããªã±ãŒã·ã§ã³ã䜿çšãã人ã ã®æ°ããç¿æ £ã圢æãããã©ãããç解ããŸããã
çŸæç¹ã§ã¯ïŒ
- 30ïŒ ã§1æ¥éä¿æïŒããã»ã©å€ãã¯ãªãã50ïŒ ãè¯ãç¿æ £ïŒ
- ä¿æ3æ¥-18ïŒ
- ä¿æ7æ¥-15ïŒ
ãã¡ããããã£ãšå°è±¡çãªæ°åã欲ããã®ã§ãããããã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ã€ã®ã¢ã€ãã¢ã§ããŒã ãããã«çµéãããŸããã