Select all the cell in UITabView or UICollectionView problem

Catalogue
  1. 1. The Issue
  2. 2. Solutions in NetWork
  3. 3. The real Solution
  4. 4. Ref

The Issue

Recently in my new project I need to select all the cell data in my UITabViewCell and UICollectionViewCell, and need to do some operations with all the cells(like delete etc.), What I do as follows:

UITabView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private func selectAll(select: Bool) {
let numSections = mListTableView?.numberOfSections
if let numSections = numSections {
for numSection in 0 ..< numSections{
let numItems = mListTableView?.numberOfRowsInSection(numSection)
if let numItems = numItems {
for numItem in 0 ..< numItems {
selectCell(NSIndexPath(forRow: numItem, inSection: numSection), select: select)
}
}
}
}
}
private func selectCell(indexPath : NSIndexPath, select: Bool) {
if mListTableView?.cellForRowAtIndexPath(indexPath) != nil {
let cell = mListTableView?.cellForRowAtIndexPath(indexPath) as! DownloadListViewCell
//cell.setSelected(select, animated: true)
cell.setSelectForDelete(select) // select status UI in UITabViewCell
mDownloadList[indexPath.row].selectToDelete = select // Pojo data
}
}

UICollectionView

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
private func selectAll(select: Bool) {
let numSections = mMyOfflineCollectView?.numberOfSections()
if let numSections = numSections {
for numSection in 0 ..< numSections{
let numItems = mMyOfflineCollectView?.numberOfItemsInSection(numSection)
if let numItems = numItems {
for numItem in 0 ..< numItems {
selectCell(NSIndexPath(forRow: numItem, inSection: numSection), flag: select)
}
}
}
}
}
private func selectCell(indexPath : NSIndexPath, flag: Bool) {
if mMyOfflineCollectView.cellForItemAtIndexPath(indexPath) != nil {
let cell = mMyOfflineCollectView.cellForItemAtIndexPath(indexPath) as! MyOfflineCollectionViewCell
cell.setSelect(flag)
if flag {
mMyOfflineCollectView.selectItemAtIndexPath(indexPath, animated: true, scrollPosition: UICollectionViewScrollPosition.None)
}else {
mMyOfflineCollectView.deselectItemAtIndexPath(indexPath, animated: true)
}
mMyofflinesData[indexPath.row].needDelete = flag
}
}

But, The problem is , I can only select the visible cell when I scoll down or up, or do operations

Solutions in NetWork

UICollectionView cellForItemAtIndexPath is nil

cellForItemAtIndexPath returns nil after force scrolling to make it visible

Select all the cells in UITableView

Easier way to select all rows in UITableView

tableView.cellForRowAtIndexPath returns nil with too many cells (swift)

tableView.cellForRowAtIndexPath(indexPath) return nil

The real Solution

The real problem happened at the cellForRowAtIndexPath / cellForItemAtIndexPath, Which defined in Apple as follows:

1
2
public func cellForRowAtIndexPath(indexPath: NSIndexPath) -> UITableViewCell?
// returns nil if cell is **not visible** or index path is out of range
1
public func cellForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewCell?

When the cell is not visible, the cellForRowAtIndexPath will return nil,
So, it’s not the right way to do the cell select operation out the
UITableViewDataSource in cellForRowAtIndexPath (UITabView), you should do it separate. The right way as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(DOWNLOAD_LIST_CELL_INDENTIFIER, forIndexPath: indexPath) as! DownloadListViewCell
cell.selectionStyle = UITableViewCellSelectionStyle.None
// ...
cell.setSelectForDelete(self.mDownloadList[indexPath.row].selectToDelete)// select status UI in UITabViewCell
// ...
return cell
}
private func selectCell(indexPath : NSIndexPath, select: Bool) {
mDownloadList[indexPath.row].selectToDelete = select // Pojo data
mListTableView?.reloadData() // reloadData
}

Ref

UITableView

UICollectionView



By SkySeraph-2016

版权声明


SkySeraph by SkySeraph is licensed under a Creative Commons BY-NC-ND 4.0 International License.
Bob创作并维护的SkySeraph博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证.
本文首发于SkySeraph博客( http://skyseraph.com ),版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

微信扫码打赏SkySeraph

如果您愿意捐助其它金额请戳我~~,扫码支付宝/微信

本文永久链接:http://skyseraph.com/2016/06/30/iOS/Select all the cell in UITabView or UICollectionView problem/

Comments

一种提高Android应用进程存活率新方法

Catalogue
  1. 1. 基础知识
    1. 1.1. Android 进程优先级
    2. 1.2. Android Low Memory Killer
    3. 1.3. 查看某个App的进程
    4. 1.4. Linux AM命令
    5. 1.5. NotificationListenerService
    6. 1.6. Android账号和同步机制
    7. 1.7. Android多进程
  2. 2. 现有方法
    1. 2.1. 网络连接保活方法
    2. 2.2. 双service(通知栏) 提高进程优先级
    3. 2.3. Service及时拉起
    4. 2.4. 守护进程/进程互拉
    5. 2.5. Linux Am命令开启后台进程
    6. 2.6. NotificationListenerService通知
    7. 2.7. 前台浮窗
  3. 3. 新方法(AccountSync)
    1. 3.1. 思路
    2. 3.2. 效果
    3. 3.3. 风险
    4. 3.4. 实现 (核心代码)
  4. 4. Refs
  5. 5. 后记

基础知识

Android 进程优先级

1 进程优先级等级一般分法

  • Activte process
  • Visible Process
  • Service process
  • Background process
  • Empty process

2 Service技巧

  • onStartCommand返回START_STICKY
  • onDestroy中startself
  • Service后台变前置,setForground(true)
  • android:persistent = “true”

3 进程优先级号

ProcessList.java

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
62
63
64
65
66
67
// Adjustment used in certain places where we don't know it yet.
// (Generally this is something that is going to be cached, but we
// don't know the exact value in the cached range to assign yet.)
static final int UNKNOWN_ADJ = 16;
// This is a process only hosting activities that are not visible,
// so it can be killed without any disruption.
static final int CACHED_APP_MAX_ADJ = 15;
static final int CACHED_APP_MIN_ADJ = 9;
// The B list of SERVICE_ADJ -- these are the old and decrepit
// services that aren't as shiny and interesting as the ones in the A list.
static final int SERVICE_B_ADJ = 8;
// This is the process of the previous application that the user was in.
// This process is kept above other things, because it is very common to
// switch back to the previous app. This is important both for recent
// task switch (toggling between the two top recent apps) as well as normal
// UI flow such as clicking on a URI in the e-mail app to view in the browser,
// and then pressing back to return to e-mail.
static final int PREVIOUS_APP_ADJ = 7;
// This is a process holding the home application -- we want to try
// avoiding killing it, even if it would normally be in the background,
// because the user interacts with it so much.
static final int HOME_APP_ADJ = 6;
// This is a process holding an application service -- killing it will not
// have much of an impact as far as the user is concerned.
static final int SERVICE_ADJ = 5;
// This is a process with a heavy-weight application. It is in the
// background, but we want to try to avoid killing it. Value set in
// system/rootdir/init.rc on startup.
static final int HEAVY_WEIGHT_APP_ADJ = 4;
// This is a process currently hosting a backup operation. Killing it
// is not entirely fatal but is generally a bad idea.
static final int BACKUP_APP_ADJ = 3;
// This is a process only hosting components that are perceptible to the
// user, and we really want to avoid killing them, but they are not
// immediately visible. An example is background music playback.
static final int PERCEPTIBLE_APP_ADJ = 2;
// This is a process only hosting activities that are visible to the
// user, so we'd prefer they don't disappear.
static final int VISIBLE_APP_ADJ = 1;
// This is the process running the current foreground app. We'd really
// rather not kill it!
static final int FOREGROUND_APP_ADJ = 0;
// This is a process that the system or a persistent process has bound to,
// and indicated it is important.
static final int PERSISTENT_SERVICE_ADJ = -11;
// This is a system persistent process, such as telephony. Definitely
// don't want to kill it, but doing so is not completely fatal.
static final int PERSISTENT_PROC_ADJ = -12;
// The system process runs at the default adjustment.
static final int SYSTEM_ADJ = -16;
// Special code for native processes that are not being managed by the system (so
// don't have an oom adj assigned by the system).
static final int NATIVE_ADJ = -17;

Android Low Memory Killer

Android系统内存不足时,系统会杀掉一部分进程以释放空间,谁生谁死的这个生死大权就是由LMK所决定的,这就是Android系统中的Low Memory Killer,其基于Linux的OOM机制,其阈值定义如下面所示的lowmemorykiller文件中,当然也可以通过系统的init.rc实现自定义。
lowmemorykiller.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static uint32_t lowmem_debug_level = 1;
static int lowmem_adj[6] = {
0,
1,
6,
12,
};
static int lowmem_adj_size = 4;
static int lowmem_minfree[6] = {
3 * 512, /* 6MB */
2 * 1024, /* 8MB */
4 * 1024, /* 16MB */
16 * 1024, /* 64MB */
};
static int lowmem_minfree_size = 4;

在Low Memory Killer中通过进程的oom_adj与占用内存的大小决定要杀死的进程,oom_adj值越小越不容易被杀死。其中,lowmem_minfree是杀进程的时机,谁被杀,则取决于lowmem_adj,具体值得含义参考上面 Android进程优先级 所述.

在init.rc中定义了init进程(系统进程)的oom_adj为-16,其不可能会被杀死(init的PID是1),而前台进程是0(这里的前台进程是指用户正在使用的Activity所在的进程),用户按Home键回到桌面时的优先级是6,普通的Service的进程是8.
init.rc

1
2
# Set init and its forked children's oom_adj.
write /proc/1/oom_adj -16

关于Low Memory Killer的具体实现原理可参考Ref-2.

查看某个App的进程

步骤(手机与PC连接)

  1. adb shell
  2. ps | grep 进程名
  3. cat /proc/pid/oom_adj //其中pid是上述grep得到的进程号

Linux AM命令

am命令:在Android系统中通过adb shell 启动某个Activity、Service、拨打电话、启动浏览器等操作Android的命令.其源码在Am.java中,在shell环境下执行am命令实际是启动一个线程执行Am.java中的主函数(main方法),am命令后跟的参数都会当做运行时参数传递到主函数中,主要实现在Am.java的run方法中。

拨打电话
命令:am start -a android.intent.action.CALL -d tel:电话号码
示例:am start -a android.intent.action.CALL -d tel:10086

打开一个网页
命令:am start -a android.intent.action.VIEW -d 网址
示例:am start -a android.intent.action.VIEW -d http://www.skyseraph.com

启动一个服务
命令:am startservice <服务名称>
示例:am startservice -n com.android.music/ com.android.music.MediaPlaybackService

NotificationListenerService

“A service that receives calls from the system when new notifications are posted or removed, or their ranking changed.” From Google

用来监听到通知的发送以及移除和排名位置变化,如果我们注册了这个服务,当系统任何一条通知到来或者被移除掉,我们都能通过这个service来监听到,甚至可以做一些管理工作。

Android账号和同步机制

属于Android中较偏冷的知识,具体参考 Ref 3 /4 /5

Android多进程

  • 实现:android:process
  • 好处:一个独立的进程可以充分利用自己的RAM预算,使其主进程拥有更多的空间处理资源。此外,操作系统对待运行在不同组件中的进程是不一样的。这意味着,当系统运行在低可用内存的条件时,并不是所有的进程都会被杀死
  • 大坑:每一个进程将有自己的Dalvik VM实例,意味着你不能通过这些实例共享数据,至少不是传统意义上的。例如,静态字段在每个进程都有自己的值,而不是你倾向于相信的只有一个值。
  • 更多详细请参考Ref 9

现有方法

网络连接保活方法

A. GCM
B. 公共的第三方push通道(信鸽等)
C. 自身跟服务器通过轮询,或者长连接
具体实现请参考 微信架构师杨干荣的”微信Android客户端后台保活经验分享” (Ref-1).

双service(通知栏) 提高进程优先级

思路:(API level > 18 )

  • 应用启动时启动一个假的Service(FakeService), startForeground(),传一个空的Notification
  • 启动真正的Service(AlwaysLiveService),startForeground(),注意必须相同Notification ID
  • FakeService stopForeground()

效果:通过adb查看,运行在后台的服务其进程号变成了1(优先级仅次于前台进程)

风险:Android系统前台service的一个漏洞,可能在6.0以上系统中修复

实现:核心代码如下

  • AlwaysLiveService 常驻内存服务
1
2
3
4
5
6
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(R.id.notify, new Notification());
startService(new Intent(this, FakeService.class));
return super.onStartCommand(intent, flags, startId);
}
  • FakeService 临时服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class FakeService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(R.id.notify, new Notification());
stopSelf();
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
stopForeground(true);
super.onDestroy();
}
}

Service及时拉起

AlarmReceiver, ConnectReceiver,BootReceiver等

  • Service设置(见上面基础部分)
  • 通过监听系统广播,如开机,锁屏,亮屏等重新启动服务
  • 通过alarm定时器,启动服务

守护进程/进程互拉

在分析360手机助手app时,发现其拥有N多个进程,一个进程kill后会被其它未kill的进程拉起,这也是一种思路吧,虽然有点流氓~
守护进程一般有这样两种方式:

  • 多个java进程守护互拉
  • 底层C守护进程拉起App上层/java进程

Linux Am命令开启后台进程

一种底层实现让进程不被杀死的方法,在Android4.4以上可能有兼容性问题,具体参考Ref-7

NotificationListenerService通知

一种需要用户允许特定权限的系统拉起方式,4.3以上系统

前台浮窗

有朋友提出一种应用退出后启动一个不可交互的浮窗,个人觉得这种方法是无效的,读者有兴趣可以一试,参考Pixel-Activity-Keep-Alive

新方法(AccountSync)

思路

利用Android系统提供的账号和同步机制实现

效果

  • 通过adb查看,运行在后台的服务其进程号变成了1(优先级仅次于前台进程),能提高进程优先级,对比如下图

正常情况

采用AccountSyncAdapter方法后

  • 进程被系统kill后,可以由syn拉起

风险

  • SyncAdapter时间进度不高,往往会因为手机处于休眠状态,而时间往后调整,同步间隔最低为1分钟
  • 用户可以单独停止或者删除,有些手机账号默认是不同步的,需要手动开启

实现 (核心代码)

1 建立数据同步系统(ContentProvider)

通过一个ContentProvider用来作数据同步,由于并没有实际数据同步,所以此处就直接建立一个空的ContentProvider即可

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
public class XXAccountProvider extends ContentProvider {
public static final String AUTHORITY = "包名.provider";
public static final String CONTENT_URI_BASE = "content://" + AUTHORITY;
public static final String TABLE_NAME = "data";
public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_BASE + "/" + TABLE_NAME);
@Override
public boolean onCreate() {
return true;
}
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(Uri uri) {
return new String();
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}

然后再Manifest中声明

1
2
3
4
5
<provider
android:name="**.XXAccountProvider"
android:authorities="@string/account_auth_provider"
android:exported="false"
android:syncable="true"/>

2 建立Sync系统 (SyncAdapter)

通过实现SyncAdapter这个系统服务后, 利用系统的定时器对程序数据ContentProvider进行更新,具体步骤为:

  • 创建Sync服务
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
public class XXSyncService extends Service {
private static final Object sSyncAdapterLock = new Object();
private static XXSyncAdapter sSyncAdapter = null;
@Override
public void onCreate() {
synchronized (sSyncAdapterLock) {
if (sSyncAdapter == null) {
sSyncAdapter = new XXSyncAdapter(getApplicationContext(), true);
}
}
}
@Override
public IBinder onBind(Intent intent) {
return sSyncAdapter.getSyncAdapterBinder();
}
static class XXSyncAdapter extends AbstractThreadedSyncAdapter {
public XXSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
getContext().getContentResolver().notifyChange(XXAccountProvider.CONTENT_URI, null, false);
}
}
}
  • 声明Sync服务
1
2
3
4
5
6
7
8
9
10
11
12
<service
android:name="**.XXSyncService"
android:exported="true"
android:process=":core">
<intent-filter>
<action
android:name="android.content.SyncAdapter"/>
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_adapter"/>
</service>

其中sync_adapter为:

1
2
3
4
5
6
7
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_auth_type"
android:allowParallelSyncs="false"
android:contentAuthority="@string/account_auth_provide"
android:isAlwaysSyncable="true"
android:supportsUploading="false"
android:userVisible="true"/>

参数说明:

android:contentAuthority 指定要同步的ContentProvider在其AndroidManifest.xml文件中有个android:authorities属性。
android:accountType 表示进行同步的账号的类型。
android:userVisible 设置是否在“设置”中显示
android:supportsUploading 设置是否必须notifyChange通知才能同步
android:allowParallelSyncs 是否支持多账号同时同步
android:isAlwaysSyncable 设置所有账号的isSyncable为1
android:syncAdapterSettingsAction 指定一个可以设置同步的activity的Action。

  • 账户调用Sync服务
    首先配置好Account(第三步),然后再通过ContentProvider实现
    手动更新
1
2
3
4
5
6
7
8
9
public void triggerRefresh() {
Bundle b = new Bundle();
b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
ContentResolver.requestSync(
account,
CONTENT_AUTHORITY,
b);
}

添加账号

1
2
3
Account account = AccountService.GetAccount();
AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
accountManager.addAccountExplicitly(...)

同步周期设置

1
2
3
ContentResolver.setIsSyncable(account, CONTENT_AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, CONTENT_AUTHORITY, true);
ContentResolver.addPeriodicSync(account, CONTENT_AUTHORITY, new Bundle(), SYNC_FREQUENCY);

3 建立账号系统 (Account Authenticator)

通过建立Account账号,并关联SyncAdapter服务实现同步

  • 创建Account服务
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
public class XXAuthService extends Service {
private XXAuthenticator mAuthenticator;
@Override
public void onCreate() {
mAuthenticator = new XXAuthenticator(this);
}
private XXAuthenticator getAuthenticator() {
if (mAuthenticator == null)
mAuthenticator = new XXAuthenticator(this);
return mAuthenticator;
}
@Override
public IBinder onBind(Intent intent) {
return getAuthenticator().getIBinder();
}
class XXAuthenticator extends AbstractAccountAuthenticator {
private final Context context;
private AccountManager accountManager;
public XXAuthenticator(Context context) {
super(context);
this.context = context;
accountManager = AccountManager.get(context);
}
@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options)
throws NetworkErrorException {
// 添加账号 示例代码
final Bundle bundle = new Bundle();
final Intent intent = new Intent(context, AuthActivity.class);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)
throws NetworkErrorException {
// 认证 示例代码
String authToken = accountManager.peekAuthToken(account, getString(R.string.account_token_type));
//if not, might be expired, register again
if (TextUtils.isEmpty(authToken)) {
final String password = accountManager.getPassword(account);
if (password != null) {
//get new token
authToken = account.name + password;
}
}
//without password, need to sign again
final Bundle bundle = new Bundle();
if (!TextUtils.isEmpty(authToken)) {
bundle.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
bundle.putString(AccountManager.KEY_AUTHTOKEN, authToken);
return bundle;
}
//no account data at all, need to do a sign
final Intent intent = new Intent(context, AuthActivity.class);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
intent.putExtra(AuthActivity.ARG_ACCOUNT_NAME, account.name);
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
@Override
public String getAuthTokenLabel(String authTokenType) {
// throw new UnsupportedOperationException();
return null;
}
@Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
return null;
}
@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)
throws NetworkErrorException {
return null;
}
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)
throws NetworkErrorException {
return null;
}
@Override
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features)
throws NetworkErrorException {
return null;
}
}
}
  • 声明Account服务
1
2
3
4
5
6
7
8
9
10
11
12
<service
android:name="**.XXAuthService"
android:exported="true"
android:process=":core">
<intent-filter>
<action
android:name="android.accounts.AccountAuthenticator"/>
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator"/>
</service>

其中authenticator为:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_auth_type"
android:icon="@drawable/icon"
android:smallIcon="@drawable/icon"
android:label="@string/app_name"
/>

Refs

  1. 微信Android客户端后台保活经验分享

  2. Android Low Memory Killer原理

  3. stackOverflow 上介绍的双Service方法

  4. Write your own Android Sync Adapter

  5. Write your own Android Authenticator

  6. Android developer

  7. Android篇从底层实现让进程不被杀死(失效Closed)

  8. Android 4.3+ NotificationListenerService 的使用

  9. Going multiprocess on Android

后记

2016.5.24

1. 本文发布时间写错了,5.19手贱成了6.19,就酱紫吧,懒得改了,五月份看过的童鞋就当狠狠滴穿越了一把吧,O(∩_∩)O哈哈哈~~

2. 本文在V2EX稀土掘金博客园CSDN等等诸多网站上有转载或发布,收到了很多评论和讨论,其中有一部分以“天下兴亡匹夫有责”的心态批判笔者等同类开发者把Android生态给搞坏了,提到iOS的诸多好处等等,阐述几点个人观点:

据笔者研究,目前双Service拉起的方式在国内排前几的应用(微信/支付宝等等)中都有用到,进程互拉方式在360手机助手、应用宝等应用中有用到,这些才是真正黑科技,笔者提到的方法仅仅是取巧性的用到了Android系统提供的方法,谈不上XXX~~

iOS的封闭造就其天然的优势,不存在这些问题; 而Android的开源,有诸多问题但不可否认的是其促进了技术的发展,科技的发展甚至人类的进步。 物极必反,很多事情都是双刃剑~

后来经一些网友提醒,发现所谓提异议的这群家伙都是产品汪,半吊子技术,所以XXOO~~

法海无涯,技术无边,风涯无罪,南无阿弥陀佛~~

本文首发于skyseraph.com“一种提高Android应用进程存活率新方法”
同步发表/转载 cnBlogs / CSDN / 伯乐在线



By SkySeraph-2016

版权声明


SkySeraph by SkySeraph is licensed under a Creative Commons BY-NC-ND 4.0 International License.
Bob创作并维护的SkySeraph博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证.
本文首发于SkySeraph博客( http://skyseraph.com ),版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

微信扫码打赏SkySeraph

如果您愿意捐助其它金额请戳我~~,扫码支付宝/微信

本文永久链接:http://skyseraph.com/2016/06/19/Android/一种提高Android应用进程存活率新方法/

Comments

一道Android OpenGL笔试题

Catalogue
  1. 1. 题目
  2. 2. 答案
  3. 3. 思考/讨论
  4. 4. 后记

题目

  • 设计一个Android平台的Gallery组件,要求Gallery中每个item内的图片显示达成有效显示的最大精度,并保证Gallery在滚屏时能够全60FPS帧率地及时显示出加载的图片。请详细说明实现架构、关键技术点及APIs。
  • 约束条件:GPU空间传输通道带宽较小,对于400*400pixel尺寸的图像而言,一次传输耗时超过30ms。

答案

当时的答案如下:

1. Gallery组件类图

Gallery组件类图设计如下图所示,其中:

  • GalleryView为自定义Gallery组件视图;
  • GalleryRenderer为GLSurfaceView的Renderer.
  • AbstractImage为抽象的ImageView基类,子类继承实现loadGLTexture和Draw
  • DataCache为图片缓存类,NetworkHelper为NetWork模块的管理类,负责图片的下载.

2. 关键技术点及相关API设计

2.1 最大精度下60fps帧率显示图片

方案1: VBO/FBO方案
VBO(Vertex Buffer Array): OpenGL存储顶点数据的高速缓存,可减少渲染时间,相关APIs如下(OpenGL ES 2.0以上)

1
2
3
4
GLES20.glGenBuffers(1, vboId, 0);//申请
GLES20.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vboId[0]);//绑定
GLES20.glBufferData(GLES30.GL_ARRAY_BUFFER, vertexBuffer.capacity() * 4,vertexBuffer,GLES30.GL_STATIC_DRAW);//存储
GLES20.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);

参考

PBO(Pixel Buffer Object):OpenGL存储像素数据的高速缓存,可实现快速的像素数据传递,减少数据的拷贝/传递时间,相关APIs如下(OpenGL ES 3.0以上)

1
2
3
4
5
6
7
GLES30.glReadBuffer(GLES30.GL_BACK); //set framebuffer to read from
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mPboHandleContainer[0]); // bind pbo
GLES30.glReadPixels(0, 0, width, height, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, pboByteBuffer); // read pixels
ByteBuffer byteBuffer =((ByteBuffer) GLES30.glMapBufferRange(GLES30.GL_PIXEL_PACK_BUFFER, 0, 4 * mWidth * mHeight,
GLES30.GL_MAP_READ_BIT)).order(ByteOrder.nativeOrder()); // map pbo to bb
GLES30.glUnmapBuffer(GLES30.GL_PIXEL_PACK_BUFFER);// unmap pbo
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0);// unbind pbo

PBO DMA异步快速传递原理及多缓存对象设计

  • 如下面两图所示,其中上图是传统的方法从图像源载入图像数据到纹理对象的过程,像素数据首先存到系统内存中,接着使用glTexImage2D将数据从系统内存拷贝到纹理对象,包含的两个子过程均需要有CPU执行;而下图中,像素数据是直接载入到PBO中,这个过程仍需要CPU来执行,但是从数据从PBO到纹理对象的过程则由GPU来执行DMA,不需要CPU参与。而且opengl可安排异步DMA,不必马上进行像素数据的传递。因此,相比而言,下图中的glTexImage2D立即返回而不是马上执行,这样CPU可以执行其它的操作而不需要等待像素数据传递的结束。


  • 如下图所示表示同时使用了两个PBO。在glTexSubImage2D将像素数据从PBO拷贝出来的同时,另一份像素数据写进了另一个PBO。即在第n帧时,PBO1用于glTexSubImage2D,而PBO2用于生成一个新的纹理对象了。再到n+1帧时,两个PBO则互换了角色。由于异步DMA传递,像素数据的更新和拷贝过程可同时进行,即CPU将纹理源更新到PBO,同时GPU将从另一PBO中拷贝出纹理,从而提高处理速度。

参考(图片来源)

方案二:Triple-Buffering等

Triple-Buffering基本思路
frame N正在显示时,frameN+1已经缓存并准备好(Only ready to show),frameN+2已经开始获取

纹理索引基本思路
所有图片都对应一个索引值,cache on memory,preloading,滑动时快速切换索引

2.2 Gallery组件架构

Gallery组件是一个独立的模块,前期设计时需考虑后期的可维护性性及可可扩展性等进行综合考虑,
例如Gallery中图片的来源,可能来自本地或者网络下载,这样设计时需要将图片加载的模块单独抽离出来以便可扩展性等; 同时,从框架上考虑,Android组件的设计在具体情况下可考虑MVC/MVP等模式,这样不仅便于后期可维护可扩展,另外也能很好的将Model、View和Control/Presenter分离.

思考/讨论

  • 由于该题是OpenGL题目,所以笔者在回答时重点侧重在图片的显示上,而对图片的加载直接忽略掉了,后面想一下,还是在上述答案中加上对Android中图片下载的多级缓存设计会比较好。

  • OpenGL渲染深入

大家有好的答案或建议可直接下面留言回复,一起讨论! ~

后记

本文首发于skyseraph.com“一道Android OpenGL笔试题”
同步发表/转载 cnBlogs / CSDN /



By SkySeraph-2016

版权声明


SkySeraph by SkySeraph is licensed under a Creative Commons BY-NC-ND 4.0 International License.
Bob创作并维护的SkySeraph博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证.
本文首发于SkySeraph博客( http://skyseraph.com ),版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

微信扫码打赏SkySeraph

如果您愿意捐助其它金额请戳我~~,扫码支付宝/微信

本文永久链接:http://skyseraph.com/2016/05/05/AR_VR/一道Android OpenGL笔试题/

Comments

完整开源项目荟萃(iOS篇)

Catalogue
  1. 1. Introduction
    1. 1.1. About
    2. 1.2. Contribute
    3. 1.3. History
  2. 2. Categories
    1. 2.1. 新闻/资讯
    2. 2.2. 社交/聊天
    3. 2.3. 读书/图片/音乐/视频
    4. 2.4. 学习/工具
    5. 2.5. 游戏
    6. 2.6. 高仿系列
    7. 2.7. 其它

Introduction

About

  • “【All-OpenSource-Project】” 致力于收集完整优质/优秀/优美完整的Android/iOS/Web等开源项目
  • 欢迎您的加入! Fork or Email to Me, tks!

Contribute

SkySeraph

History

  • 2015.11.30 First Create

Categories


新闻/资讯

  • ioctocat GitHub iOS客户端
    iOctocat v1 - GitHub for iOS (works on the iPhone, iPad, and iPod Touch) http://ioctocat.com/

  • iphone-app 开源中国社区 iPhone 客户端

  • GameDaily 电竞第一视角
    iOS Game Video App compatible with iPhone and iPad.


社交/聊天


读书/图片/音乐/视频


学习/工具


游戏


高仿系列

  • meituan 高仿美团
    高仿美团iOS版,版本号5.7

  • nuomi 高仿百度糯米
    高仿百度糯米iOS版,版本号5.13.0


其它

  • MGBoxKit iOS 优雅组件
    Simple, quick iOS tables, grids, and more

By SkySeraph 11/30/2015


版权声明


SkySeraph by SkySeraph is licensed under a Creative Commons BY-NC-ND 4.0 International License.
Bob创作并维护的SkySeraph博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证.
本文首发于SkySeraph博客( http://skyseraph.com ),版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

微信扫码打赏SkySeraph

如果您愿意捐助其它金额请戳我~~,扫码支付宝/微信

本文永久链接:http://skyseraph.com/2015/11/30/iOS/完整开源项目荟萃(iOS篇)/

Comments

完整开源项目荟萃(Android篇)

Catalogue
  1. 1. Introduction
    1. 1.1. About
    2. 1.2. Contribute
    3. 1.3. History
  2. 2. Categories
    1. 2.1. 新闻/资讯
    2. 2.2. 社交/聊天
    3. 2.3. 读书/图片/音乐/视频
    4. 2.4. 工具/学习
      1. 2.4.1. todo系列
      2. 2.4.2. Android辅助工具
    5. 2.5. 游戏
    6. 2.6. 高仿系列
    7. 2.7. Android组件篇
    8. 2.8. 其它

Introduction

About

  • “【All-OpenSource-Project】” 致力于收集完整优质/优秀/优美完整的Android/iOS/Web等开源项目
  • 欢迎您的加入! Fork or Email to Me, tks!

Contribute

SkySeraph

History

  • 2015.11.30 First Create

Categories


新闻/资讯

  • Github GitHub 客户端
    An elegent Android Client for Github. 不仅仅是Github客户端,而且是一个发现优秀Github开源项目的app

  • GithubTrends GitHub Trending
    GitHub Trending repositories Viewer with Material Design

  • TopNews 头条新闻
    高仿“今日头条”客户端,实现它的大部分功能

  • 36krReader 36氪客户端
    MD风格的36氪Android阅读客户端

  • cnblogs 博客园客户端
    博客园客户端(作者有保留,未完全开源)

  • FanfouDaily 饭否精选

  • Meizhi 干妹子
    每天自动更新一张精选妹纸图、一个小视频、一系列精选程序猿干货(周末不更新)

  • GanHuoIO
    基于Gank.IO提供的API的第三方客户端(RxJava+Retrofit+MVP)

  • ZhiHuDaily-React-Native 知乎日报

  • ZhihuDailyPurify 更纯净的知乎日报

  • ZhuanLan 知乎专栏

  • LookLook 新闻阅读,知乎日报等

  • android-arsenal.com android-arsenal.com客户端

  • POCenter 整合多个软件外包平台项目信息

社交/聊天

  • MyDiary 君の名は。, 你的名字


读书/图片/音乐/视频

  • SimplifyReader
    一款基于Google Material Design设计开发的Android客户端,包括新闻简读,图片浏览,视频爽看 ,音乐轻听以及二维码扫描五个子模块

  • shuba 书吧
    专注为用户提供人性化的搜书引擎,快捷,方便,为用户找到最好的小说

  • JamsMusicPlayer 强大的音乐播放器
    A free, powerful and elegant music player for Android.

  • Material-Movies MD风格视频播放器
    An application about movies with material design


工具/学习

todo系列

  • Minimal-Todo
    A fully Material ToDo app with minimal features, just enough to be useful.

  • Todo-Android
    Todo Android App using Realm and Lolipop Material Design.

  • TodayThing
    一款简单的 Material Design 风格的 to do list 应用

Android辅助工具

  • MobileGuard 一个管理安卓手机的应用,支持安卓7.0
  • MemoryMonitor
    一款开发者专用的内存清理、Pss监控工具

游戏


高仿系列


Android组件篇


其它

By SkySeraph 11/30/2015


版权声明


SkySeraph by SkySeraph is licensed under a Creative Commons BY-NC-ND 4.0 International License.
Bob创作并维护的SkySeraph博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证.
本文首发于SkySeraph博客( http://skyseraph.com ),版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

微信扫码打赏SkySeraph

如果您愿意捐助其它金额请戳我~~,扫码支付宝/微信

本文永久链接:http://skyseraph.com/2015/11/30/Android/完整开源项目荟萃(Android篇)/

Comments