侃遍天下无二人 发表于 2022-10-31 16:36

解决飞鸽传输2.3.6在安卓11/13上无法调用apk安装器的问题

本帖最后由 侃遍天下无二人 于 2023-8-26 15:25 编辑

【修复所写代码已开源,见 https://gitee.com/kbtxwer/feige-fix/】
上回书说到:我们通过修改返回值去掉了飞鸽传输的广告,但飞鸽传输毕竟是很古老的软件了,在安卓11上虽然还基本能正常运行,但无法通过它直接安装收到的apk,这会给我的使用带来些许不便
原本还可以利用ES文件中转站解决,但我最近破解的旧版ES文件浏览器没这个功能,于是只好上网找找安卓11上安装软件的新方法,尝试融进飞鸽传输中:
根据 Android Apk安装(兼容Android11 Api30)中的描述,安卓11废弃了 Intent.ACTION_INSTALL_PACKAGE 字段,使以前的安装方法不再可用
文中还给了示例代码,不过引入kt会让安装包体积大增,因此要转换为java的实现。

用jadx打开上回修改好的飞鸽传输,定位到 com.tj.feige.app.a.e 的577行附近,发现一个静态无返回值的b(Context context, String str)方法,这就是我们要改的方法了:


可以看出它在无条件调用 android.intent.action.VIEW,而我们要让这个方法在传入的文件后缀为apk,且安卓版本不小于11(sdk 30)时,走我们新增的逻辑(代码不能在jadx里直接修改,手动改smail也不现实,所以我在这里用Android Studio重新建立简化的开发环境写代码)

package com.tj.feige.app.a;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;

import com.kbtx.AppInstaller;

import java.io.File;
import java.io.IOException;

public class e {
    private static String a(File file){
      return "";
    };
    private static void a(Context context, String str){}

    @SuppressLint("WrongConstant")
    public static void b(Context context, String str){
      File file = new File(str);
      Intent intent = new Intent();
      intent.addFlags(268435456);
      if(file.getName().endsWith(".apk") && Build.VERSION.SDK_INT >= 30){
            intent.setClass(context, AppInstaller.class);
      }else {
            intent.setAction("android.intent.action.VIEW");
      }
      intent.setDataAndType(Uri.fromFile(file),a(file));
      context.startActivity(intent);
    }
}
我们在Android Studio里新建的类路径应当和飞鸽传输里原有的完全一致,这样就可以直接从编译出来的dex中提取对应的smail汇编直接覆盖,如果这个方法调用了dex里的其他函数,只要建一个空壳,然后假装调用就行,确保编译过程不出错。

注意到代码里调用了 AppInstaller.class,这是我仿照文章用Java实现的安装器,代码如下:
package com.kbtx;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageInstaller;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class AppInstaller extends Activity implements View.OnClickListener {
    private static final String action = "com.kbtx.Install_APK";
    private boolean should_kill = false;
    @Override
    protected void onCreate(@nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      Uri uri = getIntent().getData();
      should_kill = false;
      if (uri != null && uri.getScheme().equals("file")) {
            File file = new File(uri.getPath());
            try {
                Install(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
      }
    }

    @Override
    protected void onResume() {
      super.onResume();
      if(should_kill) finish();
      should_kill = true;
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onNewIntent(Intent intent) {
      super.onNewIntent(intent);
      if(intent==null || !intent.getAction().equals(action)) return;
      int status = intent.getExtras().getInt(PackageInstaller.EXTRA_STATUS);
      switch (status){
            case PackageInstaller.STATUS_PENDING_USER_ACTION:{
                startActivity((Intent) intent.getExtras().get(Intent.EXTRA_INTENT));
                break;
            }
            case PackageInstaller.STATUS_SUCCESS:{
                Log.i("FeiGe","应用安装成功");
                break;
            }
            case PackageInstaller.STATUS_FAILURE:{
                Log.i("FeiGe","应用安装失败");
                break;
            }
      }
    }

    public void Install(File apk) throws IOException {
      if(Build.VERSION.SDK_INT < 30) return;
      PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
      PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
      int sessionId = packageInstaller.createSession(params);
      PackageInstaller.Session session = packageInstaller.openSession(sessionId);
      addApkToInstallSession(apk,session);
      Intent intent = new Intent(this,AppInstaller.class);
      intent.setAction(action);
      PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,0);
      IntentSender statRecv = pendingIntent.getIntentSender();
      session.commit(statRecv);
    }

    private void addApkToInstallSession(File apk, PackageInstaller.Session session){
      if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            Log.e("feige","准备为新版安卓设备安装apk...");
            Log.e("feige","安装包路径: " + apk.getAbsolutePath());
            try(OutputStream packageInSession = session.openWrite("package",0,-1);
                InputStream is = new FileInputStream(apk)
            ){
                byte[] buffer = new byte;
                int n;
                while((n = is.read(buffer)) >= 0){
                  packageInSession.write(buffer,0,n);
                }
            }catch (IOException e){
                e.printStackTrace();
                Log.e("feige","传输进程出错!");
                return;
            }
            Log.e("feige","数据传输完毕");
      }
    }

    @Override
    public void onClick(View view) {
      finish();
    }
}

此部分代码在清单文件中对应的声明如下:

<activity android:name=".AppInstaller" android:launchMode="singleTop"
            android:exported="false">
            <intent-filter>
                <action android:name="com.kbtx.Install_APK" android:exported="true"/>
            </intent-filter>
      </activity>


代码写完后,直接构建apk,找到其中的dex文件,将 AppInstaller.smail放到飞哥传输中对应的位置(保证包名和路径一致),将 e.smail中的 b方法覆盖飞鸽传输原有的。
(注:此步建议把dex传输到手机上操作,否则需要用apktool手动拆包封包,没有mt会员可以用np代替)
接下来就是修改飞鸽传输的清单文件,在里面新增对AppInstaller的声明:

<activity android:name="com.kbtx.AppInstaller" android:launchMode="singleTop"
            android:exported="false">
            <intent-filter>
                <action android:name="com.kbtx.Install_APK" android:exported="true"/>
            </intent-filter>
      </activity>
同时加入允许安装未知应用的权限:

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
    <uses-permission android:name="android.permission.REPLACE_EXISTING_PACKAGE" />


然后重新编译生成apk,得到的飞鸽传输就能在安卓11下直接安装接收到的apk文件了

原版:https://wwd.lanzoue.com/ipchmuqmqaj
上次的去广告成品:https://wwd.lanzoue.com/iWKLcuqmqba
修复安装功能的成品:https://wwd.lanzoue.com/ixoJ10ewts2b
修复安装功能(用了另一套代码)+底部菜单的成品:https://wwd.lanzoue.com/i4PzS0g88tcj
修复所写代码已开源,见 https://gitee.com/kbtxwer/feige-fix/



侃遍天下无二人 发表于 2023-8-26 15:22

hekygogo 发表于 2023-8-26 15:08
谢谢楼主,一直用的飞鸽2007,可惜安卓的不行,找了很多跨平台互传软件feem landrop都不顺手,这个修改版ap ...

这两天升级到安卓13了,发现照样能正常运行

侃遍天下无二人 发表于 2022-11-1 18:43

zoomyou 发表于 2022-11-1 09:53
飞鸽传输,有PC版吗,是不是这个手机软件和PC可以互通联系的?

Windows上有ipmsg或者飞秋,Linux上有iptux,都是可以互通的

ouzhzh 发表于 2022-10-31 16:47

本帖最后由 ouzhzh 于 2022-10-31 16:48 编辑

飞鸽传输在手机上效果如何?使用过的说一下。

coolheat 发表于 2022-10-31 17:08

楼主的技术不错,佩服,下载来试试

冥界3大法王 发表于 2022-10-31 17:18

此贴最大的败笔是没有配图,殊不知红花儿配绿叶?

qqxiazhitmac 发表于 2022-10-31 17:22

感谢分享

weixu 发表于 2022-10-31 17:28

谢谢分享

qqxiazhitmac 发表于 2022-10-31 17:30

感谢分享

侃遍天下无二人 发表于 2022-10-31 19:14

冥界3大法王 发表于 2022-10-31 17:18
此贴最大的败笔是没有配图,殊不知红花儿配绿叶?

补上了,看看还有哪些细节需要补充的

冥界3大法王 发表于 2022-10-31 19:41

侃遍天下无二人 发表于 2022-10-31 19:14
补上了,看看还有哪些细节需要补充的

飞鸽传书的界面最好来一张,这样才承托主人公。@侃遍天下无二人 不然总觉得没有主角。。。

cccyyyccc1 发表于 2022-10-31 20:29

楼主厉害,老物件焕发新活力
页: [1] 2 3 4 5 6
查看完整版本: 解决飞鸽传输2.3.6在安卓11/13上无法调用apk安装器的问题