Senior Mobile Developer - Vale

Contact me via @cwtututu.


  • Home

  • Categories

  • Archives

  • Tags

  • About

切图攻略

Posted on Aug 9 2015   |   In PS   |  

Android无线调试

Posted on Aug 9 2015   |  

Dropbox API 使用时遇到的问题

Posted on Jul 25 2015   |   In iOS   |  

最近的项目需要整合Dropbox。需求是,拿到token和userids传到服务器。

记得上一次做OAuth2.0是2011年,那时候刚刚到第一个公司,新浪微博那时貌似还是OAuth1.0授权,而且文档不全,要靠试错来确定参数。那时没有集成好的类库可调用,自己也是第一次接触,用了一个星期时间把新浪微博,腾讯微博,开心网,人人网,qq空间都集成了项目内,慢慢的对OAuth2.0有些了解。

这里面主要用到两块知识,一块就是OAuth2.0的流程,另一个就是授权后如何跳转到自己的app内。

今天重做时,这两块各遇到一个问题,下面说说这两个问题。

###OAuth2.0
这个发送授权请求时,有一个参数是response_type,它的值可取token和code,我看例子请求用的是code,我也用code,结果在Safira内授权后死活都不忘自己app内跳转。后来在停留的网页上的一行小字发现了秘密。

于是改成token试一下就可以了。然后仔细看文档才发现,原来它这个流程分为成两个:

####Code flow

These parameters are passed in the query string (after the ? in the URL):

code The authorization code, which can be used to attain a bearer token by calling /oauth2/token.

state The state content, if any, originally passed to /oauth2/authorize.

Sample response

1
[REDIRECT_URI]?code=ABCDEFG&state=[STATE]

####Token flow

These parameters are passed in the URL fragment (after the # in the URL):

access_token A token which can be used to make calls to the Dropbox API.

token_type The type of token, which will always be bearer.

uid The Dropbox user ID of the authorized user.

state The state content, if any, originally passed to /oauth2/authorize.

Sample response

1
[REDIRECT_URI]#access_token=ABCDEFG&token_type=bearer&uid=12345&state=[STATE]

Custome URL Schemes

我在URL Schemes框框内填了一个xxx://xxx的格式,其实只要填xxx就可以了。

在我写我的第一个Android App时,我希望我能知道的6件事

Posted on Jul 19 2015   |   In Android   |  

我的第一个app是糟糕的。实际上,它太糟糕了,所以我把它从商店中移除,我甚至不再因我把它列在我的简历之中而烦恼。如果我在写它之前,知道一些有关Android开发的事,那个app就不会那么糟糕。

当你写你的第一个Android app时,这有一个列表你需要记着。这些经验教训是我在写第一个app源码时,犯下的真实的错误。我将会在后面展示这些错误。记住这些事情能帮助你写一个让你感到有点自豪的app。

当然,如果你作为一个Android开发的学生正在做正确的工作,无论如何,不久后你都有可能恨你写的app,正如@codestandards所说,如果你一年前码的代码并不能让你感觉到不适,那你有可能没怎么学习。

如果你是一个有经验的Java开发者, 第1,2和第5条可能并不能让你感兴趣。另外的第3,4条可能展示你一些很酷的但你可能还不知道的东西。

###对Contexts不要有静态引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainActivity extends LocationManagingActivity implements ActionBar.OnNavigationListener,
GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener {

//...

private static MeTrackerStore mMeTrackerStore;

//...

@Override
protected void onCreate(Bundle savedInstanceState) {
//...

mMeTrackerStore = new MeTrackerStore(this);
}
}

这可能看起来对任何人来说,都是一个不可能犯的错误。但并非这样,我就犯过。我也看到其他人犯过这样的错误,并且我也看到很多人并不能在第一时间指出为什么这是一个错误。不要这样做。它是一个noob move

如果当这个Activity传递到它的构造函数里,MeTrackerStore持有这个Activity的引用,那这个Activity将仍然不会被回收(除非这个静态变量被重新赋值给另一个Activity)。这是因为mMeTrackerStore是静态的,静态变量的内存是不会被回收的,直到程序里正在运行的进程停止。如果你发现自己尝试这么做,那么你的代码可能有一些严重的错误。寻找帮助的话,可以看看Google’s Udacity里的课程 “Android Development for Beginners”

注意:技术上讲,你可以hold一个对Context的静态引用,但不会引起内存泄漏,但我不会推荐你这么做。

###当心对那些你无法控制它的生命周期的对象进行隐式引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DefineGeofenceFragment extends Fragment {
public class GetLatAndLongAndUpdateMapCameraAsyncTask extends AsyncTask<String, Void, LatLng> {

@Override
protected LatLng doInBackground(String... params) {
//...
try {
//Here we make the http request for the place search suggestions
httpResponse = httpClient.execute(httpPost);
HttpEntity entity = httpResponse.getEntity();
inputStream = entity.getContent();
//..
}
}
}


}

这段代码有很多问题,但我将关注其中的一个。在Java中,非静态的内部类对包含它的类对象有一个隐式的引用。

在这个例子中,任何GetLatAndLongAndUpdateMapCameraAsyncTask对象都将有个DefineGeofenceFragment对象的引用。匿名类也是如此:它会对包含它的类对象有个隐式的引用。

这个GetLatAndLongAndUpdateMapCameraAsyncTask对象对Fragment对象有个隐式的的引用,一个我们无法控制它生命周期的对象。Android SDK负责适当的创建和销毁Fragment对象,如果因为GetLatAndLongAndUpdateMapCameraAsyncTask对象正在执行所以不能被回收的话,那它持有对象也无法被回收。

这里有一个很棒的Google IO 视频解释为什么这种事会发生

###让Android Studio为你工作

1
2
3
public ViewPager getmViewPager() {
return mViewPager;
}

这个片段是我使用”Generate Getter”代码补全时,Android Studio为我生成的,这个getter方法对这个实例变量保持了’m’前缀。这并不理想。

(另外,你一定想知道为毛实例变量神明的时候要带个’m’前缀:这个’m’常常被约定作为实例变量的前缀。它代表了’member’。)

不论你是否认为’m’作为你实例变量的前缀是一个好主意,这里有一个经验:Android Studio可以帮你按照你养成的习惯去编写代码。比如说,你可以使用Android Studio中的代码风格框去让Android Studio自动的加上’m’到你的实例变量并且自动移除’m’当它生成getters,setters,和构造参数时。

Android Studio可以做很多事情,学习快捷键和活动模版会是一个好的开始。

###方法应该只做一件事

有一个方法我写超过了1000行。这样的方法很难读懂,修改和重用。试着写仅仅做一件事的方法。典型的,这意味着你应该怀疑那些你写超过20行的代码。这里你可以招募Android Studio去帮助你指出有问题的方法:

###向那些比你更聪明,更有经验的人学习

这好像挺起来不重要,但是这是我写第一个app时犯下的错误。

当你正在写程序时,你将会犯错。其他的人已经犯过这样的错误了。向其他人学习。如果你重复那些可以避免的错误,那你就是在浪费时间。

读Pragmatic Programmer. 然后读Effective Java.这两本书会帮助你避免犯一些常见的错误。当你读完这两本书后,保持向聪明的人学习。

###使用库
当你写一个app,你可能会遇到那鞋前人已经解决了的问题。而且,大量的解决办法都是开放的作为资源库。 好好利用他们。

在我的第一个app中,我写的功能已经被其他库所提供了,它们中的一些库来自于标准的java中的一部分。另一些则是像Retrofit和Picasso这样的库。如果你不确定你要应该用什么库,你能做3件事:

  1. 听Google IO Fragmented podcast episode
  2. 订阅Android Weekly
  3. 寻找解决类似问题的开源应用。你可能发现它们用了第三发的库(third-party library)或者用了你并没有在意的标准的java库。

###总结
写一个好的Android app是非常难的。不要因为重复我的错误让它变的更加艰难。

翻译自:6 THINGS I WISH I KNEW BEFORE I WROTE MY FIRST ANDROID APP

Some Tips About NSURLSession

Posted on Jul 18 2015   |   In iOS   |  
  • 后台传输时,需要保证队列里有多于一个任务,否则程序仍然会挂起。
  • 后台传输服务仅支持NSURLSessionUploadTask,NSURLSessionDownloadTask,并不支持NSURLSessionTask。
  • 后台上传时,只能使用uploadTaskWithRequest:fromFile:这个方法。如果NSURLSession对象由backgroundSessionConfiguration这个参数创建,这个方法uploadTaskWithRequest:fromData:不会工作。参见这里
  • In iOS, when a background transfer completes or requires credentials, if your app is no longer running, iOS automatically relaunches your app in the background and calls the application:handleEventsForBackgroundURLSession:completionHandler: method on your app’s UIApplicationDelegate object. This call provides the identifier of the session that caused your app to be launched. Your app should store that completion handler, create a background configuration object with the same identifier, and create a session with that configuration object. The new session is automatically reassociated with ongoing background activity. Later, when the session finishes the last background download task, it sends the session delegate a URLSessionDidFinishEventsForBackgroundURLSession: message. Your session delegate should then call the stored completion handler.

一张图告诉你怎么选择后台运行技术

Posted on Jul 18 2015   |   In iOS   |  

iOS8 New Features: Photos Framework

Posted on Jul 8 2015   |   In iOS8   |  

最近有个项目要请求系统相册,就先把iOS8中的新特性Photos Framework研究一下,参考 iOS_8_by_Tutorials。

###模型对象
Photos Framework 中的模型对象是继承于一个基类PHObject,它们是轻量级的对象,仅仅包含了一些原数据的描叙,但并不包含其内容。这些具体的模型对象是:

  1. PHAsset:表示一个单独的asset,它可能是一张图片或者视频。它包含了元数据比如是否是喜爱的,是否是隐藏的和媒介的类型。

  2. PHAssetCollection: 表示一组有序的asset的列表。

  3. PHCollectionList: 表示一组有序的PHAssetCollection的列表,或者PHCollectionList的列表。

获取(Fetching)

获取(Fetching)在Photos framework中是一个关键的特性,你可以获取任何的模型对象通过使用它所对应的类方法。另外当你获取的时候,你也可以使用PHFetchOptions选项。PHFetchOptions允许你指定哪些是你想要的,获取的结果应该如何排列,还有当结果发生改变时,Photos应该怎样提醒你。这些可以通过设置PHFetchOptions对象中的predicate,sortDescriptors,includeAllBurstAssets,includeHiddenAssets和wantsIncrementalChangeDetails属性,然后把它传给获取(Fetch)方法得到。

获取(Fetching)会一直返回一个PHFetchResult对象,一个PHFetchResult对象是一组有序的模型对象集合,它有一个接口和NSArray类似。这里最大的不同应该能让你站起来欢呼——PHFetchResult能替你管理内存并且实现对象的懒加载。这意味着获取和遍历上千的asset,理论上讲,不会比遍历几百个asset消耗更多的内存。十分惊奇。

记住,模型对象并不hold任何的内容,如果你想retrieve某个asset collection中的所有assets, 你不的不fetch他们。类似的,如果你想去展示和这些asset绑定的图片,你不的不加载它们。

1
2
3
4
5
6
7
8
9
- (void)fetchCollections {

//获取喜爱的asset集合
self.userFavorites = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumFavorites options:nil];

//获取相册的集合(即是asset的集合的集合)
self.userAlbums = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];

}

###加载内容
当你想展示属于一个asset的图片或视频内容的话,你需要通过一个PHImageManager的实例去加载它。这个默认的图片加载器是异步工作的,处理对asset的加载,缓存和调整。它的一个叫PHCachingImageManager的子类能预缓存asset的内容到内存中,并确保当你需要用到它们的时候,它们是可用的。

1
// 1
let reuseCount = ++cell.reuseCount
let asset = currentAssetAtIndex(indexPath.item)
// 2
let options = PHImageRequestOptions() options.networkAccessAllowed = true
// 3 PHImageManager.defaultManager().requestImageForAsset(asset,
targetSize: assetThumbnailSize, contentMode: .AspectFill, options: options) { result, info in
if reuseCount == cell.reuseCount { cell.imageView.image = result
} }

注意这里的requestImageForAsset方法,这个方法可能会被回调多次。Photos Framework提供了可以立即使用的低分辨率的asset版本,随后也可能提供一个高质量的asset版本。

###预加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
- (void)updateCache {

CGFloat currentFrameCenter = CGRectGetMidY(self.collectionView.bounds);
NSLog(@"%@",NSStringFromCGRect(self.collectionView.bounds) );
if (fabs(currentFrameCenter - self.lastCacheFrameCenter) < CGRectGetHeight(self.collectionView.bounds)/3) {
// Haven't scrolled far enough yet
return;
}
self.lastCacheFrameCenter = currentFrameCenter;

static NSInteger numOffscreenAssetsToCache = 60;

NSArray * visibleIndexes = [self.collectionView.indexPathsForVisibleItems sortedArrayUsingSelector:@selector(compare:)];

if (!visibleIndexes.count) {
[self.imageManager stopCachingImagesForAllAssets];
return;
}

NSInteger firstItemToCache = ((NSIndexPath *)visibleIndexes[0]).item - numOffscreenAssetsToCache/2;
firstItemToCache = MAX(firstItemToCache, 0);

NSInteger lastItemToCache = ((NSIndexPath *)[visibleIndexes lastObject]).item + numOffscreenAssetsToCache/2;
if (self.assetsFetchResults) {
lastItemToCache = MIN(lastItemToCache, self.assetsFetchResults.count - 1);
}
else {
lastItemToCache = MIN(lastItemToCache, self.selectedAssets.count - 1);
}

NSMutableArray * indexesToStopCaching = [[NSMutableArray alloc] init];
NSMutableArray * indexesToStartCaching = [[NSMutableArray alloc] init];

// Stop caching items we scrolled past
for (NSIndexPath * index in self.cachingIndexes) {
if (index.item < firstItemToCache || index.item > lastItemToCache) {
[indexesToStopCaching addObject:index];
}
}
[self.cachingIndexes removeObjectsInArray:indexesToStopCaching];

[self.imageManager stopCachingImagesForAssets:[self assetsAtIndexPaths:indexesToStopCaching]
targetSize:self.assetThumbnailSize
contentMode:PHImageContentModeAspectFill
options:nil];

// Start caching new items in range
for (NSInteger i = firstItemToCache; i < lastItemToCache; i++) {
NSIndexPath * index = [NSIndexPath indexPathForItem:i inSection:0];
if (![self.cachingIndexes containsObject:index]) {
[indexesToStartCaching addObject:index];
[self.cachingIndexes addObject:index];
}
}

[self.imageManager startCachingImagesForAssets:[self assetsAtIndexPaths:indexesToStartCaching]
targetSize:self.assetThumbnailSize
contentMode:PHImageContentModeAspectFill
options:nil];

}

自由职业者如何提高技术

Posted on Jul 7 2015   |   In Life   |  

作为自由职业者有一段时间了,基本上都是在接单与做单的循环中。最近闲下来,思考一下自由职业者如何提高技术。就我自己的情况而言,我喜欢接自己比较熟悉的单子来做,这样既能保证按时高质量的完成任务又能获取客户较高的评价。但是这样就带来了一个问题,新技术不断的出现,我怎么能保持对技术的敏感,时时提高自己呢?

想来有如下两种办法:

第一种就是看WWDC,坚持看,又能提高技术又能学英语。

第二种就是关注技术大牛的博客,把技术大牛分享出来的东西动手做一遍或者翻译过来,一定要争取看过的博客都搞懂,不能浅尝辄止。

自己常看的一些博客:

http://www.raywenderlich.com

http://www.objc.io

Think & Build

Subjective-c

唐巧的技术博客

OneV’s Den

txx’s blog

破船之家

叶孤城___

App效果网站:

CAPPTIVATE.co

另外,最终将自己的技术栈确定为 iOS, Android, NodeJS, 希望自己能不忘初心,坚持到底。

[译]Android Handler 中的内存泄漏

Posted on Jun 24 2015   |   In Handler   |  

Android使用Java作为其开发平台。这帮助我们解决许多低层次的问题,包括内存管理,平台类型依赖(platform type dependencies,怎么翻译?)等等。但是,我们有时候仍然会由于内存泄漏而导致程序崩溃。 那么,垃圾收集器哪里去了呢?

这些大对象长时间不能被清理的案例,我将关注其中一种。这种案例最终不会产生内存泄漏——对象在某些时刻都能够被回收——所以我们有时会忽略它。但是这并不是明智的做法,因为有些时候它会导致OOM的错误。

我正在描叙的这种案例是 Handler leak,这种案例常常被Lint作为一个警告检测到。

##简单的例子

这是一个非常简单的activity。注意这个匿名的 Runnable 被这个 Handler 发出伴随着一个非常长的延迟。我们运行它并且旋转手机多次,然后查看内存并且分析它。

我们现在有7个activities在内存中。这显然不太好。让我们找出为什么GC不能清理掉他们。

我做的这个查询去得到仍然存在于内存中的Activities是用OQL(Object Query Language)创建的,它很简单,也很强大。

正如你看到的那样,其中一个activity被 this$0 引用。这是一个匿名类对宿主类的间接引用。this$0 被 callback 对象引用,然后 callback 又被一连串的 Message 的对象 next 引用回到主线程。

任何时候你在宿主类里面创建一个非静态类,Java都会为宿主对象创建一个间接引用到这个非静态类对象。

一旦你在 Handler 里发送一个 Runnable 或者 Message, 它都会存储这些来自 LooperThread 的 Message 命令的引用到列表中,直到这个message被执行。发送一些延迟的messages,至少在延迟的这段时间,是一个明显的泄漏。 发送没有延迟的也可能引起短暂的泄漏如果队列里的messages很大的话。

##静态 Runnable 对象的解决方案
让我们试着克服一个内存泄漏通过摆脱 this$0, 通过把匿名类转为静态的。

跑跑,旋转,看看内存树。

啥情况?让我们看看谁持有了 Activities 的引用。

看看引用树的底部 – activity被 DoneRunnable 类里的mTextView 中的 mContext 引用了。使用内部静态类并不足够解决掉内存泄漏, 我们需要再多做一点。

##带有WeakReference对象的静态 Runnable

让我们继续使用迭代的修复方式并且摆脱掉对TextView的引用,这个引用阻止了activity的销毁。

注意,我们保持了一个对TextView的弱引用,然后跑一跑,旋转并且看下内存树。

要小心弱引用。它们随时都可能变为null,所以解决它们的第一步是把它赋值给一个强引用并且在使用它时检测它是不是为空(这个和iOS类似啊)。

哈哈哈,仅仅一个activity实例了,这解决了我们内存的问题。

到目前为止我们用到途径是:

  • 使用静态内部类(或者外部类)。
  • 对于被Handler/Runnable操纵的对象用弱引用。

如果你比较这个代码和前面的代码,你可能会发现有很大的不同在可读性和简洁度方面。前面的代码比较短比较简洁,很直观。

写这些样板文件是很乏味的事情,特别是把 postedDelayed 设置为50ms。有一些更好更简洁的解决方案。

##在 OnDestroy 方法中清除所有 Messages

Handler类有一个很有趣的特性 – remvoeCallBacksAndMessages – 这个方法允许 null 作为参数。它将移除所有的被特定handler发出的 Runnables 和 Messages 对象。让我们使用它在 OnDestroy 回调方法中。

让我们跑一跑,旋转并且看下内存树。

不错,只有一个对象。

这种方式比前一种方式要好,因为它保持代码干净又可读。唯一要记住的是清除所有Messages在 activity/fragment destroy方法中。

如果你像我一样懒, 我还有另外一种解决办法可能你会更喜欢。:)

##使用 WeakHandler
……

更多精彩,请阅读原作者博客

[V2ex客户端源码分析]Gradle的使用

Posted on Apr 13 2015   |   In Android   |  

最近在学习Android开发,看教程太枯燥,网上找了几个不错的源码,试着分析clone,并把学习的点记录下来。

先看看根目录的build.gradle文件。

{
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    repositories {
//mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.1.0'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

allprojects {
repositories {
jcenter()
}
}

这个文件中repositories是必须要配置的,gradle会根据这个仓库去查找你要包含到项目中的第三方库,老版本的Android Studio 用的仓库是mavenCentral(), 新版本换成了这个jcenter(),具体区别见这里。

再看看app目录里的build.gradle。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apply plugin: 'com.android.application'

android {
compileSdkVersion 21
buildToolsVersion "21.1.2"

defaultConfig {
minSdkVersion 14
targetSdkVersion 19
versionCode 6
versionName "1.3.0"
}

}

dependencies {
// The shortcut form for declaring external dependencies looks like “group:name:version”.
compile 'com.android.support:support-v4:19.0.0'
compile 'com.android.support:support-v13:19.0.0'
compile 'uk.co.chrisjenx.calligraphy:calligraphy:0.6.0'
compile 'com.github.chrisbanes.actionbarpulltorefresh:library:0.9.9'
compile 'com.etsy.android.grid:library:1.0.5'
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
compile 'com.loopj.android:android-async-http:1.4.6'
compile fileTree(dir: 'libs', include: ['*.jar'])
}

这里面的dependencies就是从jcenter()那里下载的第三方库,它的格式是:group:name:version,其他key值的解释见这里。

1…345…8
Changwei

Changwei

I develop iOS/Android apps with Swift/Kotlin language.

80 posts
33 categories
34 tags
GitHub Twitter Weibo Linkedin Upwork peopleperhour
Creative Commons
© 2011 - 2020 Changwei
Powered by Hexo
Theme - NexT.Muse